net.sbbi.upnp.messages.ActionMessage.java Source code

Java tutorial

Introduction

Here is the source code for net.sbbi.upnp.messages.ActionMessage.java

Source

/*
 * ============================================================================
 *                 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;

    }

}