diff options
Diffstat (limited to 'source/net/sbbi/upnp/messages/ActionMessage.java')
| -rw-r--r-- | source/net/sbbi/upnp/messages/ActionMessage.java | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/source/net/sbbi/upnp/messages/ActionMessage.java b/source/net/sbbi/upnp/messages/ActionMessage.java new file mode 100644 index 000000000..f10eb3fef --- /dev/null +++ b/source/net/sbbi/upnp/messages/ActionMessage.java @@ -0,0 +1,437 @@ +/* + * ============================================================================ + * 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; + + } + +} |
