Java tutorial
/* * ============================================================================ * The Apache Software License, Version 1.1 * ============================================================================ * * Copyright (C) 2002 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by SuperBonBon Industries (http://www.sbbi.net/)." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "UPNPLib" and "SuperBonBon Industries" must not be * used to endorse or promote products derived from this software without * prior written permission. For written permission, please contact * info@sbbi.net. * * 5. Products derived from this software may not be called * "SuperBonBon Industries", nor may "SBBI" appear in their name, * without prior written permission of SuperBonBon Industries. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of SuperBonBon Industries. For more information on * SuperBonBon Industries, please see <http://www.sbbi.net/>. */ package net.sbbi.upnp.messages; import java.util.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xml.sax.*; import net.sbbi.upnp.services.*; import javax.xml.parsers.*; /** * Message object for an UPNP action, simply call setInputParameter() to add * the required action message params and then service() to receive the ActionResponse * built with the parsed UPNP device SOAP xml response. * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @version 1.0 */ public class ActionMessage { private final static Log log = LogFactory.getLog(ActionMessage.class); private UPNPService service; private ServiceAction serviceAction; private List inputParameters; /** * Protected constuctor so that only messages factories can build it * @param service the service for which the * @param serviceAction */ protected ActionMessage(UPNPService service, ServiceAction serviceAction) { this.service = service; this.serviceAction = serviceAction; if (serviceAction.getInputActionArguments() != null) { inputParameters = new ArrayList(); } } /** * Method to clear all set input parameters so that * this object can be reused */ public void clearInputParameters() { inputParameters.clear(); } /** * Executes the message and retuns the UPNP device response, according to the UPNP specs, * this method could take up to 30 secs to process ( time allowed for a device to respond to a request ) * @return a response object containing the UPNP parsed response * @throws IOException if some IOException occurs during message send and reception process * @throws UPNPResponseException if an UPNP error message is returned from the server * or if some parsing exception occurs ( detailErrorCode = 899, detailErrorDescription = SAXException message ) */ public ActionResponse service() throws IOException, UPNPResponseException { ActionResponse rtrVal = null; UPNPResponseException upnpEx = null; IOException ioEx = null; StringBuffer body = new StringBuffer(256); body.append("<?xml version=\"1.0\"?>\r\n"); body.append("<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\""); body.append(" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"); body.append("<s:Body>"); body.append("<u:").append(serviceAction.getName()).append(" xmlns:u=\"").append(service.getServiceType()) .append("\">"); if (serviceAction.getInputActionArguments() != null) { // this action requires params so we just set them... for (Iterator itr = inputParameters.iterator(); itr.hasNext();) { InputParamContainer container = (InputParamContainer) itr.next(); body.append("<").append(container.name).append(">").append(container.value); body.append("</").append(container.name).append(">"); } } body.append("</u:").append(serviceAction.getName()).append(">"); body.append("</s:Body>"); body.append("</s:Envelope>"); if (log.isDebugEnabled()) log.debug("POST prepared for URL " + service.getControlURL()); URL url = new URL(service.getControlURL().toString()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestMethod("POST"); HttpURLConnection.setFollowRedirects(false); //conn.setConnectTimeout( 30000 ); conn.setRequestProperty("HOST", url.getHost() + ":" + url.getPort()); conn.setRequestProperty("CONTENT-TYPE", "text/xml; charset=\"utf-8\""); conn.setRequestProperty("CONTENT-LENGTH", Integer.toString(body.length())); conn.setRequestProperty("SOAPACTION", "\"" + service.getServiceType() + "#" + serviceAction.getName() + "\""); OutputStream out = conn.getOutputStream(); out.write(body.toString().getBytes()); out.flush(); out.close(); conn.connect(); InputStream input = null; if (log.isDebugEnabled()) log.debug("executing query :\n" + body); try { input = conn.getInputStream(); } catch (IOException ex) { // java can throw an exception if he error code is 500 or 404 or something else than 200 // but the device sends 500 error message with content that is required // this content is accessible with the getErrorStream input = conn.getErrorStream(); } if (input != null) { int response = conn.getResponseCode(); String responseBody = getResponseBody(input); if (log.isDebugEnabled()) log.debug("received response :\n" + responseBody); SAXParserFactory saxParFact = SAXParserFactory.newInstance(); saxParFact.setValidating(false); saxParFact.setNamespaceAware(true); ActionMessageResponseParser msgParser = new ActionMessageResponseParser(serviceAction); StringReader stringReader = new StringReader(responseBody); InputSource src = new InputSource(stringReader); try { SAXParser parser = saxParFact.newSAXParser(); parser.parse(src, msgParser); } catch (ParserConfigurationException confEx) { // should never happen // we throw a runtimeException to notify the env problem throw new RuntimeException( "ParserConfigurationException during SAX parser creation, please check your env settings:" + confEx.getMessage()); } catch (SAXException saxEx) { // kind of tricky but better than nothing.. upnpEx = new UPNPResponseException(899, saxEx.getMessage()); } finally { try { input.close(); } catch (IOException ex) { // ignore } } if (upnpEx == null) { if (response == HttpURLConnection.HTTP_OK) { rtrVal = msgParser.getActionResponse(); } else if (response == HttpURLConnection.HTTP_INTERNAL_ERROR) { upnpEx = msgParser.getUPNPResponseException(); } else { ioEx = new IOException("Unexpected server HTTP response:" + response); } } } try { out.close(); } catch (IOException ex) { // ignore } conn.disconnect(); if (upnpEx != null) { throw upnpEx; } if (rtrVal == null && ioEx == null) { ioEx = new IOException("Unable to receive a response from the UPNP device"); } if (ioEx != null) { throw ioEx; } return rtrVal; } private String getResponseBody(InputStream in) throws IOException { byte[] buffer = new byte[256]; int readen = 0; StringBuffer content = new StringBuffer(256); while ((readen = in.read(buffer)) != -1) { content.append(new String(buffer, 0, readen)); } // some devices add \0 chars at XML message end // which causes XML parsing errors... int len = content.length(); while (content.charAt(len - 1) == '\0') { len--; content.setLength(len); } return content.toString().trim(); } /** * The list of input parameters that should be accepted by the device service for this message * @return a list of required input parameters ServiceActionArgument objects for this message * or null if the message does not require any input params */ public List getInputParameterNames() { return serviceAction.getInputActionArgumentsNames(); } /** * The list of output parameters that should be returned by the device service * @return a list of output parameters ServiceActionArgument objects for this message * or null if the message does not contains any output params. */ public List getOutputParameterNames() { return serviceAction.getOutputActionArgumentsNames(); } /** * Set the value of an input parameter before a message service call. If the param name already * exists, the param value will be overwritten with the new value provided. * @param parameterName the parameter name * @param parameterValue the parameter value as an object, primitive object are handled, all other object * will be assigned with a call to their toString() method call * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, Object parameterValue) throws IllegalArgumentException { if (parameterValue == null) { return setInputParameter(parameterName, ""); } else if (parameterValue instanceof Date) { return setInputParameter(parameterName, (Date) parameterValue); } else if (parameterValue instanceof Boolean) { return setInputParameter(parameterName, ((Boolean) parameterValue).booleanValue()); } else if (parameterValue instanceof Integer) { return setInputParameter(parameterName, ((Integer) parameterValue).intValue()); } else if (parameterValue instanceof Byte) { return setInputParameter(parameterName, ((Byte) parameterValue).byteValue()); } else if (parameterValue instanceof Short) { return setInputParameter(parameterName, ((Short) parameterValue).shortValue()); } else if (parameterValue instanceof Float) { return setInputParameter(parameterName, ((Float) parameterValue).floatValue()); } else if (parameterValue instanceof Double) { return setInputParameter(parameterName, ((Double) parameterValue).doubleValue()); } else if (parameterValue instanceof Long) { return setInputParameter(parameterName, ((Long) parameterValue).longValue()); } return setInputParameter(parameterName, parameterValue.toString()); } /** * Set the value of an input parameter before a message service call. If the param name already * exists, the param value will be overwritten with the new value provided * @param parameterName the parameter name * @param parameterValue the string parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, String parameterValue) throws IllegalArgumentException { if (serviceAction.getInputActionArguments() == null) throw new IllegalArgumentException("No input parameters required for this message"); ServiceActionArgument arg = serviceAction.getInputActionArgument(parameterName); if (arg == null) throw new IllegalArgumentException("Wrong input argument name for this action:" + parameterName + " available parameters are : " + getInputParameterNames()); for (Iterator i = inputParameters.iterator(); i.hasNext();) { InputParamContainer container = (InputParamContainer) i.next(); if (container.name.equals(parameterName)) { container.value = parameterValue; return this; } } // nothing found add the new value InputParamContainer container = new InputParamContainer(); container.name = parameterName; container.value = parameterValue; inputParameters.add(container); return this; } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the date parameter value, will be automatically translated to the correct * ISO 8601 date format for the given action input param related state variable * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, Date parameterValue) throws IllegalArgumentException { if (serviceAction.getInputActionArguments() == null) throw new IllegalArgumentException("No input parameters required for this message"); ServiceActionArgument arg = serviceAction.getInputActionArgument(parameterName); if (arg == null) throw new IllegalArgumentException("Wrong input argument name for this action:" + parameterName + " available parameters are : " + getInputParameterNames()); ServiceStateVariable linkedVar = arg.getRelatedStateVariable(); if (linkedVar.getDataType().equals(ServiceStateVariable.TIME)) { return setInputParameter(parameterName, ISO8601Date.getIsoTime(parameterValue)); } else if (linkedVar.getDataType().equals(ServiceStateVariable.TIME_TZ)) { return setInputParameter(parameterName, ISO8601Date.getIsoTimeZone(parameterValue)); } else if (linkedVar.getDataType().equals(ServiceStateVariable.DATE)) { return setInputParameter(parameterName, ISO8601Date.getIsoDate(parameterValue)); } else if (linkedVar.getDataType().equals(ServiceStateVariable.DATETIME)) { return setInputParameter(parameterName, ISO8601Date.getIsoDateTime(parameterValue)); } else if (linkedVar.getDataType().equals(ServiceStateVariable.DATETIME_TZ)) { return setInputParameter(parameterName, ISO8601Date.getIsoDateTimeZone(parameterValue)); } else { throw new IllegalArgumentException( "Related input state variable " + linkedVar.getName() + " is not of an date type"); } } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the boolean parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, boolean parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, parameterValue ? "1" : "0"); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the byte parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, byte parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Byte.toString(parameterValue)); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the short parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, short parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Short.toString(parameterValue)); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the integer parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, int parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Integer.toString(parameterValue)); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the long parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, long parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Long.toString(parameterValue)); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the float parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, float parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Float.toString(parameterValue)); } /** * Set the value of an input parameter before a message service call * @param parameterName the parameter name * @param parameterValue the double parameter value * @return the current ActionMessage object instance * @throws IllegalArgumentException if the provided parameterName is not valid for this message * or if no input parameters are required for this message */ public ActionMessage setInputParameter(String parameterName, double parameterValue) throws IllegalArgumentException { return setInputParameter(parameterName, Double.toString(parameterValue)); } /** * Input params class container */ private class InputParamContainer { private String name; private String value; } }