Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.yamahareceiver.internal.protocol.xml; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.util.Arrays; import java.util.Optional; import org.apache.commons.lang.StringUtils; import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * All other protocol classes in this directory use this class for communication. An object * of HttpXMLSendReceive is always bound to a specific host. * * @author David Graeff - Initial contribution * @author Tomasz Maruszak - Minor refactor * */ public class XMLConnection extends AbstractConnection { private Logger logger = LoggerFactory.getLogger(XMLConnection.class); private static final String XML_GET = "<?xml version=\"1.0\" encoding=\"utf-8\"?><YAMAHA_AV cmd=\"GET\">"; private static final String XML_PUT = "<?xml version=\"1.0\" encoding=\"utf-8\"?><YAMAHA_AV cmd=\"PUT\">"; private static final String XML_END = "</YAMAHA_AV>"; private static final String HEADER_CHARSET_PART = "charset="; public XMLConnection(String host) { super(host); } @FunctionalInterface public interface CheckedConsumer<T, R> { R apply(T t) throws IOException; } private <T> T postMessage(String prefix, String message, String suffix, CheckedConsumer<HttpURLConnection, T> responseConsumer) throws IOException { if (message.startsWith("<?xml")) { throw new IOException("No preformatted xml allowed!"); } message = prefix + message + suffix; writeTraceFile(message); URL url = createCrlUrl(); logger.debug("Making POST to {} with payload: {}", url, message); HttpURLConnection connection = null; try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Length", Integer.toString(message.length())); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(true); // Send request try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { wr.writeBytes(message); wr.flush(); } if (connection.getResponseCode() != 200) { throw new IOException("Changing a value on the Yamaha AVR failed: " + message); } return responseConsumer.apply(connection); } finally { if (connection != null) { connection.disconnect(); } } } /** * Post the given xml message * * @param message XML formatted message excluding < ?xml > or <YAMAHA_AV> tags. * @throws IOException */ @Override public void send(String message) throws IOException { postMessage(XML_PUT, message, XML_END, c -> null); } /** * Post the given xml message and return the response as string. * * @param message XML formatted message excluding <?xml> or <YAMAHA_AV> tags. * @return Return the response as text or throws an exception if the connection failed. * @throws IOException */ @Override public String sendReceive(final String message) throws IOException { return postMessage(XML_GET, message, XML_END, c -> consumeResponse(c)); } private String consumeResponse(HttpURLConnection connection) throws IOException { // Read response Charset responseCharset = getResponseCharset(connection, StandardCharsets.UTF_8); try (BufferedReader rd = new BufferedReader( new InputStreamReader(connection.getInputStream(), responseCharset))) { String line; StringBuilder responseBuffer = new StringBuilder(); while ((line = rd.readLine()) != null) { responseBuffer.append(line); responseBuffer.append('\r'); } String response = responseBuffer.toString(); writeTraceFile(response); return response; } } public String getResponse(String path) throws IOException { URL url = createBaseUrl(path); logger.debug("Making GET to {}", url); HttpURLConnection connection = null; try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setUseCaches(false); connection.setDoInput(true); connection.setDoOutput(false); if (connection.getResponseCode() != 200) { throw new IOException("Request failed"); } return consumeResponse(connection); } finally { if (connection != null) { connection.disconnect(); } } } private Charset getResponseCharset(HttpURLConnection connection, Charset defaultCharset) { // See https://stackoverflow.com/a/3934280/1906057 Charset charset = defaultCharset; String contentType = connection.getContentType(); String[] values = contentType.split(";"); // values.length should be 2 // Example: // Content-Type:text/xml; charset="utf-8" Optional<String> charsetName = Arrays.stream(values).map(x -> x.trim()) .filter(x -> x.toLowerCase().startsWith(HEADER_CHARSET_PART)) .map(x -> x.substring(HEADER_CHARSET_PART.length() + 1, x.length() - 1)).findFirst(); if (charsetName.isPresent() && !StringUtils.isEmpty(charsetName.get())) { try { charset = Charset.forName(charsetName.get()); } catch (UnsupportedCharsetException | IllegalCharsetNameException e) { logger.warn("The charset {} provided in the response {} is not supported", charsetName, contentType); } } logger.trace("The charset {} will be used to parse the response", charset); return charset; } /** * Creates an {@link URL} object to the Yamaha control endpoint * * @return * @throws MalformedURLException */ private URL createCrlUrl() throws MalformedURLException { return createBaseUrl("/YamahaRemoteControl/ctrl"); } /** * Creates an {@link URL} object to Yamaha * * @return * @throws MalformedURLException */ private URL createBaseUrl(String path) throws MalformedURLException { return new URL("http://" + host + path); } }