diff --git a/.classpath b/.classpath
index 1b443b512..10fe6dcd2 100644
--- a/.classpath
+++ b/.classpath
@@ -17,7 +17,6 @@
-
@@ -36,6 +35,6 @@
-
+
diff --git a/NOTICE b/NOTICE
index 7a9657377..fdb65db16 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,3 +9,6 @@ Copyright 2001-2008 The Apache Software Foundation
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed by
+SuperBonBon Industries (http://www.sbbi.net/)
\ No newline at end of file
diff --git a/build.xml b/build.xml
index 438832ab9..4c7352e47 100644
--- a/build.xml
+++ b/build.xml
@@ -172,7 +172,7 @@
-
+
@@ -182,7 +182,6 @@
-
diff --git a/lib/commons-jxpath-1.1.License b/lib/commons-jxpath-1.1.License
deleted file mode 100644
index 603fe6a26..000000000
--- a/lib/commons-jxpath-1.1.License
+++ /dev/null
@@ -1,55 +0,0 @@
-/* ====================================================================
- *
- * The Apache Software License, Version 1.1
- *
- * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
- * reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, 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 acknowlegement:
- * "This product includes software developed by the
- * Apache Software Foundation (http://www.apache.org/)."
- * Alternately, this acknowlegement may appear in the software itself,
- * if and wherever such third-party acknowlegements normally appear.
- *
- * 4. The names "The Jakarta Project", "Commons", and "Apache Software
- * Foundation" must not be used to endorse or promote products derived
- * from this software without prior written permission. For written
- * permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache"
- * nor may "Apache" appear in their names without prior written
- * permission of the Apache Group.
- *
- * 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 (INCLUDING, 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 the Apache Software Foundation. For more
- * information on the Apache Software Foundation, please see
- * .
- *
- */
\ No newline at end of file
diff --git a/lib/commons-jxpath-1.1.jar b/lib/commons-jxpath-1.1.jar
deleted file mode 100644
index 9516e2ad6..000000000
Binary files a/lib/commons-jxpath-1.1.jar and /dev/null differ
diff --git a/lib/commons-jxpath-1.3.License b/lib/commons-jxpath-1.3.License
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/lib/commons-jxpath-1.3.License
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/commons-jxpath-1.3.jar b/lib/commons-jxpath-1.3.jar
new file mode 100644
index 000000000..e344a1e66
Binary files /dev/null and b/lib/commons-jxpath-1.3.jar differ
diff --git a/lib/sbbi-upnplib-1.0.4.License b/lib/sbbi-upnplib-1.0.4.License
deleted file mode 100644
index 5b80110cb..000000000
--- a/lib/sbbi-upnplib-1.0.4.License
+++ /dev/null
@@ -1,46 +0,0 @@
- ============================================================================
- 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 .
-
diff --git a/lib/sbbi-upnplib-1.0.4.jar b/lib/sbbi-upnplib-1.0.4.jar
deleted file mode 100644
index 271e919c7..000000000
Binary files a/lib/sbbi-upnplib-1.0.4.jar and /dev/null differ
diff --git a/source/net/sbbi/upnp/Discovery.java b/source/net/sbbi/upnp/Discovery.java
new file mode 100644
index 000000000..ec5cd92ab
--- /dev/null
+++ b/source/net/sbbi/upnp/Discovery.java
@@ -0,0 +1,264 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import net.sbbi.upnp.devices.UPNPRootDevice;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Class to discover an UPNP device on the network.
+ * A multicast socket will be created to discover devices, the binding port for this socket is set to 1901,
+ * if this is causing a problem you can use the net.sbbi.upnp.Discovery.bindPort system property
+ * to specify another port.
+ * The discovery methods only accept matching device description and broadcast message response IP
+ * to avoid a security flaw with the protocol. If you are not happy with such behaviour
+ * you can set the net.sbbi.upnp.ddos.matchip system property to false to avoid this check.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class Discovery {
+
+ private final static Log log = LogFactory.getLog( Discovery.class );
+
+ public final static String ROOT_DEVICES = "upnp:rootdevice";
+ public final static String ALL_DEVICES = "ssdp:all";
+
+ public static final int DEFAULT_MX = 3;
+ public static final int DEFAULT_TTL = 4;
+ public static final int DEFAULT_TIMEOUT = 1500;
+ public static final String DEFAULT_SEARCH = ALL_DEVICES;
+ public static final int DEFAULT_SSDP_SEARCH_PORT = 1901;
+
+ public final static String SSDP_IP = "239.255.255.250";
+ public final static int SSDP_PORT = 1900;
+
+ /**
+ * Devices discovering on all network interfaces with default values, all root devices will be searched
+ * @return an array of UPNP Root device or null if nothing found with the default timeout.
+ * Null does NOT means that no UPNP device is available on the network. It only means
+ * that for this default timeout no devices responded or that effectively no devices
+ * are available at all.
+ * @throws IOException if some IOException occurs during discovering
+ */
+ public static UPNPRootDevice[] discover() throws IOException {
+ return discover( DEFAULT_TIMEOUT, DEFAULT_TTL, DEFAULT_MX, DEFAULT_SEARCH );
+ }
+
+ /**
+ * Devices discovering on all network interfaces with a given root device to search
+ * @param searchTarget the device URI to search
+ * @return an array of UPNP Root device that matches the search or null if nothing found with the default timeout.
+ * Null does NOT means that no UPNP device is available on the network. It only means
+ * that for this given timeout no devices responded or that effectively no devices
+ * are available at all.
+ * @throws IOException if some IOException occurs during discovering
+ */
+ public static UPNPRootDevice[] discover( String searchTarget ) throws IOException {
+ return discover( DEFAULT_TIMEOUT, DEFAULT_TTL, DEFAULT_MX, searchTarget );
+ }
+
+ /**
+ * Devices discovering on all network interfaces with a given timeout and a given root device to search
+ * @param timeOut the time allowed for a device to give a response
+ * @param searchTarget the device URI to search
+ * @return an array of UPNP Root device that matches the search or null if nothing found with the given timeout.
+ * Null does NOT means that no UPNP device is available on the network. It only means
+ * that for this given timeout no devices responded or that effectively no devices
+ * are available at all.
+ * @throws IOException if some IOException occurs during discovering
+ */
+ public static UPNPRootDevice[] discover( int timeOut, String searchTarget ) throws IOException {
+ return discover( timeOut, DEFAULT_TTL, DEFAULT_MX, searchTarget );
+ }
+
+ /**
+ * Devices discovering on all network interfaces with a given timeout and a given root device to search, as well as a ttl and mx param
+ * @param timeOut the timeout for the a device to give a reponse
+ * @param ttl the UDP socket packets time to live
+ * @param mx discovery message mx http header field value
+ * @param searchTarget the device URI to search
+ * @return an array of UPNP Root device that matches the search or null if nothing found within the given timeout.
+ * Null return does NOT means that no UPNP device is available on the network. It only means
+ * that for this given timeout no devices responded or that effectively no devices
+ * are available at all.
+ * @throws IOException if some IOException occurs during discovering
+ */
+ public static UPNPRootDevice[] discover( int timeOut, int ttl, int mx, String searchTarget ) throws IOException {
+ return discoverDevices( timeOut, ttl, mx, searchTarget, null );
+ }
+
+ /**
+ * Devices discovering with a given timeout and a given root device to search on an given network interface, as well as a ttl and mx param
+ * @param timeOut the timeout for the a device to give a reponse
+ * @param ttl the UDP socket packets time to live
+ * @param mx discovery message mx http header field value
+ * @param searchTarget the device URI to search
+ * @param ni the networkInterface where to search devices, null to lookup all interfaces
+ * @return an array of UPNP Root device that matches the search or null if nothing found within the given timeout.
+ * Null return does NOT means that no UPNP device is available on the network. It only means
+ * that for this given timeout no devices responded or that effectively no devices
+ * are available at all.
+ * @throws IOException if some IOException occurs during discovering
+ */
+ public static UPNPRootDevice[] discover( int timeOut, int ttl, int mx, String searchTarget, NetworkInterface ni ) throws IOException {
+ return discoverDevices( timeOut, ttl, mx, searchTarget, ni );
+ }
+
+ private static UPNPRootDevice[] discoverDevices( int timeOut, int ttl, int mx, String searchTarget, NetworkInterface ni ) throws IOException {
+ if ( searchTarget == null || searchTarget.trim().length() == 0 ) {
+ throw new IllegalArgumentException( "Illegal searchTarget" );
+ }
+
+ final Map devices = new HashMap();
+
+ DiscoveryResultsHandler handler = new DiscoveryResultsHandler() {
+
+ public void discoveredDevice( String usn, String udn, String nt, String maxAge, URL location, String firmware ) {
+ synchronized( devices ) {
+ if ( ! devices.containsKey( usn ) ) {
+ try {
+ UPNPRootDevice device = new UPNPRootDevice( location, maxAge, firmware, usn, udn );
+ devices.put( usn, device );
+ } catch ( Exception ex ) {
+ log.error( "Error occured during upnp root device object creation from location " + location, ex );
+ }
+ }
+ }
+ }
+ };
+
+ DiscoveryListener.getInstance().registerResultsHandler( handler, searchTarget );
+ if ( ni == null ) {
+ for ( Enumeration e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
+ NetworkInterface intf = (NetworkInterface)e.nextElement();
+ for ( Enumeration adrs = intf.getInetAddresses(); adrs.hasMoreElements(); ) {
+ InetAddress adr = (InetAddress)adrs.nextElement();
+ if ( adr instanceof Inet4Address && !adr.isLoopbackAddress() ) {
+ sendSearchMessage( adr, ttl, mx, searchTarget );
+ }
+ }
+ }
+ } else {
+ for ( Enumeration adrs = ni.getInetAddresses(); adrs.hasMoreElements(); ) {
+ InetAddress adr = (InetAddress)adrs.nextElement();
+ if ( adr instanceof Inet4Address && !adr.isLoopbackAddress() ) {
+ sendSearchMessage( adr, ttl, mx, searchTarget );
+ }
+ }
+ }
+
+ try {
+ Thread.sleep( timeOut );
+ } catch ( InterruptedException ex ) {
+ // don't care
+ }
+
+ DiscoveryListener.getInstance().unRegisterResultsHandler( handler, searchTarget );
+
+ if ( devices.size() == 0 ) {
+ return null;
+ }
+ int j = 0;
+ UPNPRootDevice[] rootDevices = new UPNPRootDevice[devices.size()];
+ for ( Iterator i = devices.values().iterator(); i.hasNext(); ) {
+ rootDevices[j++] = (UPNPRootDevice)i.next();
+ }
+ return rootDevices;
+
+ }
+
+ /**
+ * Sends an SSDP search message on the network
+ * @param src the sender ip
+ * @param ttl the time to live
+ * @param mx the mx field
+ * @param searchTarget the search target
+ * @throws IOException if some IO errors occurs during search
+ */
+ public static void sendSearchMessage( InetAddress src, int ttl, int mx, String searchTarget ) throws IOException {
+
+ int bindPort = DEFAULT_SSDP_SEARCH_PORT;
+ String port = System.getProperty( "net.sbbi.upnp.Discovery.bindPort" );
+ if ( port != null ) {
+ bindPort = Integer.parseInt( port );
+ }
+ InetSocketAddress adr = new InetSocketAddress( InetAddress.getByName( Discovery.SSDP_IP ), Discovery.SSDP_PORT );
+
+ java.net.MulticastSocket skt = new java.net.MulticastSocket( null );
+ skt.bind( new InetSocketAddress( src, bindPort ) );
+ skt.setTimeToLive( ttl );
+ StringBuffer packet = new StringBuffer();
+ packet.append( "M-SEARCH * HTTP/1.1\r\n" );
+ packet.append( "HOST: 239.255.255.250:1900\r\n" );
+ packet.append( "MAN: \"ssdp:discover\"\r\n" );
+ packet.append( "MX: ").append( mx ).append( "\r\n" );
+ packet.append( "ST: " ).append( searchTarget ).append( "\r\n" ).append( "\r\n" );
+ if ( log.isDebugEnabled() ) log.debug( "Sending discovery message on 239.255.255.250:1900 multicast address form ip " + src.getHostAddress() + ":\n" + packet.toString() );
+ String toSend = packet.toString();
+ byte[] pk = toSend.getBytes();
+ skt.send( new DatagramPacket( pk, pk.length, adr ) );
+ skt.disconnect();
+ skt.close();
+ }
+
+}
diff --git a/source/net/sbbi/upnp/DiscoveryAdvertisement.java b/source/net/sbbi/upnp/DiscoveryAdvertisement.java
new file mode 100644
index 000000000..a88678c46
--- /dev/null
+++ b/source/net/sbbi/upnp/DiscoveryAdvertisement.java
@@ -0,0 +1,379 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * SSDP messages listener Thread, notify registered objects implementing the interface DiscoveryEventHandler
+ * when a device joins the networks or leaves it.
+ * The listener thread is set to only accept matching device description and broadcast message sender IP
+ * to avoid a security flaw with the protocol. If you are not happy with such behaviour
+ * you can set the net.sbbi.upnp.ddos.matchip system property to false to avoid this check.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class DiscoveryAdvertisement implements Runnable {
+
+ private final static Log log = LogFactory.getLog( DiscoveryAdvertisement.class );
+
+ private static boolean MATCH_IP = true;
+
+ static {
+ String prop = System.getProperty( "net.sbbi.upnp.ddos.matchip" );
+ if ( prop != null && prop.equals( "false" ) ) MATCH_IP = false;
+ }
+
+ private static final int DEFAULT_TIMEOUT = 250;
+
+ public final static int EVENT_SSDP_ALIVE = 0;
+ public final static int EVENT_SSDP_BYE_BYE = 1;
+
+ private final static String NTS_SSDP_ALIVE = "ssdp:alive";
+ private final static String NTS_SSDP_BYE_BYE = "ssdp:byebye";
+ private final static String NT_ALL_EVENTS = "DiscoveryAdvertisement:nt:allevents";
+
+ private Map byeByeRegistered = new HashMap();
+ private Map aliveRegistered = new HashMap();
+ private Map USNPerIP = new HashMap();
+
+ private final Object REGISTRATION_PROCESS = new Object();
+
+ private final static DiscoveryAdvertisement singleton = new DiscoveryAdvertisement();
+ private boolean inService = false;
+ private boolean daemon = true;
+
+ private java.net.MulticastSocket skt;
+ private DatagramPacket input;
+
+ private DiscoveryAdvertisement() {
+ }
+
+ public final static DiscoveryAdvertisement getInstance() {
+ return singleton;
+ }
+
+ public void setDaemon( boolean daemon ) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Registers an event category sent by UPNP devices
+ * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE
+ * or DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE
+ * @param nt the type of device advertisement, upnp:rootdevice will return you all advertisement in relation with nt upnp:rootdevice
+ * a null value specify that all nt type are wanted
+ * @param eventHandler the events handler, this objet will receive notifications..
+ * @throws IOException if an error ocurs when the SSDP events listeners threads starts
+ */
+ public void registerEvent( int notificationEvent, String nt, DiscoveryEventHandler eventHandler ) throws IOException {
+ synchronized( REGISTRATION_PROCESS ) {
+ if ( !inService ) startDevicesListenerThread();
+ if ( nt == null ) nt = NT_ALL_EVENTS;
+ if ( notificationEvent == EVENT_SSDP_ALIVE ) {
+ Set handlers = (Set)aliveRegistered.get( nt );
+ if ( handlers == null ) {
+ handlers = new HashSet();
+ aliveRegistered.put( nt, handlers );
+ }
+ handlers.add( eventHandler );
+ } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) {
+ Set handlers = (Set)byeByeRegistered.get( nt );
+ if ( handlers == null ) {
+ handlers = new HashSet();
+ byeByeRegistered.put( nt, handlers );
+ }
+ handlers.add( eventHandler );
+ } else {
+ throw new IllegalArgumentException( "Unknown notificationEvent type" );
+ }
+ }
+ }
+
+ /**
+ * Unregisters an event category sent by UPNP devices
+ * @param notificationEvent the event type, either DiscoveryAdvertisement.EVENT_SSDP_ALIVE
+ * or DiscoveryAdvertisement.EVENT_SSDP_BYE_BYE
+ * @param nt the type of device advertisement, upnp:rootdevice will unregister all advertisement in relation with nt upnp:rootdevice
+ * a null value specify that all nt type are unregistered
+ * @param eventHandler the events handler that needs to be unregistred.
+ */
+ public void unRegisterEvent( int notificationEvent, String nt, DiscoveryEventHandler eventHandler ) {
+ synchronized( REGISTRATION_PROCESS ) {
+ if ( nt == null ) nt = NT_ALL_EVENTS;
+ if ( notificationEvent == EVENT_SSDP_ALIVE ) {
+ Set handlers = (Set)aliveRegistered.get( nt );
+ if ( handlers != null ) {
+ handlers.remove( eventHandler );
+ if ( handlers.size() == 0 ) {
+ aliveRegistered.remove( nt );
+ }
+ }
+ } else if ( notificationEvent == EVENT_SSDP_BYE_BYE ) {
+ Set handlers = (Set)byeByeRegistered.get( nt );
+ if ( handlers != null ) {
+ handlers.remove( eventHandler );
+ if ( handlers.size() == 0 ) {
+ byeByeRegistered.remove( nt );
+ }
+ }
+ } else {
+ throw new IllegalArgumentException( "Unknown notificationEvent type" );
+ }
+ if ( aliveRegistered.size() == 0 && byeByeRegistered.size() == 0 ) {
+ stopDevicesListenerThread();
+ }
+ }
+ }
+
+ private void startDevicesListenerThread() throws IOException {
+ synchronized( singleton ) {
+ if ( !inService ) {
+ this.startMultiCastSocket();
+ Thread deamon = new Thread( this, "DiscoveryAdvertisement daemon" );
+ deamon.setDaemon( daemon );
+ deamon.start();
+ // wait for the thread to be started
+ while( !inService ) {
+ // let's wait a few ms
+ try {
+ Thread.sleep( 2 );
+ } catch( InterruptedException ex ) {
+ // don t care
+ }
+ }
+ }
+ }
+ }
+
+ private void stopDevicesListenerThread() {
+ synchronized( singleton ) {
+ inService = false;
+ }
+ }
+
+ private void startMultiCastSocket() throws IOException {
+
+ skt = new java.net.MulticastSocket( null );
+ skt.bind( new InetSocketAddress( InetAddress.getByName( "0.0.0.0" ), Discovery.SSDP_PORT ) );
+ skt.setTimeToLive( Discovery.DEFAULT_TTL );
+ skt.setSoTimeout( DEFAULT_TIMEOUT );
+ skt.joinGroup( InetAddress.getByName( Discovery.SSDP_IP ) );
+
+ byte[] buf = new byte[2048];
+ input = new DatagramPacket( buf, buf.length );
+
+ }
+
+ public void run() {
+ if ( !Thread.currentThread().getName().equals( "DiscoveryAdvertisement daemon" ) ) {
+ throw new RuntimeException( "No right to call this method" );
+ }
+ inService = true;
+ while ( inService ) {
+ try {
+ listenBroadCast();
+ } catch ( SocketTimeoutException ex ) {
+ // ignoring
+ } catch ( IOException ioEx ) {
+ log.error( "IO Exception during UPNP DiscoveryAdvertisement messages listening thread", ioEx );
+ } catch( Exception ex ) {
+ log.error( "Fatal Error during UPNP DiscoveryAdvertisement messages listening thread, thread will exit", ex );
+ inService = false;
+ aliveRegistered.clear();
+ byeByeRegistered.clear();
+ USNPerIP.clear();
+ }
+ }
+
+ try {
+ skt.leaveGroup( InetAddress.getByName( Discovery.SSDP_IP ) );
+ skt.close();
+ } catch ( Exception ex ) {
+ // ignoring
+ }
+ }
+
+ private void listenBroadCast() throws IOException {
+
+ skt.receive( input );
+ InetAddress from = input.getAddress();
+ String received = new String( input.getData(), input.getOffset(), input.getLength() );
+ HttpResponse msg = null;
+ try {
+ msg = new HttpResponse( received );
+ } catch (IllegalArgumentException ex ) {
+ // crappy http sent
+ if ( log.isDebugEnabled() ) log.debug( "Skipping uncompliant HTTP message " + received );
+ return;
+ }
+ String header = msg.getHeader();
+ if ( header != null && header.startsWith( "NOTIFY" ) ) {
+ if ( log.isDebugEnabled() ) log.debug( received );
+ String ntsField = msg.getHTTPHeaderField( "nts" );
+ if( ntsField == null || ntsField.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'ntsField' field" );
+ return;
+ }
+ if ( ntsField.equals( NTS_SSDP_ALIVE ) ) {
+ String deviceDescrLoc = msg.getHTTPHeaderField( "location" );
+ if( deviceDescrLoc == null || deviceDescrLoc.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'location' field" );
+ return;
+ }
+ URL loc = new URL( deviceDescrLoc );
+ if ( MATCH_IP ) {
+ InetAddress locHost = InetAddress.getByName( loc.getHost() );
+ if ( !from.equals( locHost ) ) {
+ log.warn( "Discovery message sender IP " + from +
+ " does not match device description IP " + locHost +
+ " skipping message, set the net.sbbi.upnp.ddos.matchip system property" +
+ " to false to avoid this check" );
+ return;
+ }
+ }
+
+ String nt = msg.getHTTPHeaderField( "nt" );
+ if( nt == null || nt.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'nt' field" );
+ return;
+ }
+ String maxAge = msg.getHTTPFieldElement( "Cache-Control", "max-age" );
+ if( maxAge == null || maxAge.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'max-age' field" );
+ return;
+ }
+ String usn = msg.getHTTPHeaderField( "usn" );
+ if( usn == null || usn.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'usn' field" );
+ return;
+ }
+
+ USNPerIP.put( usn, from );
+ String udn = usn;
+ int index = udn.indexOf( "::" );
+ if ( index != -1 ) udn = udn.substring( 0, index );
+ synchronized( REGISTRATION_PROCESS ) {
+ Set handlers = (Set)aliveRegistered.get( NT_ALL_EVENTS );
+ if ( handlers != null ) {
+ for ( Iterator i = handlers.iterator(); i.hasNext(); ) {
+ DiscoveryEventHandler eventHandler = (DiscoveryEventHandler)i.next();
+ eventHandler.eventSSDPAlive( usn, udn, nt, maxAge, loc );
+ }
+ }
+ handlers = (Set)aliveRegistered.get( nt );
+ if ( handlers != null ) {
+ for ( Iterator i = handlers.iterator(); i.hasNext(); ) {
+ DiscoveryEventHandler eventHandler = (DiscoveryEventHandler)i.next();
+ eventHandler.eventSSDPAlive( usn, udn, nt, maxAge, loc );
+ }
+ }
+ }
+ } else if ( ntsField.equals( NTS_SSDP_BYE_BYE ) ) {
+ String usn = msg.getHTTPHeaderField( "usn" );
+ if( usn == null || usn.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'usn' field" );
+ return;
+ }
+ String nt = msg.getHTTPHeaderField( "nt" );
+ if( nt == null || nt.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'nt' field" );
+ return;
+ }
+
+ InetAddress originalAliveSenderIp = (InetAddress)USNPerIP.get( usn );
+ if ( originalAliveSenderIp != null ) {
+ // we check that the sender ip of message for the usn
+ // match the sender ip of the alive message for wich the usn
+ // has been received
+ if ( !originalAliveSenderIp.equals( from ) ) {
+ // someone else is trying to say that the usn is leaving
+ // since IP do not match we skip the message
+ return;
+ }
+ }
+
+ String udn = usn;
+ int index = udn.indexOf( "::" );
+ if ( index != -1 ) udn = udn.substring( 0, index );
+ synchronized( REGISTRATION_PROCESS ) {
+ Set handlers = (Set)byeByeRegistered.get( NT_ALL_EVENTS );
+ if ( handlers != null ) {
+ for ( Iterator i = handlers.iterator(); i.hasNext(); ) {
+ DiscoveryEventHandler eventHandler = (DiscoveryEventHandler)i.next();
+ eventHandler.eventSSDPByeBye( usn, udn, nt );
+ }
+ }
+ handlers = (Set)byeByeRegistered.get( nt );
+ if ( handlers != null ) {
+ for ( Iterator i = handlers.iterator(); i.hasNext(); ) {
+ DiscoveryEventHandler eventHandler = (DiscoveryEventHandler)i.next();
+ eventHandler.eventSSDPByeBye( usn, udn, nt );
+ }
+ }
+ }
+ } else {
+ log.warn( "Unvalid NTS field value (" + ntsField + ") received in NOTIFY message :" + received );
+ }
+ }
+ }
+}
diff --git a/source/net/sbbi/upnp/DiscoveryEventHandler.java b/source/net/sbbi/upnp/DiscoveryEventHandler.java
new file mode 100644
index 000000000..1685a6daa
--- /dev/null
+++ b/source/net/sbbi/upnp/DiscoveryEventHandler.java
@@ -0,0 +1,76 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+/**
+ * Interface for object that want to receive events from the
+ * DiscoveryAdvertisement thread
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public interface DiscoveryEventHandler {
+
+ /**
+ * Called when a device joins the network or advertise it is still alive
+ * @param usn the device USN (udn::nt)
+ * @param udn the device UDN
+ * @param nt the device NT
+ * @param maxAge the device maxAge
+ * @param location the device location
+ */
+ public void eventSSDPAlive( String usn, String udn, String nt, String maxAge, java.net.URL location );
+
+ /**
+ * Called when a device is leaving the network
+ * @param usn the device USN (udn::nt)
+ * @param udn the device UDN
+ * @param nt the device NT
+ */
+ public void eventSSDPByeBye( String usn, String udn, String nt );
+}
diff --git a/source/net/sbbi/upnp/DiscoveryListener.java b/source/net/sbbi/upnp/DiscoveryListener.java
new file mode 100644
index 000000000..5924070d4
--- /dev/null
+++ b/source/net/sbbi/upnp/DiscoveryListener.java
@@ -0,0 +1,290 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class can be used to listen for UPNP devices responses when a search message is sent by a control point
+ * ( using the net.sbbi.upnp.Discovery.sendSearchMessage() method )
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class DiscoveryListener implements Runnable {
+
+ private final static Log log = LogFactory.getLog( DiscoveryListener.class );
+
+ private static boolean MATCH_IP = true;
+
+ static {
+ String prop = System.getProperty( "net.sbbi.upnp.ddos.matchip" );
+ if ( prop != null && prop.equals( "false" ) ) MATCH_IP = false;
+ }
+
+ private static final int DEFAULT_TIMEOUT = 250;
+
+ private Map registeredHandlers = new HashMap();
+
+ private final Object REGISTRATION_PROCESS = new Object();
+
+ private final static DiscoveryListener singleton = new DiscoveryListener();
+
+ private boolean inService = false;
+ private boolean daemon = true;
+
+ private java.net.MulticastSocket skt;
+ private DatagramPacket input;
+
+ private DiscoveryListener() {
+ }
+
+ public final static DiscoveryListener getInstance() {
+ return singleton;
+ }
+
+ /**
+ * Sets the listener as a daemon thread
+ * @param daemon daemon thread
+ */
+ public void setDaemon( boolean daemon ) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Registers an SSDP response message handler
+ * @param resultsHandler the SSDP response message handler
+ * @param searchTarget the search target
+ * @throws IOException if some errors occurs during SSDP search response messages listener thread startup
+ */
+ public void registerResultsHandler( DiscoveryResultsHandler resultsHandler, String searchTarget ) throws IOException {
+ synchronized( REGISTRATION_PROCESS ) {
+ if ( !inService ) startDevicesListenerThread();
+ Set handlers = (Set)registeredHandlers.get( searchTarget );
+ if ( handlers == null ) {
+ handlers = new HashSet();
+ registeredHandlers.put( searchTarget, handlers );
+ }
+ handlers.add( resultsHandler );
+ }
+ }
+
+ /**
+ * Unregisters an SSDP response message handler
+ * @param resultsHandler the SSDP response message handler
+ * @param searchTarget the search target
+ */
+ public void unRegisterResultsHandler( DiscoveryResultsHandler resultsHandler, String searchTarget ) {
+ synchronized( REGISTRATION_PROCESS ) {
+ Set handlers = (Set)registeredHandlers.get( searchTarget );
+ if ( handlers != null ) {
+ handlers.remove( resultsHandler );
+ if ( handlers.size() == 0 ) {
+ registeredHandlers.remove( searchTarget );
+ }
+ }
+ if ( registeredHandlers.size() == 0 ) {
+ stopDevicesListenerThread();
+ }
+ }
+ }
+
+ private void startDevicesListenerThread() throws IOException {
+ synchronized( singleton ) {
+ if ( !inService ) {
+
+ this.startMultiCastSocket();
+ Thread deamon = new Thread( this, "DiscoveryListener daemon" );
+ deamon.setDaemon( daemon );
+ deamon.start();
+ while ( !inService ) {
+ // wait for the thread to be started let's wait a few ms
+ try {
+ Thread.sleep( 2 );
+ } catch( InterruptedException ex ) {
+ // don t care
+ }
+ }
+ }
+ }
+ }
+
+ private void stopDevicesListenerThread() {
+ synchronized( singleton ) {
+ inService = false;
+ }
+ }
+
+ private void startMultiCastSocket() throws IOException {
+ int bindPort = Discovery.DEFAULT_SSDP_SEARCH_PORT;
+ String port = System.getProperty( "net.sbbi.upnp.Discovery.bindPort" );
+ if ( port != null ) {
+ bindPort = Integer.parseInt( port );
+ }
+
+ skt = new java.net.MulticastSocket( null );
+ skt.bind( new InetSocketAddress( InetAddress.getByName( "0.0.0.0" ), bindPort ) );
+ skt.setTimeToLive( Discovery.DEFAULT_TTL );
+ skt.setSoTimeout( DEFAULT_TIMEOUT );
+ skt.joinGroup( InetAddress.getByName( Discovery.SSDP_IP ) );
+
+ byte[] buf = new byte[2048];
+ input = new DatagramPacket( buf, buf.length );
+
+ }
+
+ public void run() {
+ if ( !Thread.currentThread().getName().equals( "DiscoveryListener daemon" ) ) {
+ throw new RuntimeException( "No right to call this method" );
+ }
+ inService = true;
+ while ( inService ) {
+ try {
+ listenBroadCast();
+ } catch ( SocketTimeoutException ex ) {
+ // ignoring
+ } catch ( IOException ioEx ) {
+ log.error( "IO Exception during UPNP DiscoveryListener messages listening thread", ioEx );
+ } catch( Exception ex ) {
+ log.error( "Fatal Error during UPNP DiscoveryListener messages listening thread, thread will exit", ex );
+ inService = false;
+ }
+ }
+
+ try {
+ skt.leaveGroup( InetAddress.getByName( Discovery.SSDP_IP ) );
+ skt.close();
+ } catch ( Exception ex ) {
+ // ignoring
+ }
+ }
+
+ private void listenBroadCast() throws IOException {
+
+ skt.receive( input );
+ InetAddress from = input.getAddress();
+ String received = new String( input.getData(), input.getOffset(), input.getLength() );
+ HttpResponse msg = null;
+ try {
+ msg = new HttpResponse( received );
+ } catch (IllegalArgumentException ex ) {
+ // crappy http sent
+ if ( log.isDebugEnabled() ) log.debug( "Skipping uncompliant HTTP message " + received );
+ return;
+ }
+ String header = msg.getHeader();
+ if ( header != null && header.startsWith( "HTTP/1.1 200 OK" ) && msg.getHTTPHeaderField( "st" ) != null ) {
+ // probably a search repsonse !
+ String deviceDescrLoc = msg.getHTTPHeaderField( "location" );
+ if( deviceDescrLoc == null || deviceDescrLoc.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'location' field" );
+ return;
+ }
+ URL loc = new URL( deviceDescrLoc );
+ if ( MATCH_IP ) {
+ InetAddress locHost = InetAddress.getByName( loc.getHost() );
+ if ( !from.equals( locHost ) ) {
+ log.warn( "Discovery message sender IP " + from +
+ " does not match device description IP " + locHost +
+ " skipping device, set the net.sbbi.upnp.ddos.matchip system property" +
+ " to false to avoid this check" );
+ return;
+ }
+ }
+ if ( log.isDebugEnabled() ) log.debug( "Processing " + deviceDescrLoc + " device description location" );
+ String st = msg.getHTTPHeaderField( "st" );
+ if( st == null || st.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'st' field" );
+ return;
+ }
+ String usn = msg.getHTTPHeaderField( "usn" );
+ if( usn == null || usn.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'usn' field" );
+ return;
+ }
+ String maxAge = msg.getHTTPFieldElement( "Cache-Control", "max-age" );
+ if( maxAge == null || maxAge.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'max-age' field" );
+ return;
+ }
+ String server = msg.getHTTPHeaderField( "server" );
+ if( server == null || server.trim().length() == 0 ) {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping SSDP message, missing HTTP header 'server' field" );
+ return;
+ }
+
+ String udn = usn;
+ int index = udn.indexOf( "::" );
+ if ( index != -1 ) udn = udn.substring( 0, index );
+ synchronized( REGISTRATION_PROCESS ) {
+ Set handlers = (Set)registeredHandlers.get( st );
+ if ( handlers != null ) {
+ for ( Iterator i = handlers.iterator(); i.hasNext(); ) {
+ DiscoveryResultsHandler handler = (DiscoveryResultsHandler)i.next();
+ handler.discoveredDevice( usn, udn, st, maxAge, loc, server );
+ }
+ }
+ }
+ } else {
+ if ( log.isDebugEnabled() ) log.debug( "Skipping uncompliant HTTP message " + received );
+ }
+ }
+}
diff --git a/source/net/sbbi/upnp/DiscoveryResultsHandler.java b/source/net/sbbi/upnp/DiscoveryResultsHandler.java
new file mode 100644
index 000000000..1074bcac5
--- /dev/null
+++ b/source/net/sbbi/upnp/DiscoveryResultsHandler.java
@@ -0,0 +1,71 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+/**
+ * This interface can be use to register against the DiscoveryListener class
+ * to receive SSDP search responses.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public interface DiscoveryResultsHandler {
+
+ /**
+ * Method called by the DiscoveryListener class when a search response message has been received from the
+ * network
+ * @param usn the device USN
+ * @param udn the device UDN
+ * @param nt the device NT
+ * @param maxAge the message max age
+ * @param location the device location
+ * @param firmware the device firmware
+ */
+ public void discoveredDevice( String usn, String udn, String nt, String maxAge, java.net.URL location, String firmware );
+
+}
diff --git a/source/net/sbbi/upnp/HttpResponse.java b/source/net/sbbi/upnp/HttpResponse.java
new file mode 100644
index 000000000..183d79d03
--- /dev/null
+++ b/source/net/sbbi/upnp/HttpResponse.java
@@ -0,0 +1,145 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * A class to parse an HTTP response message.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class HttpResponse {
+
+ private String header;
+ private Map fields;
+ private String body;
+
+
+ /**
+ * Constructor of the response, will try to parse the raw response data
+ * @param rawHttpResponse the raw response data
+ * @throws IllegalArgumentException if some error occurs during parsing
+ */
+ protected HttpResponse( String rawHttpResponse ) throws IllegalArgumentException {
+ if ( rawHttpResponse == null || rawHttpResponse.trim().length() == 0 ) {
+ throw new IllegalArgumentException( "Empty HTTP response message" );
+ }
+ boolean bodyParsing = false;
+ StringBuffer bodyParsed = new StringBuffer();
+ fields = new HashMap();
+ String[] lines = rawHttpResponse.split( "\\r\\n" );
+ this.header = lines[0].trim();
+
+ for ( int i = 1; i < lines.length; i++ ) {
+
+ String line = lines[i];
+ if ( line.length() == 0 ) {
+ // line break before body
+ bodyParsing = true;
+ } else if ( bodyParsing ) {
+ // we parse the message body
+ bodyParsed.append( line ).append( "\r\n" );
+ } else {
+ // we parse the header
+ if ( line.length() > 0 ) {
+ int delim = line.indexOf( ':' );
+ if ( delim != -1 ) {
+ String key = line.substring( 0, delim ).toUpperCase();
+ String value = line.substring( delim + 1 ).trim();
+ fields.put( key, value );
+ } else {
+ throw new IllegalArgumentException( "Invalid HTTP message header :" + line );
+ }
+ }
+ }
+ }
+ if ( bodyParsing ) {
+ body = bodyParsed.toString();
+ }
+ }
+
+ public String getHeader() {
+ return header;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public String getHTTPFieldElement( String fieldName, String elementName ) throws IllegalArgumentException {
+ String fieldNameValue = getHTTPHeaderField( fieldName );
+ if ( fieldName!= null ) {
+
+ StringTokenizer tokenizer = new StringTokenizer( fieldNameValue.trim(), "," );
+ while (tokenizer.countTokens() > 0) {
+ String nextToken = tokenizer.nextToken().trim();
+ if ( nextToken.startsWith( elementName ) ) {
+ int index = nextToken.indexOf( "=" );
+ if ( index != -1 ) {
+ return nextToken.substring( index + 1 ).trim();
+ }
+ }
+ }
+ }
+ throw new IllegalArgumentException( "HTTP element field " + elementName + " is not present" );
+ }
+
+ public String getHTTPHeaderField( String fieldName ) throws IllegalArgumentException {
+ String field = (String)fields.get( fieldName.toUpperCase() );
+ if ( field == null ) {
+ throw new IllegalArgumentException( "HTTP field " + fieldName + " is not present");
+ }
+ return field;
+ }
+
+
+}
diff --git a/source/net/sbbi/upnp/JXPathParser.java b/source/net/sbbi/upnp/JXPathParser.java
new file mode 100644
index 000000000..d0aa8a37d
--- /dev/null
+++ b/source/net/sbbi/upnp/JXPathParser.java
@@ -0,0 +1,95 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.jxpath.xml.DOMParser;
+import org.apache.commons.jxpath.xml.XMLParser;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Parser to use with JXPath, this is used to fix some problems encountered
+ * with some UPNP devices returning buggy xml docs...
+ * This parser acts like a wrapper and make some chars search and replace
+ * such as 0x0 with 0x20 to produce a valid XML doc.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class JXPathParser implements XMLParser {
+
+ private final static Log log = LogFactory.getLog( JXPathParser.class );
+
+ private char buggyChar = (char)0;
+
+ public Object parseXML( InputStream in ){
+ StringBuffer xml = new StringBuffer();
+ try {
+ byte[] buffer = new byte[512];
+ int readen = 0;
+ while ( ( readen = in.read( buffer ) ) != -1 ) {
+ xml.append( new String( buffer, 0, readen ) );
+ }
+ } catch ( IOException ex ) {
+ log.error( "IOException occured during XML reception", ex );
+ return null;
+ }
+ String doc = xml.toString();
+ log.debug( "Readen raw xml doc:\n" + doc );
+ if ( doc.indexOf( buggyChar ) != -1 ) {
+ doc = doc.replace( buggyChar, ' ' );
+ }
+
+ ByteArrayInputStream in2 = new ByteArrayInputStream( doc.getBytes() );
+ DOMParser parser = new DOMParser();
+ return parser.parseXML( in2 ) ;
+ }
+}
diff --git a/source/net/sbbi/upnp/ServiceEventHandler.java b/source/net/sbbi/upnp/ServiceEventHandler.java
new file mode 100644
index 000000000..480626277
--- /dev/null
+++ b/source/net/sbbi/upnp/ServiceEventHandler.java
@@ -0,0 +1,70 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+/**
+ * Interface to implement to receive notifications about state
+ * variables changes on au UPNP service. The object implementing this interface
+ * can be used with the ServicesEventing class register method to receive the
+ * desired notifications.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public interface ServiceEventHandler {
+
+ /**
+ * Handle a var change, called each time a UPNP service fires a
+ * state variable eventing message.
+ * The code implemented in this method can block the thread.
+ * @param varName the state variable name
+ * @param newValue the new state variable value
+ */
+ public void handleStateVariableEvent( String varName, String newValue );
+
+}
diff --git a/source/net/sbbi/upnp/ServiceEventMessageParser.java b/source/net/sbbi/upnp/ServiceEventMessageParser.java
new file mode 100644
index 000000000..36b000cd6
--- /dev/null
+++ b/source/net/sbbi/upnp/ServiceEventMessageParser.java
@@ -0,0 +1,105 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xml.sax.Attributes;
+
+/**
+ * Simple SAX handler for UPNP service event message parsing, this message is in SOAP format
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ServiceEventMessageParser extends org.xml.sax.helpers.DefaultHandler {
+
+ private boolean readPropertyName = false;
+
+ private String currentPropName = null;
+
+ private Map changedStateVars = new HashMap();
+
+ protected ServiceEventMessageParser() {
+ }
+
+ public Map getChangedStateVars() {
+ return changedStateVars;
+ }
+
+ public void characters( char[] ch, int start, int length ) {
+ if ( currentPropName != null ) {
+ String origChars = (String)changedStateVars.get( currentPropName );
+ String newChars = new String( ch, start, length );
+ if ( origChars == null ) {
+ changedStateVars.put( currentPropName, newChars );
+ } else {
+ changedStateVars.put( currentPropName, origChars + newChars );
+ }
+ }
+ }
+
+ public void startElement( String uri, String localName, String qName, Attributes attributes ) {
+ if ( localName.equals( "property" ) ) {
+ readPropertyName = true;
+ } else if ( readPropertyName ) {
+ currentPropName = localName;
+ }
+ }
+
+ public void endElement( String uri, String localName, String qName ) {
+ if ( currentPropName != null && localName.equals( currentPropName ) ) {
+ readPropertyName = false;
+ currentPropName = null;
+ }
+ }
+
+}
+
+
diff --git a/source/net/sbbi/upnp/ServiceEventSubscription.java b/source/net/sbbi/upnp/ServiceEventSubscription.java
new file mode 100644
index 000000000..8fbfa3b7f
--- /dev/null
+++ b/source/net/sbbi/upnp/ServiceEventSubscription.java
@@ -0,0 +1,111 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.net.InetAddress;
+import java.net.URL;
+
+/**
+ * This class is used to provide information about a subscription done
+ * via the ServicesEventing class
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ServiceEventSubscription {
+
+ private String serviceType = null;
+ private String serviceId = null;
+ private URL serviceURL = null;
+ private String SID = null;
+ private InetAddress deviceIp = null;
+ private int durationTime = 0;
+
+ public ServiceEventSubscription( String serviceType, String serviceId, URL serviceURL,
+ String sid, InetAddress deviceIp, int durationTime ) {
+ this.serviceType = serviceType;
+ this.serviceId = serviceId;
+ this.serviceURL = serviceURL;
+ SID = sid;
+ this.deviceIp = deviceIp;
+ this.durationTime = durationTime;
+ }
+
+ public InetAddress getDeviceIp() {
+ return deviceIp;
+ }
+
+ /**
+ * Subcription duration in seconds
+ * @return sub duration time, 0 for an infinite time
+ */
+ public int getDurationTime() {
+ return durationTime;
+ }
+
+ public String getServiceId() {
+ return serviceId;
+ }
+
+ public String getServiceType() {
+ return serviceType;
+ }
+
+ public URL getServiceURL() {
+ return serviceURL;
+ }
+
+ /**
+ * The subscription ID returned by the UPNPDevice
+ * @return subscription id
+ */
+ public String getSID() {
+ return SID;
+ }
+
+}
diff --git a/source/net/sbbi/upnp/ServicesEventing.java b/source/net/sbbi/upnp/ServicesEventing.java
new file mode 100644
index 000000000..8778192e0
--- /dev/null
+++ b/source/net/sbbi/upnp/ServicesEventing.java
@@ -0,0 +1,445 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import net.sbbi.upnp.services.UPNPService;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.InputSource;
+
+/**
+ * This class can be used with the ServiceEventHandler interface
+ * to recieve notifications about state variables changes on
+ * a given UPNP service.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ServicesEventing implements Runnable {
+
+ private final static Log log = LogFactory.getLog( ServicesEventing.class );
+
+ private final static ServicesEventing singleton = new ServicesEventing();
+ private boolean inService = false;
+
+ private boolean daemon = true;
+ private int daemonPort = 9999;
+
+ private ServerSocket server = null;
+
+ private List registered = new ArrayList();
+
+ private ServicesEventing() {
+ }
+
+ public final static ServicesEventing getInstance() {
+ return singleton;
+ }
+
+ /**
+ * Set the listeniner thread as a daemon, default to true.
+ * Only works when no more objects are registered.
+ * @param daemon the new thread type.
+ */
+ public void setDaemon( boolean daemon ) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Sets the listener thread port, default to 9999.
+ * Only works when no more objects are registered.
+ * @param daemonPort the new listening port
+ */
+ public void setDaemonPort( int daemonPort ) {
+ this.daemonPort = daemonPort;
+ }
+
+ /**
+ * Register state variable events notification for a device service
+ * @param service the service to register with
+ * @param handler the registrant object
+ * @param subscriptionDuration subscription time in seconds, -1 for infinite time
+ * @return the subscription duration returned by the device, 0 for an infinite duration or -1 if no subscription done
+ * @throws IOException if some IOException error happens during coms with the device
+ */
+ public int register( UPNPService service, ServiceEventHandler handler, int subscriptionDuration ) throws IOException {
+ ServiceEventSubscription sub = registerEvent( service, handler, subscriptionDuration );
+ if ( sub != null ) {
+ return sub.getDurationTime();
+ }
+ return -1;
+ }
+
+ /**
+ * Register state variable events notification for a device service
+ * @param service the service to register with
+ * @param handler the registrant object
+ * @param subscriptionDuration subscription time in seconds, -1 for infinite time
+ * @return an ServiceEventSubscription object instance containing all the required info or null if no subscription done
+ * @throws IOException if some IOException error happens during coms with the device
+ */
+ public ServiceEventSubscription registerEvent( UPNPService service, ServiceEventHandler handler, int subscriptionDuration ) throws IOException {
+
+ URL eventingLoc = service.getEventSubURL();
+
+ if ( eventingLoc != null ) {
+
+ if ( !inService ) startServicesEventingThread();
+ String duration = Integer.toString( subscriptionDuration );
+ if ( subscriptionDuration == -1 ) {
+ duration = "infinite";
+ }
+
+ Subscription sub = lookupSubscriber( service, handler );
+ if ( sub != null ) {
+ // allready registered let's try to unregister it
+ unRegister( service, handler );
+ }
+
+ StringBuffer packet = new StringBuffer( 64 );
+ packet.append( "SUBSCRIBE " ).append( eventingLoc.getFile() ).append( " HTTP/1.1\r\n" );
+ packet.append( "HOST: " ).append( eventingLoc.getHost() ).append( ":" ).append( eventingLoc.getPort() ).append( "\r\n" );
+ packet.append( "CALLBACK: \r\n" );
+ packet.append( "NT: upnp:event\r\n" );
+ packet.append( "Connection: close\r\n" );
+ packet.append( "TIMEOUT: Second-" ).append( duration ).append( "\r\n\r\n" );
+
+ Socket skt = new Socket( eventingLoc.getHost(), eventingLoc.getPort() );
+ skt.setSoTimeout( 30000 ); // 30 secs timeout according to the specs
+ if ( log.isDebugEnabled() ) log.debug( packet );
+ OutputStream out = skt.getOutputStream();
+ out.write( packet.toString().getBytes() );
+ out.flush();
+
+ InputStream in = skt.getInputStream();
+ StringBuffer data = new StringBuffer();
+ int readen = 0;
+ byte[] buffer = new byte[256];
+ while ( ( readen = in.read( buffer ) ) != -1 ) {
+ data.append( new String( buffer, 0, readen ) );
+ }
+ in.close();
+ out.close();
+ skt.close();
+ if ( log.isDebugEnabled() ) log.debug( data.toString() );
+ if ( data.toString().trim().length() > 0 ) {
+ HttpResponse resp = new HttpResponse( data.toString() );
+
+ if ( resp.getHeader().startsWith( "HTTP/1.1 200 OK" ) ) {
+ String sid = resp.getHTTPHeaderField( "SID" );
+ String actualTimeout = resp.getHTTPHeaderField( "TIMEOUT" );
+ int durationTime = 0;
+ // actualTimeout = Second-xxx or Second-infinite
+ if ( !actualTimeout.equalsIgnoreCase( "Second-infinite" ) ) {
+ durationTime = Integer.parseInt( actualTimeout.substring( 7 ) );
+ }
+ sub = new Subscription();
+ sub.handler = handler;
+ sub.sub = new ServiceEventSubscription( service.getServiceType(), service.getServiceId(),
+ service.getEventSubURL(), sid, skt.getInetAddress(),
+ durationTime );
+ synchronized( registered ) {
+ registered.add( sub );
+ }
+ return sub.sub;
+ }
+ }
+ }
+ return null;
+
+ }
+
+ private Subscription lookupSubscriber( UPNPService service, ServiceEventHandler handler ) {
+ synchronized( registered ) {
+ for ( Iterator i = registered.iterator(); i.hasNext(); ) {
+ Subscription sub = (Subscription)i.next();
+
+ if ( sub.handler == handler &&
+ sub.sub.getServiceId().hashCode() == service.getServiceId().hashCode() &&
+ sub.sub.getServiceType().hashCode() == service.getServiceType().hashCode() &&
+ sub.sub.getServiceURL().equals( service.getEventSubURL() ) ) {
+ return sub;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Subscription lookupSubscriber( String sid, InetAddress deviceIp ) {
+ synchronized( registered ) {
+ for ( Iterator i = registered.iterator(); i.hasNext(); ) {
+ Subscription sub = (Subscription)i.next();
+
+ if ( sub.sub.getSID().equals( sid ) && sub.sub.getDeviceIp().equals( deviceIp ) ) {
+ return sub;
+ }
+ }
+ }
+ return null;
+ }
+
+ private Subscription lookupSubscriber( String sid ) {
+ synchronized( registered ) {
+ for ( Iterator i = registered.iterator(); i.hasNext(); ) {
+ Subscription sub = (Subscription)i.next();
+
+ if ( sub.sub.getSID().equals( sid ) ) {
+ return sub;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Unregisters events notifications from a service
+ * @param service the service that need to be unregistered
+ * @param handler the handler that registered for this service
+ * @return true if unregistered false otherwise ( the given handler never registred for the given service )
+ * @throws IOException if some IOException error happens during coms with the device
+ */
+ public boolean unRegister( UPNPService service, ServiceEventHandler handler ) throws IOException {
+
+ URL eventingLoc = service.getEventSubURL();
+
+ if ( eventingLoc != null ) {
+
+ Subscription sub = lookupSubscriber( service, handler );
+ if ( sub != null ) {
+ synchronized( registered ) {
+ registered.remove( sub );
+ }
+ if ( registered.size() == 0 ) {
+ stopServicesEventingThread();
+ }
+
+ StringBuffer packet = new StringBuffer( 64 );
+ packet.append( "UNSUBSCRIBE " ).append( eventingLoc.getFile() ).append( " HTTP/1.1\r\n" );
+ packet.append( "HOST: " ).append( eventingLoc.getHost() ).append( ":" ).append( eventingLoc.getPort() ).append( "\r\n" );
+ packet.append( "SID: " ).append( sub.sub.getSID() ).append( "\r\n\r\n" );
+ Socket skt = new Socket( eventingLoc.getHost(), eventingLoc.getPort() );
+ skt.setSoTimeout( 30000 ); // 30 secs timeout according to the specs
+ if ( log.isDebugEnabled() ) log.debug( packet );
+ OutputStream out = skt.getOutputStream();
+ out.write( packet.toString().getBytes() );
+ out.flush();
+
+ InputStream in = skt.getInputStream();
+ StringBuffer data = new StringBuffer();
+ int readen = 0;
+ byte[] buffer = new byte[256];
+ while ( ( readen = in.read( buffer ) ) != -1 ) {
+ data.append( new String( buffer, 0, readen ) );
+ }
+ in.close();
+ out.close();
+ skt.close();
+ if ( log.isDebugEnabled() ) log.debug( data.toString() );
+ if ( data.toString().trim().length() > 0 ) {
+ HttpResponse resp = new HttpResponse( data.toString() );
+ if ( resp.getHeader().startsWith( "HTTP/1.1 200 OK" ) ) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+
+ private void startServicesEventingThread() {
+ synchronized( singleton ) {
+ if ( !inService ) {
+ Thread deamon = new Thread( singleton, "ServicesEventing daemon" );
+ deamon.setDaemon( daemon );
+ inService = true;
+ deamon.start();
+ }
+ }
+ }
+
+ private void stopServicesEventingThread() {
+ synchronized( singleton ) {
+ inService = false;
+ try {
+ server.close();
+ } catch ( IOException ex ) {
+ // should not happen
+ }
+ }
+ }
+
+ public void run() {
+ // only the deamon thread is allowed to call such method
+ if ( !Thread.currentThread().getName().equals( "ServicesEventing daemon" ) ) return;
+ try {
+ server = new ServerSocket( daemonPort );
+ } catch ( IOException ex ) {
+ log.error( "Error during daemon server socket on port " + daemonPort + " creation", ex );
+ return;
+ }
+ while ( inService ) {
+ try {
+ Socket skt = server.accept();
+ new Thread( new RequestProcessor( skt ) ).start();
+ } catch ( IOException ioEx ) {
+ if ( inService ) {
+ log.error( "IO Exception during UPNP messages listening thread", ioEx );
+ }
+ }
+ }
+ }
+
+ private class Subscription {
+ private ServiceEventSubscription sub = null;
+ private ServiceEventHandler handler = null;
+ }
+
+ private class RequestProcessor implements Runnable {
+
+ private Socket client;
+
+ private RequestProcessor( Socket client ) {
+ this.client = client;
+ }
+
+ public void run() {
+ try {
+ client.setSoTimeout( 30000 );
+ InputStream in = client.getInputStream();
+ OutputStream out = client.getOutputStream();
+
+ int readen = 0;
+ StringBuffer data = new StringBuffer();
+ byte[] buffer = new byte[256];
+ boolean EOF = false;
+ while ( !EOF && ( readen = in.read( buffer ) ) != -1 ) {
+ data.append( new String( buffer, 0, readen ) );
+ // avoid a strange behaviour with some impls.. the -1 is never reached and a sockettimeout occurs
+ // and a 0 byte is sent as the last byte
+ if ( data.charAt( data.length()-1 ) == (char)0 ) {
+ EOF = true;
+ }
+ }
+
+ String packet = data.toString();
+ if ( packet.trim().length() > 0 ) {
+
+ if ( packet.indexOf( (char)0 ) != -1 ) packet = packet.replace( (char)0, ' ' );
+ HttpResponse resp = new HttpResponse( packet );
+ if ( resp.getHeader().startsWith( "NOTIFY" ) ) {
+
+ String sid = resp.getHTTPHeaderField( "SID" );
+ InetAddress deviceIp = client.getInetAddress();
+ String postURL = resp.getHTTPHeaderField( "SID" );
+ Subscription subscription = null;
+ if ( sid != null && postURL != null ) {
+ subscription = lookupSubscriber( sid, deviceIp );
+ if ( subscription == null ) {
+ // not found maybe that the IP is not the same
+ subscription = lookupSubscriber( sid );
+ }
+ }
+ if ( subscription != null ) {
+ // respond ok
+ out.write( "HTTP/1.1 200 OK\r\n".getBytes() );
+ } else {
+ // unknown sid respond ko
+ out.write( "HTTP/1.1 412 Precondition Failed\r\n".getBytes() );
+ }
+
+ out.flush();
+ in.close();
+ out.close();
+ client.close();
+
+ if ( subscription != null ) {
+ // let's parse it
+ SAXParserFactory saxParFact = SAXParserFactory.newInstance();
+ saxParFact.setValidating( false );
+ saxParFact.setNamespaceAware( true );
+ SAXParser parser = saxParFact.newSAXParser();
+ ServiceEventMessageParser msgParser = new ServiceEventMessageParser();
+ StringReader stringReader = new StringReader( resp.getBody() );
+ InputSource src = new InputSource( stringReader );
+ parser.parse( src, msgParser );
+
+ Map changedStateVars = msgParser.getChangedStateVars();
+ for ( Iterator i = changedStateVars.keySet().iterator(); i.hasNext(); ) {
+ String stateVarName = (String)i.next();
+ String stateVarNewVal = (String)changedStateVars.get( stateVarName );
+ subscription.handler.handleStateVariableEvent( stateVarName, stateVarNewVal );
+ }
+ }
+ }
+ }
+ } catch ( IOException ioEx ) {
+ log.error( "IO Exception during client processing thread", ioEx );
+ } catch( Exception ex ) {
+ log.error( "Unexpected error during client processing thread", ex );
+ }
+ }
+ }
+}
diff --git a/source/net/sbbi/upnp/devices/DeviceIcon.java b/source/net/sbbi/upnp/devices/DeviceIcon.java
new file mode 100644
index 000000000..7037a496b
--- /dev/null
+++ b/source/net/sbbi/upnp/devices/DeviceIcon.java
@@ -0,0 +1,86 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.devices;
+
+import java.net.*;
+
+/**
+ * Java Bean for a device icon properties
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class DeviceIcon {
+
+ protected String mimeType;
+ protected int width;
+ protected int height;
+ protected int depth;
+ protected URL url;
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ public URL getUrl() {
+ return url;
+ }
+
+}
diff --git a/source/net/sbbi/upnp/devices/UPNPDevice.java b/source/net/sbbi/upnp/devices/UPNPDevice.java
new file mode 100644
index 000000000..c9f9e7450
--- /dev/null
+++ b/source/net/sbbi/upnp/devices/UPNPDevice.java
@@ -0,0 +1,298 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.devices;
+
+import net.sbbi.upnp.services.*;
+
+import java.util.*;
+import java.net.*;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class represents an UPNP device, this device contains a set of services
+ * that will be needed to access the device functionalities.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class UPNPDevice {
+
+ private final static Log log = LogFactory.getLog( UPNPDevice.class );
+
+ protected String deviceType;
+ protected String friendlyName;
+ protected String manufacturer;
+ protected URL manufacturerURL;
+ protected URL presentationURL;
+ protected String modelDescription;
+ protected String modelName;
+ protected String modelNumber;
+ protected String modelURL;
+ protected String serialNumber;
+ protected String UDN;
+ protected String USN;
+ protected long UPC;
+
+ protected List deviceIcons;
+ protected List services;
+ protected List childDevices;
+
+ protected UPNPDevice parent;
+
+ public URL getManufacturerURL() {
+ return manufacturerURL;
+ }
+
+ /**
+ * Presentation URL
+ * @return URL the presenation URL, or null if the device does not provide
+ * such information
+ */
+ public URL getPresentationURL() {
+ return presentationURL;
+ }
+
+ public String getModelDescription() {
+ return modelDescription;
+ }
+
+ public String getModelName() {
+ return modelName;
+ }
+
+ public String getModelNumber() {
+ return modelNumber;
+ }
+
+ public String getModelURL() {
+ return modelURL;
+ }
+
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ public String getUDN() {
+ return UDN;
+ }
+
+ public String getUSN(){
+ return USN;
+ }
+
+ public long getUPC() {
+ return UPC;
+ }
+
+ public String getDeviceType() {
+ return deviceType;
+ }
+
+ public String getFriendlyName() {
+ return friendlyName;
+ }
+
+ public String getManufacturer() {
+ return manufacturer;
+ }
+
+ public boolean isRootDevice() {
+ return this instanceof UPNPRootDevice;
+ }
+
+ /**
+ * Access to the device icons definitions
+ * @return a list containing DeviceIcon objects or null if no icons defined
+ */
+ public List getDeviceIcons() {
+ return deviceIcons;
+ }
+
+ /**
+ * Generates a list of all the child ( not only top level, full childrens hierarchy included )
+ * UPNPDevice objects for this device.
+ * @return the generated list or null if no child devices bound
+ */
+ public List getChildDevices() {
+ if ( childDevices == null ) return null;
+ List rtrVal = new ArrayList();
+ for ( Iterator itr = childDevices.iterator(); itr.hasNext(); ) {
+ UPNPDevice device = (UPNPDevice)itr.next();
+ rtrVal.add( device );
+ List found = device.getChildDevices();
+ if ( found != null ) {
+ rtrVal.addAll( found );
+ }
+ }
+ return rtrVal;
+
+ }
+
+ /**
+ * Generates a list of all the child ( only top level )
+ * UPNPDevice objects for this device.
+ * @return the generated list or null if no child devices bound
+ */
+ public List getTopLevelChildDevices() {
+ if ( childDevices == null ) return null;
+ List rtrVal = new ArrayList();
+ for ( Iterator itr = childDevices.iterator(); itr.hasNext(); ) {
+ UPNPDevice device = (UPNPDevice)itr.next();
+ rtrVal.add( device );
+ }
+ return rtrVal;
+ }
+
+ /**
+ * Return the parent UPNPDevice, null if the device is an UPNPRootDevice
+ * @return the parent device instance
+ */
+ public UPNPDevice getDirectParent() {
+ return parent;
+ }
+
+ /**
+ * Looks for a child UPNP device definition file,
+ * the whole devices tree will be searched, starting from the current
+ * device node.
+ * @param deviceURI the device URI to search
+ * @return An UPNPDevice if anything matches or null
+ */
+ public UPNPDevice getChildDevice( String deviceURI ) {
+ if ( log.isDebugEnabled() ) log.debug( "searching for device URI:" + deviceURI );
+ if ( getDeviceType().equals( deviceURI ) ) return this;
+ if ( childDevices == null ) return null;
+ for ( Iterator itr = childDevices.iterator(); itr.hasNext(); ) {
+ UPNPDevice device = (UPNPDevice)itr.next();
+ UPNPDevice found = device.getChildDevice( deviceURI );
+ if ( found != null ) {
+ return found;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Looks for all UPNP device service definitions objects
+ * @return A list of all device services
+ */
+ public List getServices() {
+ if ( services == null ) return null;
+ List rtrVal = new ArrayList();
+ rtrVal.addAll( services );
+ return rtrVal;
+ }
+
+ /**
+ * Looks for a UPNP device service definition object for the given service URI (Type)
+ * @param serviceURI the URI of the service
+ * @return A matching UPNPService object or null
+ */
+ public UPNPService getService( String serviceURI ) {
+ if ( services == null ) return null;
+ if ( log.isDebugEnabled() ) log.debug( "searching for service URI:" + serviceURI );
+ for ( Iterator itr = services.iterator(); itr.hasNext(); ) {
+ UPNPService service = (UPNPService)itr.next();
+ if ( service.getServiceType().equals( serviceURI ) ) {
+ return service;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Looks for a UPNP device service definition object for the given service ID
+ * @param serviceURI the ID of the service
+ * @return A matching UPNPService object or null
+ */
+ public UPNPService getServiceByID( String serviceID ) {
+ if ( services == null ) return null;
+ if ( log.isDebugEnabled() ) log.debug( "searching for service ID:" + serviceID );
+ for ( Iterator itr = services.iterator(); itr.hasNext(); ) {
+ UPNPService service = (UPNPService)itr.next();
+ if ( service.getServiceId().equals( serviceID ) ) {
+ return service;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Looks for the all the UPNP device service definition object for the current
+ * UPNP device object. This method can be used to retreive multiple same kind
+ * ( same service type ) of services with different services id on a device
+ * @param serviceURI the URI of the service
+ * @return A matching List of UPNPService objects or null
+ */
+ public List getServices( String serviceURI ) {
+ if ( services == null ) return null;
+ List rtrVal = new ArrayList();
+ if ( log.isDebugEnabled() ) log.debug( "searching for services URI:" + serviceURI );
+ for ( Iterator itr = services.iterator(); itr.hasNext(); ) {
+ UPNPService service = (UPNPService)itr.next();
+ if ( service.getServiceType().equals( serviceURI ) ) {
+ rtrVal.add( service );
+ }
+ }
+ if ( rtrVal.size() == 0 ) {
+ return null;
+ }
+ return rtrVal;
+ }
+
+ /**
+ * The toString return the device type
+ * @return the device type
+ */
+ public String toString() {
+ return getDeviceType();
+ }
+
+}
diff --git a/source/net/sbbi/upnp/devices/UPNPRootDevice.java b/source/net/sbbi/upnp/devices/UPNPRootDevice.java
new file mode 100644
index 000000000..f11a31e0e
--- /dev/null
+++ b/source/net/sbbi/upnp/devices/UPNPRootDevice.java
@@ -0,0 +1,450 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.devices;
+
+import java.net.*;
+import java.util.*;
+import java.io.*;
+
+import org.apache.commons.jxpath.*;
+import org.apache.commons.jxpath.xml.*;
+import org.apache.commons.logging.*;
+
+import net.sbbi.upnp.JXPathParser;
+import net.sbbi.upnp.services.*;
+
+/**
+ * Root UPNP device that is contained in a device definition file.
+ * Slightly differs from a simple UPNPDevice object.
+ * This object will contains all the child devices, this is the top
+ * objet in the UPNP device devices hierarchy.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class UPNPRootDevice extends UPNPDevice {
+
+ private final static Log log = LogFactory.getLog( UPNPRootDevice.class );
+
+ private int specVersionMajor;
+ private int specVersionMinor;
+ private URL URLBase;
+ private long validityTime;
+ private long creationTime;
+ private URL deviceDefLoc;
+ private String deviceDefLocData;
+ private String vendorFirmware;
+ private String discoveryUSN;
+ private String discoveryUDN;
+
+ private DocumentContainer UPNPDevice;
+
+ /**
+ * Constructor for the root device, constructs itself from
+ * An xml device definition file provided by the UPNP device via http normally.
+ * @param deviceDefLoc the location of the XML device definition file
+ * using "the urn:schemas-upnp-org:device-1-0" namespace
+ * @param maxAge the maximum age of this UPNP device in secs before considered to be outdated
+ * @param vendorFirmware the vendor firmware
+ * @param discoveryUSN the discovery USN used to find and create this device
+ * @param discoveryUDN the discovery UDN used to find and create this device
+ * @throws MalformedURLException if the location URL is invalid and cannot be used to populate this root object and its child devices
+ * IllegalStateException if the device has an unsupported version, currently only version 1.0 is supported
+ */
+ public UPNPRootDevice( URL deviceDefLoc, String maxAge, String vendorFirmware, String discoveryUSN, String discoveryUDN ) throws MalformedURLException, IllegalStateException {
+ this( deviceDefLoc, maxAge );
+ this.vendorFirmware = vendorFirmware;
+ this.discoveryUSN = discoveryUSN;
+ this.discoveryUDN = discoveryUDN;
+ }
+
+ /**
+ * Constructor for the root device, constructs itself from
+ * An xml device definition file provided by the UPNP device via http normally.
+ * @param deviceDefLoc the location of the XML device definition file
+ * using "the urn:schemas-upnp-org:device-1-0" namespace
+ * @param maxAge the maximum age of this UPNP device in secs before considered to be outdated
+ * @param vendorFirmware the vendor firmware
+ * @throws MalformedURLException if the location URL is invalid and cannot be used to populate this root object and its child devices
+ * IllegalStateException if the device has an unsupported version, currently only version 1.0 is supported
+ */
+ public UPNPRootDevice( URL deviceDefLoc, String maxAge, String vendorFirmware ) throws MalformedURLException, IllegalStateException {
+ this( deviceDefLoc, maxAge );
+ this.vendorFirmware = vendorFirmware;
+ }
+
+ /**
+ * Constructor for the root device, constructs itself from
+ * An xml device definition file provided by the UPNP device via http normally.
+ * @param deviceDefLoc the location of the XML device definition file
+ * using "the urn:schemas-upnp-org:device-1-0" namespace
+ * @param maxAge the maximum age in secs of this UPNP device before considered to be outdated
+ * @throws MalformedURLException if the location URL is invalid and cannot be used to populate this root object and its child devices
+ * IllegalStateException if the device has an unsupported version, currently only version 1.0 is supported
+ */
+ public UPNPRootDevice( URL deviceDefLoc, String maxAge ) throws MalformedURLException, IllegalStateException {
+ this.deviceDefLoc = deviceDefLoc;
+ DocumentContainer.registerXMLParser( DocumentContainer.MODEL_DOM, new JXPathParser() );
+ UPNPDevice = new DocumentContainer( deviceDefLoc, DocumentContainer.MODEL_DOM );
+ validityTime = Integer.parseInt( maxAge ) * 1000;
+ creationTime = System.currentTimeMillis();
+
+ JXPathContext context = JXPathContext.newContext( this );
+ Pointer rootPtr = context.getPointer( "UPNPDevice/root" );
+ JXPathContext rootCtx = context.getRelativeContext( rootPtr );
+
+ specVersionMajor = Integer.parseInt( (String)rootCtx.getValue( "specVersion/major" ) );
+ specVersionMinor = Integer.parseInt( (String)rootCtx.getValue( "specVersion/minor" ) );
+
+ if ( !( specVersionMajor == 1 && specVersionMinor == 0 ) ) {
+ throw new IllegalStateException( "Unsupported device version (" + specVersionMajor + "." + specVersionMinor + ")" );
+ }
+ boolean buildURLBase = true;
+ String base = null;
+ try {
+ base = (String)rootCtx.getValue( "URLBase" );
+ if ( base != null && base.trim().length() > 0 ) {
+ URLBase = new URL( base );
+ if ( log.isDebugEnabled() ) log.debug( "device URLBase " + URLBase );
+ buildURLBase = false;
+ }
+ } catch ( JXPathException ex ) {
+ // URLBase is not mandatory we assume we use the URL of the device
+ } catch ( MalformedURLException malformedEx ) {
+ // crappy urlbase provided
+ log.warn( "Error occured during device baseURL " + base + " parsing, building it from device default location", malformedEx );
+ }
+ if ( buildURLBase ) {
+ String URL = deviceDefLoc.getProtocol() + "://" + deviceDefLoc.getHost() + ":" + deviceDefLoc.getPort();
+ String path = deviceDefLoc.getPath();
+ if ( path != null ) {
+ int lastSlash = path.lastIndexOf( '/' );
+ if ( lastSlash != -1 ) {
+ URL += path.substring( 0, lastSlash );
+ }
+ }
+ URLBase = new URL( URL );
+ }
+ Pointer devicePtr = rootCtx.getPointer( "device" );
+ JXPathContext deviceCtx = rootCtx.getRelativeContext( devicePtr );
+
+ fillUPNPDevice( this, null, deviceCtx, URLBase );
+ }
+
+ /**
+ * The validity time for this device in milliseconds,
+ * @return the number of milliseconds remaining before the device object that has been build is considered to
+ * be outdated, after this delay the UPNP device should resend an advertisement message or a negative value
+ * if the device is outdated
+ */
+ public long getValidityTime() {
+ long elapsed = System.currentTimeMillis() - creationTime;
+ return validityTime - elapsed;
+ }
+
+ /**
+ * Resets the device validity time
+ * @param newMaxAge the maximum age in secs of this UPNP device before considered to be outdated
+ */
+ public void resetValidityTime( String newMaxAge ) {
+ validityTime = Integer.parseInt( newMaxAge ) * 1000;
+ creationTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Retreives the device description file location
+ * @return an URL
+ */
+ public URL getDeviceDefLoc() {
+ return deviceDefLoc;
+ }
+
+ public int getSpecVersionMajor() {
+ return specVersionMajor;
+ }
+
+ public int getSpecVersionMinor() {
+ return specVersionMinor;
+ }
+
+ public String getVendorFirmware() {
+ return vendorFirmware;
+ }
+
+ public String getDiscoveryUSN() {
+ return discoveryUSN;
+ }
+
+ public String getDiscoveryUDN() {
+ return discoveryUDN;
+ }
+
+ /**
+ * URL base acces
+ * @return URL the URL base, or null if the device does not provide
+ * such information
+ */
+ public URL getURLBase() {
+ return URLBase;
+ }
+
+ /**
+ * Parsing an UPNPdevice description element () in the description XML file
+ * @param device the device object that will be populated
+ * @param parent the device parent object
+ * @param deviceCtx an XPath context for object population
+ * @param baseURL the base URL of the UPNP device
+ * @throws MalformedURLException if some URL provided in the description file is invalid
+ */
+ private void fillUPNPDevice( UPNPDevice device, UPNPDevice parent, JXPathContext deviceCtx, URL baseURL ) throws MalformedURLException {
+
+ device.deviceType = getMandatoryData( deviceCtx, "deviceType" );
+ if ( log.isDebugEnabled() ) log.debug( "parsing device " + device.deviceType );
+ device.friendlyName = getMandatoryData( deviceCtx, "friendlyName" );
+ device.manufacturer = getNonMandatoryData( deviceCtx, "manufacturer" );
+ String base = getNonMandatoryData( deviceCtx, "manufacturerURL" );
+ try {
+ if ( base != null ) device.manufacturerURL = new URL( base );
+ } catch ( java.net.MalformedURLException ex ) {
+ // crappy data provided, keep the field null
+ }
+ try {
+ device.presentationURL = getURL( getNonMandatoryData( deviceCtx, "presentationURL" ), URLBase );
+ } catch ( java.net.MalformedURLException ex ) {
+ // crappy data provided, keep the field null
+ }
+ device.modelDescription = getNonMandatoryData( deviceCtx, "modelDescription" );
+ device.modelName = getMandatoryData( deviceCtx, "modelName" );
+ device.modelNumber = getNonMandatoryData( deviceCtx, "modelNumber" );
+ device.modelURL = getNonMandatoryData( deviceCtx, "modelURL" );
+ device.serialNumber = getNonMandatoryData( deviceCtx, "serialNumber" );
+ device.UDN = getMandatoryData( deviceCtx, "UDN" );
+ device.USN = UDN.concat( "::" ).concat( deviceType );
+ String tmp = getNonMandatoryData( deviceCtx, "UPC" );
+ if ( tmp != null ) {
+ try {
+ device.UPC = Long.parseLong( tmp );
+ } catch ( Exception ex ) {
+ // non all numeric field provided, non upnp compliant device
+ }
+ }
+ device.parent = parent;
+
+ fillUPNPServicesList( device, deviceCtx );
+ fillUPNPDeviceIconsList( device, deviceCtx, URLBase );
+
+ Pointer deviceListPtr;
+ try {
+ deviceListPtr = deviceCtx.getPointer( "deviceList" );
+ } catch ( JXPathException ex ) {
+ // no pointers for this device list, this can happen
+ // if the device has no child devices, simply returning
+ return;
+ }
+ JXPathContext deviceListCtx = deviceCtx.getRelativeContext( deviceListPtr );
+ Double arraySize = (Double)deviceListCtx.getValue( "count( device )" );
+ device.childDevices = new ArrayList();
+ if ( log.isDebugEnabled() ) log.debug( "child devices count is " + arraySize );
+ for ( int i = 1; i <= arraySize.intValue(); i++ ) {
+ Pointer devicePtr = deviceListCtx.getPointer( "device[" + i + "]" );
+ JXPathContext childDeviceCtx = deviceListCtx.getRelativeContext( devicePtr );
+ UPNPDevice childDevice = new UPNPDevice();
+ fillUPNPDevice( childDevice, device, childDeviceCtx, baseURL );
+ if ( log.isDebugEnabled() ) log.debug( "adding child device " + childDevice.getDeviceType() );
+ device.childDevices.add( childDevice );
+ }
+ }
+
+ private String getMandatoryData( JXPathContext ctx, String ctxFieldName ) {
+ String value = (String)ctx.getValue( ctxFieldName );
+ if ( value != null && value.length() == 0 ) {
+ throw new JXPathException( "Mandatory field " + ctxFieldName + " not provided, uncompliant UPNP device !!" );
+ }
+ return value;
+ }
+
+ private String getNonMandatoryData( JXPathContext ctx, String ctxFieldName ) {
+ String value = null;
+ try {
+ value = (String)ctx.getValue( ctxFieldName );
+ if ( value != null && value.length() == 0 ) {
+ value = null;
+ }
+ } catch ( JXPathException ex ) {
+ value = null;
+ }
+ return value;
+ }
+
+ /**
+ * Parsing an UPNPdevice services list element () in the description XML file
+ * @param device the device object that will store the services list (UPNPService) objects
+ * @param deviceCtx an XPath context for object population
+ * @throws MalformedURLException if some URL provided in the description
+ * file for a service entry is invalid
+ */
+ private void fillUPNPServicesList( UPNPDevice device, JXPathContext deviceCtx ) throws MalformedURLException {
+ Pointer serviceListPtr = deviceCtx.getPointer( "serviceList" );
+ JXPathContext serviceListCtx = deviceCtx.getRelativeContext( serviceListPtr );
+ Double arraySize = (Double)serviceListCtx.getValue( "count( service )" );
+ if ( log.isDebugEnabled() ) log.debug( "device services count is " + arraySize );
+ device.services = new ArrayList();
+ for ( int i = 1; i <= arraySize.intValue(); i++ ) {
+
+ Pointer servicePtr = serviceListCtx.getPointer( "service["+i+"]" );
+ JXPathContext serviceCtx = serviceListCtx.getRelativeContext( servicePtr );
+ // TODO possibility of bugs if deviceDefLoc contains a file name
+ URL base = URLBase != null ? URLBase : deviceDefLoc;
+ UPNPService service = new UPNPService( serviceCtx, base, this );
+ device.services.add( service );
+ }
+ }
+
+ /**
+ * Parsing an UPNPdevice icons list element () in the description XML file
+ * This list can be null
+ * @param device the device object that will store the icons list (DeviceIcon) objects
+ * @param deviceCtx an XPath context for object population
+ * @throws MalformedURLException if some URL provided in the description
+ * file for an icon URL
+ */
+ private void fillUPNPDeviceIconsList( UPNPDevice device, JXPathContext deviceCtx, URL baseURL ) throws MalformedURLException {
+ Pointer iconListPtr;
+ try {
+ iconListPtr = deviceCtx.getPointer( "iconList" );
+ } catch ( JXPathException ex ) {
+ // no pointers for icons list, this can happen
+ // simply returning
+ return;
+ }
+ JXPathContext iconListCtx = deviceCtx.getRelativeContext( iconListPtr );
+ Double arraySize = (Double)iconListCtx.getValue( "count( icon )" );
+ if ( log.isDebugEnabled() ) log.debug( "device icons count is " + arraySize );
+ device.deviceIcons = new ArrayList();
+ for ( int i = 1; i <= arraySize.intValue(); i++ ) {
+
+ DeviceIcon ico = new DeviceIcon();
+ ico.mimeType = (String)iconListCtx.getValue( "icon["+i+"]/mimetype" );
+ ico.width = Integer.parseInt( (String)iconListCtx.getValue( "icon["+i+"]/width" ) );
+ ico.height = Integer.parseInt( (String)iconListCtx.getValue( "icon["+i+"]/height" ) );
+ ico.depth = Integer.parseInt( (String)iconListCtx.getValue( "icon["+i+"]/depth" ) );
+ ico.url = getURL( (String)iconListCtx.getValue( "icon["+i+"]/url" ), baseURL );
+ if ( log.isDebugEnabled() ) log.debug( "icon URL is " + ico.url );
+ device.deviceIcons.add( ico );
+ }
+ }
+
+ /**
+ * Parsing an URL from the descriptionXML file
+ * @param url the string representation fo the URL
+ * @param baseURL the base device URL, needed if the url param is relative
+ * @return an URL object defining the url param
+ * @throws MalformedURLException if the url param or baseURL.toExternalForm() + url
+ * cannot be parsed to create an URL object
+ */
+ public final static URL getURL( String url, URL baseURL ) throws MalformedURLException {
+ URL rtrVal;
+ if ( url == null || url.trim().length() == 0 ) return null;
+ try {
+ rtrVal = new URL( url );
+ } catch ( MalformedURLException malEx ) {
+ // maybe that the url is relative, we add the baseURL and reparse it
+ // if relative then we take the device baser url root and add the url
+ if ( baseURL != null ) {
+ url = url.replace( '\\', '/' );
+ if ( url.charAt( 0 ) != '/' ) {
+ // the path is relative to the device baseURL
+ String externalForm = baseURL.toExternalForm();
+ if ( !externalForm.endsWith( "/" ) ) {
+ externalForm += "/";
+ }
+ rtrVal = new URL( externalForm + url );
+ } else {
+ // the path is not relative
+ String URLRoot = baseURL.getProtocol() + "://" + baseURL.getHost() + ":" + baseURL.getPort();
+ rtrVal = new URL( URLRoot + url );
+ }
+ } else {
+ throw malEx;
+ }
+ }
+ return rtrVal;
+ }
+
+ /**
+ * Retrieves the device definition XML data
+ * @return the device definition XML data as a String
+ */
+ public String getDeviceDefLocData() {
+ if ( deviceDefLocData == null ) {
+ try {
+ java.io.InputStream in = deviceDefLoc.openConnection().getInputStream();
+ int readen = 0;
+ byte[] buff = new byte[512];
+ StringBuffer strBuff = new StringBuffer();
+ while( ( readen = in.read( buff ) ) != -1 ) {
+ strBuff.append( new String( buff, 0, readen ) );
+ }
+ in.close();
+ deviceDefLocData = strBuff.toString();
+ } catch ( IOException ioEx ) {
+ return null;
+ }
+ }
+ return deviceDefLocData;
+ }
+
+ /**
+ * Used for JXPath parsing, do not use this method
+ * @return a Container object for Xpath parsing capabilities
+ */
+ public Container getUPNPDevice() {
+ return UPNPDevice;
+ }
+
+}
diff --git a/source/net/sbbi/upnp/impls/InternetGatewayDevice.java b/source/net/sbbi/upnp/impls/InternetGatewayDevice.java
new file mode 100644
index 000000000..a82b2ac3c
--- /dev/null
+++ b/source/net/sbbi/upnp/impls/InternetGatewayDevice.java
@@ -0,0 +1,506 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.impls;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import net.sbbi.upnp.Discovery;
+import net.sbbi.upnp.devices.UPNPDevice;
+import net.sbbi.upnp.devices.UPNPRootDevice;
+import net.sbbi.upnp.messages.ActionMessage;
+import net.sbbi.upnp.messages.ActionResponse;
+import net.sbbi.upnp.messages.StateVariableMessage;
+import net.sbbi.upnp.messages.StateVariableResponse;
+import net.sbbi.upnp.messages.UPNPMessageFactory;
+import net.sbbi.upnp.messages.UPNPResponseException;
+import net.sbbi.upnp.services.UPNPService;
+
+/**
+ * This class can be used to access some funtionalities on the
+ * InternetGatewayDevice on your network without having to know
+ * anything about the required input/output parameters.
+ * All device functions are not provided.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class InternetGatewayDevice {
+
+ private final static Log log = LogFactory.getLog( InternetGatewayDevice.class );
+
+ private UPNPRootDevice igd;
+ private UPNPMessageFactory msgFactory;
+
+ public InternetGatewayDevice( UPNPRootDevice igd ) throws UnsupportedOperationException {
+ this( igd, true, true );
+ }
+
+ private InternetGatewayDevice( UPNPRootDevice igd, boolean WANIPConnection, boolean WANPPPConnection ) throws UnsupportedOperationException {
+ this.igd = igd;
+ UPNPDevice myIGDWANConnDevice = igd.getChildDevice( "urn:schemas-upnp-org:device:WANConnectionDevice:1" );
+ if ( myIGDWANConnDevice == null ) {
+ throw new UnsupportedOperationException( "device urn:schemas-upnp-org:device:WANConnectionDevice:1 not supported by IGD device " + igd.getModelName() );
+ }
+
+ UPNPService wanIPSrv = myIGDWANConnDevice.getService( "urn:schemas-upnp-org:service:WANIPConnection:1" );
+ UPNPService wanPPPSrv = myIGDWANConnDevice.getService( "urn:schemas-upnp-org:service:WANPPPConnection:1" );
+
+ if ( ( WANIPConnection && WANPPPConnection ) && ( wanIPSrv == null && wanPPPSrv == null ) ) {
+ throw new UnsupportedOperationException( "Unable to find any urn:schemas-upnp-org:service:WANIPConnection:1 or urn:schemas-upnp-org:service:WANPPPConnection:1 service" );
+ } else if ( ( WANIPConnection && !WANPPPConnection ) && wanIPSrv == null ) {
+ throw new UnsupportedOperationException( "Unable to find any urn:schemas-upnp-org:service:WANIPConnection:1 service" );
+ } else if ( ( !WANIPConnection && WANPPPConnection ) && wanPPPSrv == null ) {
+ throw new UnsupportedOperationException( "Unable to find any urn:schemas-upnp-org:service:WANPPPConnection:1 service" );
+ }
+
+ if ( wanIPSrv != null && wanPPPSrv == null ) {
+ msgFactory = UPNPMessageFactory.getNewInstance( wanIPSrv );
+ } else if ( wanPPPSrv != null && wanIPSrv == null ) {
+ msgFactory = UPNPMessageFactory.getNewInstance( wanPPPSrv );
+ } else {
+ // Unable to test the following code since no router implementing both IP and PPP connection on hands..
+ /*// discover the active WAN interface using the WANCommonInterfaceConfig specs
+ UPNPDevice wanDevice = igd.getChildDevice( "urn:schemas-upnp-org:device:WANDevice:1" );
+ UPNPService configService = wanDevice.getService( "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1" );
+ if ( configService != null ) {
+ // retreive the first active connection
+ ServiceAction act = configService.getUPNPServiceAction( "GetActiveConnection" );
+ if ( act != null ) {
+ UPNPMessageFactory msg = UPNPMessageFactory.getNewInstance( configService );
+ String deviceContainer = null;
+ String serviceID = null;
+ try {
+ // always lookup for the first index of active connections.
+ ActionResponse resp = msg.getMessage( "GetActiveConnection" ).setInputParameter( "NewActiveConnectionIndex", 0 ).service();
+ deviceContainer = resp.getOutActionArgumentValue( "NewActiveConnDeviceContainer" );
+ serviceID = resp.getOutActionArgumentValue( "NewActiveConnectionServiceID" );
+ } catch ( IOException ex ) {
+ // no response returned
+ } catch ( UPNPResponseException respEx ) {
+ // should never happen unless the damn thing is bugged
+ }
+ if ( deviceContainer != null && deviceContainer.trim().length() > 0 &&
+ serviceID != null && serviceID.trim().length() > 0 ) {
+ for ( Iterator i = igd.getChildDevices().iterator(); i.hasNext(); ) {
+ UPNPDevice dv = (UPNPDevice)i.next();
+
+ if ( deviceContainer.startsWith( dv.getUDN() ) &&
+ dv.getDeviceType().indexOf( ":WANConnectionDevice:" ) != -1 ) {
+ myIGDWANConnDevice = dv;
+ break;
+ }
+ }
+ msgFactory = UPNPMessageFactory.getNewInstance( myIGDWANConnDevice.getServiceByID( serviceID ) );
+ }
+ }
+ }*/
+ // Doing a tricky test with external IP address, the unactive interface should return a null value or none
+ if ( testWANInterface( wanIPSrv ) ) {
+ msgFactory = UPNPMessageFactory.getNewInstance( wanIPSrv );
+ } else if( testWANInterface( wanPPPSrv ) ) {
+ msgFactory = UPNPMessageFactory.getNewInstance( wanPPPSrv );
+ }
+ if ( msgFactory == null ) {
+ // Nothing found using WANCommonInterfaceConfig! IP by default
+ log.warn( "Unable to detect active WANIPConnection, dfaulting to urn:schemas-upnp-org:service:WANIPConnection:1" );
+ msgFactory = UPNPMessageFactory.getNewInstance( wanIPSrv );
+ }
+ }
+ }
+
+ private boolean testWANInterface( UPNPService srv ) {
+ UPNPMessageFactory tmp = UPNPMessageFactory.getNewInstance( srv );
+
+ ActionMessage msg = tmp.getMessage( "GetExternalIPAddress" );
+ String ipToParse = null;
+ try {
+ ipToParse = msg.service().getOutActionArgumentValue( "NewExternalIPAddress" );
+ } catch ( UPNPResponseException ex ) {
+ // ok probably not the IP interface
+ } catch ( IOException ex ) {
+ // not really normal
+ log.warn( "IOException occured during device detection", ex );
+ }
+ if ( ipToParse != null && ipToParse.length() > 0 && !ipToParse.equals( "0.0.0.0" ) ) {
+ try {
+ return InetAddress.getByName( ipToParse ) != null;
+ } catch ( UnknownHostException ex ) {
+ // ok a crappy IP provided, definitly the wrong interface..
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retreives the IDG UNPNRootDevice object
+ * @return the UNPNRootDevie object bound to this object
+ */
+ public UPNPRootDevice getIGDRootDevice() {
+ return igd;
+ }
+
+ /**
+ * Lookup all the IGD (IP or PPP) devices on the network. If a device implements both
+ * IP and PPP, the active service will be used for nat mappings.
+ * @param timeout the timeout in ms to listen for devices response, -1 for default value
+ * @return an array of devices to play with or null if nothing found.
+ * @throws IOException if some IO Exception occurs during discovery
+ */
+ public static InternetGatewayDevice[] getDevices( int timeout ) throws IOException {
+ return lookupDeviceDevices( timeout, Discovery.DEFAULT_TTL, Discovery.DEFAULT_MX, true, true, null );
+ }
+
+ /**
+ * Lookup all the IGD (IP urn:schemas-upnp-org:service:WANIPConnection:1, or PPP urn:schemas-upnp-org:service:WANPPPConnection:1)
+ * devices for a given network interface. If a device implements both
+ * IP and PPP, the active service will be used for nat mappings.
+ * @param timeout the timeout in ms to listen for devices response, -1 for default value
+ * @param ttl the discovery ttl such as {@link net.sbbi.upnp.Discovery#DEFAULT_TTL}
+ * @param mx the discovery mx such as {@link net.sbbi.upnp.Discovery#DEFAULT_MX}
+ * @param ni the network interface where to lookup IGD devices
+ * @return an array of devices to play with or null if nothing found.
+ * @throws IOException if some IO Exception occurs during discovery
+ */
+ public static InternetGatewayDevice[] getDevices( int timeout, int ttl, int mx, NetworkInterface ni ) throws IOException {
+ return lookupDeviceDevices( timeout, ttl, mx, true, true, ni );
+ }
+
+ /**
+ * Lookup all the IGD IP devices on the network (urn:schemas-upnp-org:service:WANIPConnection:1 service)
+ * @param timeout the timeout in ms to listen for devices response, -1 for default value
+ * @return an array of devices to play with or null if nothing found or if found devices
+ * do not have the urn:schemas-upnp-org:service:WANIPConnection:1 service
+ * @deprecated use generic {@link #getDevices(int)} or {@link #getDevices(int, int, int, NetworkInterface)} methods since this one is not
+ * usable with all IGD devices ( will only work with devices implementing the urn:schemas-upnp-org:service:WANIPConnection:1 service )
+ */
+ public static InternetGatewayDevice[] getIPDevices( int timeout ) throws IOException {
+ return lookupDeviceDevices( timeout, Discovery.DEFAULT_TTL, Discovery.DEFAULT_MX, true, false, null );
+ }
+
+ /**
+ * Lookup all the IGD PPP devices on the network (urn:schemas-upnp-org:service:WANPPPConnection:1 service)
+ * @param timeout the timeout in ms to listen for devices response, -1 for default value
+ * @return an array of devices to play with or null if nothing found or if found devices
+ * do not have the urn:schemas-upnp-org:service:WANPPPConnection:1 service
+ * @deprecated use generic {@link #getDevices(int)} or {@link #getDevices(int, int, int, NetworkInterface)} methods since this one is not
+ * usable with all IGD devices ( will only work with devices implementing the urn:schemas-upnp-org:service:WANPPPConnection:1 service )
+ */
+ public static InternetGatewayDevice[] getPPPDevices( int timeout ) throws IOException {
+ return lookupDeviceDevices( timeout, Discovery.DEFAULT_TTL, Discovery.DEFAULT_MX, false, true, null );
+ }
+
+ private static InternetGatewayDevice[] lookupDeviceDevices( int timeout, int ttl, int mx, boolean WANIPConnection, boolean WANPPPConnection, NetworkInterface ni ) throws IOException {
+ UPNPRootDevice[] devices = null;
+ InternetGatewayDevice[] rtrVal = null;
+ if ( timeout == -1 ) {
+ devices = Discovery.discover( Discovery.DEFAULT_TIMEOUT, ttl, mx, "urn:schemas-upnp-org:device:InternetGatewayDevice:1", ni );
+ } else {
+ devices = Discovery.discover( timeout, ttl, mx, "urn:schemas-upnp-org:device:InternetGatewayDevice:1", ni );
+ }
+
+ if ( devices != null ) {
+ Set valid = new HashSet();
+ for ( int i = 0; i < devices.length; i++ ) {
+ try {
+ valid.add( new InternetGatewayDevice( devices[i], WANIPConnection, WANPPPConnection ) );
+ } catch ( UnsupportedOperationException ex ) {
+ // the device is either not IP or PPP
+ if ( log.isDebugEnabled() ) log.debug( "UnsupportedOperationException during discovery " + ex.getMessage() );
+ }
+ }
+ if ( valid.size() == 0 ) {
+ return null;
+ }
+ rtrVal = new InternetGatewayDevice[valid.size()];
+ int i = 0;
+ for ( Iterator itr = valid.iterator(); itr.hasNext(); ) {
+ rtrVal[i++] = (InternetGatewayDevice)itr.next();
+ }
+
+ }
+ return rtrVal;
+ }
+
+ /**
+ * Retreives the external IP address
+ * @return a String representing the external IP
+ * @throws UPNPResponseException if the devices returns an error code
+ * @throws IOException if some error occurs during communication with the device
+ */
+ public String getExternalIPAddress() throws UPNPResponseException, IOException {
+ ActionMessage msg = msgFactory.getMessage( "GetExternalIPAddress" );
+ return msg.service().getOutActionArgumentValue( "NewExternalIPAddress" );
+ }
+
+ /**
+ * Retreives a generic port mapping entry.
+ * @param newPortMappingIndex the index to lookup in the nat table of the upnp device
+ * @return an action response Object containing the following fields :
+ * NewRemoteHost, NewExternalPort, NewProtocol, NewInternalPort,
+ * NewInternalClient, NewEnabled, NewPortMappingDescription, NewLeaseDuration or null if the index does not exists
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if some unexpected error occurs on the UPNP device
+ */
+ public ActionResponse getGenericPortMappingEntry( int newPortMappingIndex ) throws IOException, UPNPResponseException {
+
+ ActionMessage msg = msgFactory.getMessage( "GetGenericPortMappingEntry" );
+ msg.setInputParameter( "NewPortMappingIndex", newPortMappingIndex );
+
+ try {
+ return msg.service();
+ } catch ( UPNPResponseException ex ) {
+ if ( ex.getDetailErrorCode() == 714 ) {
+ return null;
+ }
+ throw ex;
+ }
+
+ }
+
+ /**
+ * Retreives information about a specific port mapping
+ * @param remoteHost the remote host ip to check, null if wildcard
+ * @param externalPort the port to check
+ * @param protocol the protocol for the mapping, either TCP or UDP
+ * @return an action response Object containing the following fields :
+ * NewInternalPort, NewInternalClient, NewEnabled, NewPortMappingDescription, NewLeaseDuration or
+ * null if no such entry exists in the device NAT table
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if some unexpected error occurs on the UPNP device
+ */
+ public ActionResponse getSpecificPortMappingEntry( String remoteHost, int externalPort, String protocol ) throws IOException, UPNPResponseException {
+ remoteHost = remoteHost == null ? "" : remoteHost;
+ checkPortMappingProtocol( protocol );
+ checkPortRange( externalPort );
+
+ ActionMessage msg = msgFactory.getMessage( "GetSpecificPortMappingEntry" );
+ msg.setInputParameter( "NewRemoteHost", remoteHost )
+ .setInputParameter( "NewExternalPort", externalPort )
+ .setInputParameter( "NewProtocol", protocol );
+
+ try {
+ return msg.service();
+ } catch ( UPNPResponseException ex ) {
+ if ( ex.getDetailErrorCode() == 714 ) {
+ return null;
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Configures a nat entry on the UPNP device.
+ * @param description the mapping description, null for no description
+ * @param remoteHost the remote host ip for this entry, null for a wildcard value
+ * @param internalPort the internal client port where data should be redirected
+ * @param externalPort the external port to open on the UPNP device an map on the internal client, 0 for a wildcard value
+ * @param internalClient the internal client ip where data should be redirected
+ * @param leaseDuration the lease duration in seconds 0 for an infinite time
+ * @param protocol the protocol, either TCP or UDP
+ * @return true if the port is mapped false if the mapping is allready done for another internal client
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if the device does not accept some settings :
+ * 402 Invalid Args See UPnP Device Architecture section on Control
+ * 501 Action Failed See UPnP Device Architecture section on Control
+ * 715 WildCardNotPermittedInSrcIP The source IP address cannot be wild-carded
+ * 716 WildCardNotPermittedInExtPort The external port cannot be wild-carded
+ * 724 SamePortValuesRequired Internal and External port values must be the same
+ * 725 OnlyPermanentLeasesSupported The NAT implementation only supports permanent lease times on port mappings
+ * 726 RemoteHostOnlySupportsWildcard RemoteHost must be a wildcard and cannot be a specific IP address or DNS name
+ * 727 ExternalPortOnlySupportsWildcard ExternalPort must be a wildcard and cannot be a specific port value
+ */
+ public boolean addPortMapping( String description, String remoteHost,
+ int internalPort, int externalPort,
+ String internalClient, int leaseDuration,
+ String protocol ) throws IOException, UPNPResponseException {
+ remoteHost = remoteHost == null ? "" : remoteHost;
+ checkPortMappingProtocol( protocol );
+ if ( externalPort != 0 ) {
+ checkPortRange( externalPort );
+ }
+ checkPortRange( internalPort );
+ description = description == null ? "" : description;
+ if ( leaseDuration < 0 ) throw new IllegalArgumentException( "Invalid leaseDuration (" + leaseDuration + ") value" );
+
+ ActionMessage msg = msgFactory.getMessage( "AddPortMapping" );
+ msg.setInputParameter( "NewRemoteHost", remoteHost )
+ .setInputParameter( "NewExternalPort", externalPort )
+ .setInputParameter( "NewProtocol", protocol )
+ .setInputParameter( "NewInternalPort", internalPort )
+ .setInputParameter( "NewInternalClient", internalClient )
+ .setInputParameter( "NewEnabled", true )
+ .setInputParameter( "NewPortMappingDescription", description )
+ .setInputParameter( "NewLeaseDuration", leaseDuration );
+ try {
+ msg.service();
+ return true;
+ } catch ( UPNPResponseException ex ) {
+ if ( ex.getDetailErrorCode() == 718 ) {
+ return false;
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Deletes a port mapping on the IDG device
+ * @param remoteHost the host ip for which the mapping was done, null value for a wildcard value
+ * @param externalPort the port to close
+ * @param protocol the protocol for the mapping, TCP or UDP
+ * @return true if the port has been unmapped correctly otherwise false ( entry does not exists ).
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if the devices returns an error message
+ */
+ public boolean deletePortMapping( String remoteHost, int externalPort, String protocol ) throws IOException, UPNPResponseException {
+
+ remoteHost = remoteHost == null ? "" : remoteHost;
+ checkPortMappingProtocol( protocol );
+ checkPortRange( externalPort );
+ ActionMessage msg = msgFactory.getMessage( "DeletePortMapping" );
+ msg.setInputParameter( "NewRemoteHost", remoteHost )
+ .setInputParameter( "NewExternalPort", externalPort )
+ .setInputParameter( "NewProtocol", protocol );
+ try {
+ msg.service();
+ return true;
+ } catch ( UPNPResponseException ex ) {
+ if ( ex.getDetailErrorCode() == 714 ) {
+ return false;
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Retreives the current number of mapping in the NAT table
+ * @return the nat table current number of mappings or null if the device does not allow to query state variables
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if the devices returns an error message with error code other than 404
+ */
+ public Integer getNatMappingsCount() throws IOException, UPNPResponseException {
+
+ Integer rtrval = null;
+ StateVariableMessage natTableSize = msgFactory.getStateVariableMessage( "PortMappingNumberOfEntries" );
+ try {
+ StateVariableResponse resp = natTableSize.service();
+ rtrval = new Integer( resp.getStateVariableValue() );
+ } catch ( UPNPResponseException ex ) {
+ // 404 can happen if device do not implement state variables queries
+ if ( ex.getDetailErrorCode() != 404 ) {
+ throw ex;
+ }
+ }
+ return rtrval;
+ }
+
+ /**
+ * Computes the total entries in avaliable in the nat table size, not that this method is not guaranteed to work
+ * with all upnp devices since it is not an generic IGD command
+ * @return the number of entries or null if the NAT table size cannot be computed for the device
+ * @throws IOException if some error occurs during communication with the device
+ * @throws UPNPResponseException if the devices returns an error message with error code other than 713 or 402
+ */
+ public Integer getNatTableSize() throws IOException, UPNPResponseException {
+
+ // first let's look at the first index.. some crappy devices do not start with index 0
+ // we stop at index 50
+ int startIndex = -1;
+ for ( int i = 0; i < 50; i++ ) {
+ try {
+ this.getGenericPortMappingEntry( i );
+ startIndex = i;
+ break;
+ } catch ( UPNPResponseException ex ) {
+ // some devices return the 402 code
+ if ( ex.getDetailErrorCode() != 713 && ex.getDetailErrorCode() != 402 ) {
+ throw ex;
+ }
+ }
+ }
+ if ( startIndex == -1 ) {
+ // humm nothing found within the first 200 indexes..
+ // returning null
+ return null;
+ }
+ int size = 0;
+ while ( true ) {
+
+ try {
+ this.getGenericPortMappingEntry( startIndex++ );
+ size++;
+ } catch ( UPNPResponseException ex ) {
+ if ( ex.getDetailErrorCode() == 713 || ex.getDetailErrorCode() == 402 ) {
+ /// ok index unknown
+ break;
+ }
+ throw ex;
+ }
+ }
+ return new Integer( size );
+ }
+
+ private void checkPortMappingProtocol( String prot ) throws IllegalArgumentException {
+ if ( prot == null || ( !prot.equals( "TCP" ) && !prot.equals( "UDP" ) ) )
+ throw new IllegalArgumentException( "PortMappingProtocol must be either TCP or UDP" );
+ }
+
+ private void checkPortRange( int port ) throws IllegalArgumentException {
+ if ( port < 1 || port > 65535 )
+ throw new IllegalArgumentException( "Port range must be between 1 and 65535" );
+ }
+
+
+}
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 .
+ */
+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 SuperBonBon
+ * @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( "\r\n" );
+ body.append( "" );
+ body.append( "" );
+ body.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( "" );
+ body.append( "" );
+ body.append( "" );
+
+ 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;
+
+ }
+
+}
diff --git a/source/net/sbbi/upnp/messages/ActionMessageResponseParser.java b/source/net/sbbi/upnp/messages/ActionMessageResponseParser.java
new file mode 100644
index 000000000..ce9e29392
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/ActionMessageResponseParser.java
@@ -0,0 +1,160 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.Attributes;
+
+import net.sbbi.upnp.services.*;
+
+/**
+ * Simple SAX handler for UPNP response message parsing, this message is in SOAP format
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ActionMessageResponseParser extends org.xml.sax.helpers.DefaultHandler {
+
+ private final static Log log = LogFactory.getLog( ActionMessageResponseParser.class );
+
+ private final static String SOAP_FAULT_EL = "Fault";
+
+ private ServiceAction serviceAction;
+ private String bodyElementName;
+ private boolean faultResponse = false;
+ private UPNPResponseException msgEx;
+
+ private boolean readFaultCode = false;
+ private boolean readFaultString = false;
+ private boolean readErrorCode = false;
+ private boolean readErrorDescription = false;
+ private boolean parseOutputParams = false;
+ private ActionResponse result;
+ private ServiceActionArgument parsedResultOutArg;
+
+ protected ActionMessageResponseParser( ServiceAction serviceAction ) {
+ this.serviceAction = serviceAction;
+ bodyElementName = serviceAction.getName() + "Response";
+ }
+
+ protected UPNPResponseException getUPNPResponseException() {
+ return msgEx;
+ }
+
+ protected ActionResponse getActionResponse() {
+ return result;
+ }
+
+ public void characters( char[] ch, int start, int length ) {
+ if ( parseOutputParams ) {
+ if ( parsedResultOutArg != null ) {
+ String origChars = result.getOutActionArgumentValue( parsedResultOutArg.getName() );
+ String newChars = new String( ch, start, length );
+ if ( origChars == null ) {
+ result.addResult( parsedResultOutArg, newChars );
+ } else {
+ result.addResult( parsedResultOutArg, origChars + newChars );
+ }
+ }
+ } else if ( readFaultCode ) {
+ msgEx.faultCode = new String( ch, start, length );
+ readFaultCode = false;
+ } else if ( readFaultString ) {
+ msgEx.faultString = new String( ch, start, length );
+ readFaultString = false;
+ } else if ( readErrorCode ) {
+ String code = new String( ch, start, length );
+ try {
+ msgEx.detailErrorCode = Integer.parseInt( code );
+ } catch ( Throwable ex ) {
+ log.debug( "Error during returned error code " + code + " parsing" );
+ }
+ readErrorCode = false;
+ } else if ( readErrorDescription ) {
+ msgEx.detailErrorDescription = new String( ch, start, length );
+ readErrorDescription = false;
+ }
+ }
+
+ public void startElement( String uri, String localName, String qName, Attributes attributes ) {
+ if ( parseOutputParams ) {
+ ServiceActionArgument arg = serviceAction.getActionArgument( localName );
+ if ( arg != null && arg.getDirection() == ServiceActionArgument.DIRECTION_OUT ) {
+ parsedResultOutArg = arg;
+ result.addResult( parsedResultOutArg, null );
+ } else {
+ parsedResultOutArg = null;
+ }
+ } else if ( faultResponse ) {
+ if ( localName.equals( "faultcode") ) {
+ readFaultCode = true;
+ } else if ( localName.equals( "faultstring" ) ) {
+ readFaultString = true;
+ } else if ( localName.equals( "errorCode" ) ) {
+ readErrorCode = true;
+ } else if ( localName.equals( "errorDescription" ) ) {
+ readErrorDescription = true;
+ }
+ } else if ( localName.equals( SOAP_FAULT_EL ) ) {
+ msgEx = new UPNPResponseException();
+ faultResponse = true;
+ } else if ( localName.equals( bodyElementName ) ) {
+ parseOutputParams = true;
+ result = new ActionResponse();
+ }
+ }
+
+ public void endElement( String uri, String localName, String qName ) {
+ if ( parsedResultOutArg != null && parsedResultOutArg.getName().equals( localName ) ) {
+ parsedResultOutArg = null;
+ } else if ( localName.equals( bodyElementName ) ) {
+ parseOutputParams = false;
+ }
+ }
+}
diff --git a/source/net/sbbi/upnp/messages/ActionResponse.java b/source/net/sbbi/upnp/messages/ActionResponse.java
new file mode 100644
index 000000000..bc67aa9e4
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/ActionResponse.java
@@ -0,0 +1,101 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import net.sbbi.upnp.services.ServiceActionArgument;
+
+import java.util.*;
+
+/**
+ * An action respons container Object
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class ActionResponse {
+
+ private Map outArguments = new HashMap();
+ private Map outArgumentsVals = new HashMap();
+
+ protected ActionResponse() {
+ }
+
+ public ServiceActionArgument getOutActionArgument( String actionArgumentName ) {
+ return (ServiceActionArgument)outArguments.get( actionArgumentName );
+ }
+
+ public String getOutActionArgumentValue( String actionArgumentName ) {
+ return (String)outArgumentsVals.get( actionArgumentName );
+ }
+
+ public Set getOutActionArgumentNames() {
+ return outArguments.keySet();
+ }
+
+ /**
+ * Adds a result to the response, adding an existing result ServiceActionArgument
+ * will override the ServiceActionArgument value
+ * @param arg the service action argument
+ * @param value the arg value
+ */
+ protected void addResult( ServiceActionArgument arg, String value ) {
+ outArguments.put( arg.getName(), arg );
+ outArgumentsVals.put( arg.getName(), value );
+ }
+
+ public String toString() {
+ StringBuffer rtrVal = new StringBuffer();
+ for ( Iterator i = outArguments.keySet().iterator(); i.hasNext(); ) {
+ String name = (String)i.next();
+ String value = (String)outArgumentsVals.get( name );
+ rtrVal.append( name ).append( "=" ).append( value );
+ if ( i.hasNext() ) rtrVal.append( "\n" );
+ }
+ return rtrVal.toString();
+ }
+
+}
diff --git a/source/net/sbbi/upnp/messages/StateVariableMessage.java b/source/net/sbbi/upnp/messages/StateVariableMessage.java
new file mode 100644
index 000000000..89fdf4b5d
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/StateVariableMessage.java
@@ -0,0 +1,204 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import org.xml.sax.*;
+import javax.xml.parsers.*;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import net.sbbi.upnp.services.*;
+
+/**
+ * This class is used to create state variable messages to
+ * comminicate with the device
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class StateVariableMessage {
+
+ private final static Log log = LogFactory.getLog( StateVariableMessage.class );
+
+ private UPNPService service;
+ private ServiceStateVariable serviceStateVar;
+
+ protected StateVariableMessage( UPNPService service, ServiceStateVariable serviceStateVar ) {
+ this.service = service;
+ this.serviceStateVar = serviceStateVar;
+ }
+
+ /**
+ * Executes the state variable query 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 state variable response object containing the variable value
+ * @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 StateVariableResponse service() throws IOException, UPNPResponseException {
+ StateVariableResponse rtrVal = null;
+ UPNPResponseException upnpEx = null;
+ IOException ioEx = null;
+ StringBuffer body = new StringBuffer( 256 );
+
+ body.append( "\r\n" );
+ body.append( "" );
+ body.append( "" );
+ body.append( "" );
+ body.append( "" ).append( serviceStateVar.getName() ).append( "" );
+ body.append( "" );
+ body.append( "" );
+ body.append( "" );
+
+ 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( "SOAPACTION", "\"urn:schemas-upnp-org:control-1-0#QueryStateVariable\"" );
+ conn.setRequestProperty( "CONTENT-TYPE", "text/xml; charset=\"utf-8\"" );
+ conn.setRequestProperty( "CONTENT-LENGTH", Integer.toString( body.length() ) );
+ OutputStream out = conn.getOutputStream();
+ out.write( body.toString().getBytes() );
+ out.flush();
+ 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 );
+ StateVariableResponseParser msgParser = new StateVariableResponseParser( serviceStateVar );
+ 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 ) {
+ // ignoring
+ }
+ }
+ if ( upnpEx == null ) {
+ if ( response == HttpURLConnection.HTTP_OK ) {
+ rtrVal = msgParser.getStateVariableResponse();
+ } 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();
+ }
+}
diff --git a/source/net/sbbi/upnp/messages/StateVariableResponse.java b/source/net/sbbi/upnp/messages/StateVariableResponse.java
new file mode 100644
index 000000000..196f7c036
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/StateVariableResponse.java
@@ -0,0 +1,74 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import net.sbbi.upnp.services.ServiceStateVariable;
+
+/**
+ * This class contains data returned by a state variable query response
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class StateVariableResponse {
+
+ protected ServiceStateVariable stateVar;
+ protected String stateVariableValue;
+
+ protected StateVariableResponse() {
+ }
+
+ public ServiceStateVariable getStateVar() {
+ return stateVar;
+ }
+
+ public String getStateVariableValue() {
+ return stateVariableValue;
+ }
+
+
+}
diff --git a/source/net/sbbi/upnp/messages/StateVariableResponseParser.java b/source/net/sbbi/upnp/messages/StateVariableResponseParser.java
new file mode 100644
index 000000000..000164ce0
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/StateVariableResponseParser.java
@@ -0,0 +1,154 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+import net.sbbi.upnp.services.*;
+
+/**
+ * Simple SAX handler for UPNP state variable query response message parsing, this message is in SOAP format
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class StateVariableResponseParser extends org.xml.sax.helpers.DefaultHandler {
+
+ private final static Log log = LogFactory.getLog( StateVariableResponseParser.class );
+
+ private final static String SOAP_FAULT_EL = "Fault";
+
+ private ServiceStateVariable stateVar;
+ private boolean faultResponse = false;
+ private UPNPResponseException msgEx;
+
+ private boolean readFaultCode = false;
+ private boolean readFaultString = false;
+ private boolean readErrorCode = false;
+ private boolean readErrorDescription = false;
+ private boolean parseStateVar = false;
+ private StateVariableResponse result;
+
+ protected StateVariableResponseParser( ServiceStateVariable stateVar ) {
+ this.stateVar = stateVar;
+ }
+
+ protected UPNPResponseException getUPNPResponseException() {
+ return msgEx;
+ }
+
+ protected StateVariableResponse getStateVariableResponse() {
+ return result;
+ }
+
+ public void characters( char[] ch, int start, int length ) {
+ if ( parseStateVar ) {
+ String origChars = result.stateVariableValue;
+ String newChars = new String( ch, start, length );
+ if ( origChars == null ) {
+ result.stateVariableValue = newChars;
+ } else {
+ result.stateVariableValue = origChars + newChars;
+ }
+ } else if ( readFaultCode ) {
+ msgEx.faultCode = new String( ch, start, length );
+ readFaultCode = false;
+ } else if ( readFaultString ) {
+ msgEx.faultString = new String( ch, start, length );
+ readFaultString = false;
+ } else if ( readErrorCode ) {
+ String code = new String( ch, start, length );
+ try {
+ msgEx.detailErrorCode = Integer.parseInt( code );
+ } catch ( Throwable ex ) {
+ log.debug( "Error during returned error code " + code + " parsing" );
+ }
+ readErrorCode = false;
+ } else if ( readErrorDescription ) {
+ msgEx.detailErrorDescription = new String( ch, start, length );
+ readErrorDescription = false;
+ }
+ }
+
+ public void startElement( String uri, String localName, String qName, Attributes attributes ) {
+
+ if ( faultResponse ) {
+ if ( localName.equals( "faultcode") ) {
+ readFaultCode = true;
+ } else if ( localName.equals( "faultstring" ) ) {
+ readFaultString = true;
+ } else if ( localName.equals( "errorCode" ) ) {
+ readErrorCode = true;
+ } else if ( localName.equals( "errorDescription" ) ) {
+ readErrorDescription = true;
+ }
+ } else if ( localName.equals( SOAP_FAULT_EL ) ) {
+ msgEx = new UPNPResponseException();
+ faultResponse = true;
+ } else if ( localName.equals( "return" ) || localName.equals( "varName" ) ) {
+ // some buggy implementations ( intel sample media server )
+ // do not use the specs compliant return element name but varName ...
+ parseStateVar = true;
+ result = new StateVariableResponse();
+ result.stateVar = stateVar;
+ }
+ }
+
+ public void endElement( String uri, String localName, String qName ) throws SAXException {
+ // some buggy implementations ( intel sample media server )
+ // do not use the specs compliant return element name but varName ...
+ if ( localName.equals( "return" ) || localName.equals( "varName" ) ) {
+ parseStateVar = false;
+ }
+ }
+
+}
+
diff --git a/source/net/sbbi/upnp/messages/UPNPMessageFactory.java b/source/net/sbbi/upnp/messages/UPNPMessageFactory.java
new file mode 100644
index 000000000..9880582dc
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/UPNPMessageFactory.java
@@ -0,0 +1,108 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+import net.sbbi.upnp.services.*;
+
+/**
+ * Factory to create UPNP messages to access and communicate with
+ * a given UPNPDevice service capabilities
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class UPNPMessageFactory {
+
+ private UPNPService service;
+
+ /**
+ * Private constructor since this is a factory.
+ * @param service the UPNPService that will be used to generate messages by thid factory
+ */
+ private UPNPMessageFactory( UPNPService service ) {
+ this.service = service;
+ }
+
+ /**
+ * Generate a new factory instance for a given device service definition object
+ * @param service the UPNP service definition object for messages creation
+ * @return a new message factory
+ */
+ public static UPNPMessageFactory getNewInstance( UPNPService service ) {
+ return new UPNPMessageFactory( service );
+ }
+
+ /**
+ * Creation of a new ActionMessage to communicate with the UPNP device
+ * @param serviceActionName the name of a service action, this name is case sensitive
+ * and matches exactly the name provided by the UPNP device in the XML definition file
+ * @return a ActionMessage object or null if the action is unknown for this service messages factory
+ */
+ public ActionMessage getMessage( String serviceActionName ) {
+ ServiceAction serviceAction = service.getUPNPServiceAction( serviceActionName );
+ if ( serviceAction != null ) {
+ return new ActionMessage( service, serviceAction );
+ }
+ return null;
+ }
+
+ /**
+ * Creation of a new StateVariableMessage to communicate with the UPNP device, for a service state variable query
+ * @param serviceStateVariable the name of a service state variable, this name is case sensitive
+ * and matches exactly the name provided by the UPNP device in the XML definition file
+ * @return a StateVariableMessage object or null if the state variable is unknown for this service mesages factory
+ */
+ public StateVariableMessage getStateVariableMessage( String serviceStateVariable ) {
+ ServiceStateVariable stateVar = service.getUPNPServiceStateVariable( serviceStateVariable );
+ if ( stateVar != null ) {
+ return new StateVariableMessage( service, stateVar );
+ }
+ return null;
+ }
+
+}
diff --git a/source/net/sbbi/upnp/messages/UPNPResponseException.java b/source/net/sbbi/upnp/messages/UPNPResponseException.java
new file mode 100644
index 000000000..d3a5e1f48
--- /dev/null
+++ b/source/net/sbbi/upnp/messages/UPNPResponseException.java
@@ -0,0 +1,97 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.messages;
+
+/**
+ * An exception throws when parsing a message if a SOAP fault
+ * exception is returned.
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class UPNPResponseException extends Exception {
+
+ private static final long serialVersionUID = 8313107558129180594L;
+
+ protected String faultCode;
+ protected String faultString;
+ protected int detailErrorCode;
+ protected String detailErrorDescription;
+
+ public UPNPResponseException() {
+ }
+
+ public UPNPResponseException( int detailErrorCode, String detailErrorDescription ) {
+ this.detailErrorCode = detailErrorCode;
+ this.detailErrorDescription = detailErrorDescription;
+ }
+
+ public String getFaultCode() {
+ return faultCode == null ? "Client" : faultCode ;
+ }
+
+ public String getFaultString() {
+ return faultString == null ? "UPnPError" : faultString;
+ }
+
+ public int getDetailErrorCode() {
+ return detailErrorCode;
+ }
+
+ public String getDetailErrorDescription() {
+ return detailErrorDescription;
+ }
+
+ public String getMessage() {
+ return "Detailed error code :" + detailErrorCode + ", Detailed error description :" + detailErrorDescription;
+ }
+
+ public String getLocalizedMessage() {
+ return getMessage();
+ }
+
+}
diff --git a/source/net/sbbi/upnp/services/ISO8601Date.java b/source/net/sbbi/upnp/services/ISO8601Date.java
new file mode 100644
index 000000000..6194a7109
--- /dev/null
+++ b/source/net/sbbi/upnp/services/ISO8601Date.java
@@ -0,0 +1,331 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+/**
+ * ISO8601 Date implementation taken from org.w3c package and modified
+ * to work with UPNP date types
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ISO8601Date {
+
+ private static boolean check(StringTokenizer st, String token) throws NumberFormatException {
+ try {
+ if (st.nextToken().equals(token)) {
+ return true;
+ } else {
+ throw new NumberFormatException("Missing [" + token + "]");
+ }
+ } catch (NoSuchElementException ex) {
+ return false;
+ }
+ }
+
+ private static Calendar getCalendar( String isodate ) throws NumberFormatException {
+ // YYYY-MM-DDThh:mm:ss.sTZD or hh:mm:ss.sTZD
+ // does it contains a date ?
+ boolean isATime = isodate.indexOf( ':' ) != -1;
+ boolean isADate = isodate.indexOf( '-' ) != -1 || ( isodate.length() == 4 && !isATime );
+ if ( isATime && ! isADate ) {
+ if ( !isodate.toUpperCase().startsWith( "T" ) ) {
+ isodate = "T" + isodate;
+ }
+ }
+ StringTokenizer st = new StringTokenizer(isodate, "-T:.+Z", true);
+ Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.clear();
+ if ( isADate ) {
+ // Year
+ if (st.hasMoreTokens()) {
+ int year = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.YEAR, year);
+ } else {
+ return calendar;
+ }
+ // Month
+ if (check(st, "-") && (st.hasMoreTokens())) {
+ int month = Integer.parseInt(st.nextToken()) - 1;
+ calendar.set(Calendar.MONTH, month);
+ } else {
+ return calendar;
+ }
+ // Day
+ if (check(st, "-") && (st.hasMoreTokens())) {
+ int day = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ } else {
+ return calendar;
+ }
+ }
+ // Hour
+ if ( ( check(st, "T") ) && ( st.hasMoreTokens() ) ) {
+ int hour = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ } else {
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+ // Minutes
+ if (check(st, ":") && (st.hasMoreTokens())) {
+ int minutes = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.MINUTE, minutes);
+ } else {
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ return calendar;
+ }
+
+ //
+ // Not mandatory now
+ //
+
+ // Secondes
+ if (!st.hasMoreTokens()) {
+ return calendar;
+ }
+ String tok = st.nextToken();
+ if (tok.equals(":")) { // secondes
+ if (st.hasMoreTokens()) {
+ int secondes = Integer.parseInt(st.nextToken());
+ calendar.set(Calendar.SECOND, secondes);
+ if (!st.hasMoreTokens()) {
+ return calendar;
+ }
+ // frac sec
+ tok = st.nextToken();
+ if (tok.equals(".")) {
+ // bug fixed, thx to Martin Bottcher
+ String nt = st.nextToken();
+ while (nt.length() < 3) {
+ nt += "0";
+ }
+ nt = nt.substring(0, 3); // Cut trailing chars..
+ int millisec = Integer.parseInt(nt);
+ // int millisec = Integer.parseInt(st.nextToken()) * 10;
+ calendar.set(Calendar.MILLISECOND, millisec);
+ if (!st.hasMoreTokens()) {
+ return calendar;
+ }
+ tok = st.nextToken();
+ } else {
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+ } else {
+ throw new NumberFormatException("No secondes specified");
+ }
+ } else {
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+ // Timezone
+ if (!tok.equals("Z")) { // UTC
+ if (!(tok.equals("+") || tok.equals("-"))) {
+ throw new NumberFormatException("only Z, + or - allowed");
+ }
+ boolean plus = tok.equals("+");
+ if (!st.hasMoreTokens()) {
+ throw new NumberFormatException("Missing hour field");
+ }
+ int tzhour = Integer.parseInt(st.nextToken());
+ int tzmin = 0;
+ if (check(st, ":") && (st.hasMoreTokens())) {
+ tzmin = Integer.parseInt(st.nextToken());
+ } else {
+ throw new NumberFormatException("Missing minute field");
+ }
+ if (plus) {
+ calendar.add(Calendar.HOUR, -tzhour);
+ calendar.add(Calendar.MINUTE, -tzmin);
+ } else {
+ calendar.add(Calendar.HOUR, tzhour);
+ calendar.add(Calendar.MINUTE, tzmin);
+ }
+ }
+ return calendar;
+ }
+
+ /**
+ * Parse the given string in ISO 8601 format and build a Date object.
+ * @param isodate the date in ISO 8601 format
+ * @return a Date instance
+ * @exception InvalidDateException
+ * if the date is not valid
+ */
+ public static Date parse(String isodate) throws NumberFormatException {
+ Calendar calendar = getCalendar(isodate);
+ return calendar.getTime();
+ }
+
+ private static String twoDigit(int i) {
+ if (i >= 0 && i < 10) {
+ return "0" + String.valueOf(i);
+ }
+ return String.valueOf(i);
+ }
+
+ /**
+ * Generate a ISO 8601 date
+ * @param date a Date instance
+ * @return a string representing the date in the ISO 8601 format
+ */
+ public static String getIsoDate(Date date) {
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(calendar.get(Calendar.YEAR));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
+ return buffer.toString();
+ }
+
+ /**
+ * Generate a ISO 8601 date time without timezone
+ * @param date a Date instance
+ * @return a string representing the date in the ISO 8601 format
+ */
+ public static String getIsoDateTime(Date date) {
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(calendar.get(Calendar.YEAR));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
+ buffer.append("T");
+ buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
+ buffer.append(".");
+ buffer.append(twoDigit(calendar.get(Calendar.MILLISECOND) / 10));
+ return buffer.toString();
+ }
+
+ /**
+ * Generate a ISO 8601 date time with timezone
+ * @param date a Date instance
+ * @return a string representing the date in the ISO 8601 format
+ */
+ public static String getIsoDateTimeZone(Date date) {
+ Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.setTime(date);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(calendar.get(Calendar.YEAR));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.MONTH) + 1));
+ buffer.append("-");
+ buffer.append(twoDigit(calendar.get(Calendar.DAY_OF_MONTH)));
+ buffer.append("T");
+ buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
+ buffer.append(".");
+ buffer.append(twoDigit(calendar.get(Calendar.MILLISECOND) / 10));
+ buffer.append("Z");
+ return buffer.toString();
+ }
+
+ /**
+ * Generate a ISO 8601 time
+ * @param date a Date instance
+ * @return a string representing the date in the ISO 8601 format
+ */
+ public static String getIsoTime(Date date) {
+ Calendar calendar = new GregorianCalendar();
+ calendar.setTime(date);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
+ buffer.append(".");
+ buffer.append(twoDigit(calendar.get(Calendar.MILLISECOND) / 10));
+ return buffer.toString();
+ }
+
+ /**
+ * Generate a ISO 8601 time
+ * @param date a Date instance
+ * @return a string representing the date in the ISO 8601 format
+ */
+ public static String getIsoTimeZone(Date date) {
+ Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+ calendar.setTime(date);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(twoDigit(calendar.get(Calendar.HOUR_OF_DAY)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.MINUTE)));
+ buffer.append(":");
+ buffer.append(twoDigit(calendar.get(Calendar.SECOND)));
+ buffer.append(".");
+ buffer.append(twoDigit(calendar.get(Calendar.MILLISECOND) / 10));
+ buffer.append("Z");
+ return buffer.toString();
+ }
+
+}
diff --git a/source/net/sbbi/upnp/services/ServiceAction.java b/source/net/sbbi/upnp/services/ServiceAction.java
new file mode 100644
index 000000000..b994ffc16
--- /dev/null
+++ b/source/net/sbbi/upnp/services/ServiceAction.java
@@ -0,0 +1,202 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+import java.util.*;
+
+/**
+ * An object to represent a service action proposed by an UPNP service
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class ServiceAction {
+
+ protected String name;
+ protected UPNPService parent;
+ private List orderedActionArguments;
+ private List orderedInputActionArguments;
+ private List orderedOutputActionArguments;
+ private List orderedInputActionArgumentsNames;
+ private List orderedOutputActionArgumentsNames;
+
+ protected ServiceAction() {
+ }
+
+ public UPNPService getParent() {
+ return parent;
+ }
+
+ /**
+ * The action in and out arguments ServiceActionArgument objects list
+ * @return the list with ServiceActionArgument objects or null if the action has no params
+ */
+ public List getActionArguments() {
+ return orderedActionArguments;
+ }
+
+ /**
+ * Look for an ServiceActionArgument for a given name
+ * @param argumentName the argument name
+ * @return the argument or null if not found or not available
+ */
+ public ServiceActionArgument getActionArgument( String argumentName ) {
+ if ( orderedActionArguments == null ) return null;
+ for ( Iterator i = orderedActionArguments.iterator(); i.hasNext(); ) {
+ ServiceActionArgument arg = (ServiceActionArgument)i.next();
+ if ( arg.getName().equals( argumentName ) ) return arg;
+ }
+ return null;
+ }
+
+ protected void setActionArguments( List orderedActionArguments ) {
+ this.orderedActionArguments = orderedActionArguments;
+ orderedInputActionArguments = getListForActionArgument( orderedActionArguments, ServiceActionArgument.DIRECTION_IN );
+ orderedOutputActionArguments = getListForActionArgument( orderedActionArguments, ServiceActionArgument.DIRECTION_OUT );
+ orderedInputActionArgumentsNames = getListForActionArgumentNames( orderedActionArguments, ServiceActionArgument.DIRECTION_IN );
+ orderedOutputActionArgumentsNames = getListForActionArgumentNames( orderedActionArguments, ServiceActionArgument.DIRECTION_OUT );
+ }
+
+ /**
+ * Return a list containing input ( when a response is sent ) arguments objects
+ * @return a list containing input arguments ServiceActionArgument objects or null when nothing
+ * is needed for such operation
+ */
+ public List getInputActionArguments() {
+ return orderedInputActionArguments;
+ }
+
+ /**
+ * Look for an input ServiceActionArgument for a given name
+ * @param argumentName the input argument name
+ * @return the argument or null if not found or not available
+ */
+ public ServiceActionArgument getInputActionArgument( String argumentName ) {
+ if ( orderedInputActionArguments == null ) return null;
+ for ( Iterator i = orderedInputActionArguments.iterator(); i.hasNext(); ) {
+ ServiceActionArgument arg = (ServiceActionArgument)i.next();
+ if ( arg.getName().equals( argumentName ) ) return arg;
+ }
+ return null;
+ }
+
+
+ /**
+ * Return a list containing output ( when a response is received ) arguments objects
+ * @return a list containing output arguments ServiceActionArgument objects or null when nothing
+ * returned for such operation
+ */
+ public List getOutputActionArguments() {
+ return orderedOutputActionArguments;
+ }
+
+ /**
+ * Look for an output ServiceActionArgument for a given name
+ * @param argumentName the input argument name
+ * @return the argument or null if not found or not available
+ */
+ public ServiceActionArgument getOutputActionArgument( String argumentName ) {
+ if ( orderedOutputActionArguments == null ) return null;
+ for ( Iterator i = orderedOutputActionArguments.iterator(); i.hasNext(); ) {
+ ServiceActionArgument arg = (ServiceActionArgument)i.next();
+ if ( arg.getName().equals( argumentName ) ) return arg;
+ }
+ return null;
+ }
+
+ /**
+ * Return a list containing input ( when a response is sent ) arguments names
+ * @return a list containing input arguments names as Strings or null when nothing
+ * is needed for such operation
+ */
+ public List getInputActionArgumentsNames() {
+ return orderedInputActionArgumentsNames;
+ }
+
+ /**
+ * Return a list containing output ( when a response is received ) arguments names
+ * @return a list containing output arguments names as Strings or null when nothing
+ * returned for such operation
+ */
+ public List getOutputActionArgumentsNames() {
+ return orderedOutputActionArgumentsNames;
+ }
+
+ /**
+ * The action name
+ * @return The action name
+ */
+ public String getName() {
+ return name;
+ }
+
+ private List getListForActionArgument( List args, String direction ) {
+ if ( args == null ) return null;
+ List rtrVal = new ArrayList();
+ for ( Iterator itr = args.iterator(); itr.hasNext(); ) {
+ ServiceActionArgument actArg = (ServiceActionArgument)itr.next();
+ if ( actArg.getDirection() == direction ) {
+ rtrVal.add( actArg );
+ }
+ }
+ if ( rtrVal.size() == 0 ) rtrVal = null;
+ return rtrVal;
+ }
+
+ private List getListForActionArgumentNames( List args, String direction ) {
+ if ( args == null ) return null;
+ List rtrVal = new ArrayList();
+ for ( Iterator itr = args.iterator(); itr.hasNext(); ) {
+ ServiceActionArgument actArg = (ServiceActionArgument)itr.next();
+ if ( actArg.getDirection() == direction ) {
+ rtrVal.add( actArg.getName() );
+ }
+ }
+ if ( rtrVal.size() == 0 ) rtrVal = null;
+ return rtrVal;
+ }
+}
diff --git a/source/net/sbbi/upnp/services/ServiceActionArgument.java b/source/net/sbbi/upnp/services/ServiceActionArgument.java
new file mode 100644
index 000000000..4bd1a613e
--- /dev/null
+++ b/source/net/sbbi/upnp/services/ServiceActionArgument.java
@@ -0,0 +1,87 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+/**
+ * An argument for a service action
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class ServiceActionArgument {
+
+ public final static String DIRECTION_IN = "in";
+ public final static String DIRECTION_OUT = "out";
+
+ protected ServiceStateVariable relatedStateVariable;
+ protected String name;
+ protected String direction;
+
+ /**
+ * The related service state variable for this ServiceActionArgument
+ * @return The related service state variable for this ServiceActionArgument
+ */
+ public ServiceStateVariable getRelatedStateVariable() {
+ return relatedStateVariable;
+ }
+
+ /**
+ * The argument name
+ * @return the argument name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The argument direction
+ * @return the argument direction (in|out)
+ */
+ public String getDirection() {
+ return direction;
+ }
+}
diff --git a/source/net/sbbi/upnp/services/ServiceStateVariable.java b/source/net/sbbi/upnp/services/ServiceStateVariable.java
new file mode 100644
index 000000000..0ae5e3437
--- /dev/null
+++ b/source/net/sbbi/upnp/services/ServiceStateVariable.java
@@ -0,0 +1,263 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
+import java.util.Set;
+
+import net.sbbi.upnp.messages.StateVariableMessage;
+import net.sbbi.upnp.messages.UPNPMessageFactory;
+import net.sbbi.upnp.messages.UPNPResponseException;
+
+/**
+ * Class to contain a service state variable definition
+ * @author SuperBonBon
+ * @version 1.0
+ */
+
+public class ServiceStateVariable implements ServiceStateVariableTypes {
+
+ private StateVariableMessage stateVarMsg = null;
+
+ protected String name;
+ protected boolean sendEvents;
+ protected String dataType;
+ protected String defaultValue;
+
+ protected String minimumRangeValue;
+ protected String maximumRangeValue;
+ protected String stepRangeValue;
+ protected Set allowedvalues;
+ protected UPNPService parent;
+
+ protected ServiceStateVariable() {
+ }
+
+ /**
+ * Call to the UPNP device to retreive the state variable actual value
+ * @return the state variable actual value on the device, should be never null, an empty string could be returned by the device
+ * @throws UPNPResponseException if the device throws an exception during query
+ * @throws IOException if some IO error with device occurs during query
+ */
+ public String getValue() throws UPNPResponseException, IOException {
+ if ( stateVarMsg == null ) {
+ synchronized( this ) {
+ if ( stateVarMsg == null ) {
+ UPNPMessageFactory factory = UPNPMessageFactory.getNewInstance( parent );
+ stateVarMsg = factory.getStateVariableMessage( name );
+ }
+ }
+ }
+ return stateVarMsg.service().getStateVariableValue();
+ }
+
+ /**
+ * State variable name
+ * @return the state variable name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The parent UPNPService Object
+ * @return the parent object instance
+ */
+ public UPNPService getParent() {
+ return parent;
+ }
+
+ /**
+ * Boolean to indicate if the variable is sending events when value
+ * of the var is changing. The events can be subscribed via the {@link net.sbbi.upnp.ServicesEventing}
+ * class
+ * @return true if sending events
+ */
+ public boolean isSendEvents() {
+ return sendEvents;
+ }
+
+ /**
+ * The default value of the state variable
+ * @return the default value representation as an string
+ */
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ /**
+ * The variable UPNP data type
+ * @return the data type
+ */
+ public String getDataType() {
+ return dataType;
+ }
+
+ /**
+ * The varialbe JAVA data type (using an UPNP->Java mapping)
+ * @return the class mapped
+ */
+ public Class getDataTypeAsClass() {
+ return getDataTypeClassMapping( dataType );
+ }
+
+ /**
+ * A set of allowed values (String objects) for the variable
+ * @return the allowed values or null if none
+ */
+ public Set getAllowedvalues() {
+ return allowedvalues;
+ }
+
+ /**
+ * The minimum value as a string
+ * @return the minimum value or null if no restriction
+ */
+ public String getMinimumRangeValue() {
+ return minimumRangeValue;
+ }
+
+ /**
+ * The maximum value as a string
+ * @return the maximum value or null if no restriction
+ */
+ public String getMaximumRangeValue() {
+ return maximumRangeValue;
+ }
+
+ /**
+ * The value step range as a string
+ * @return the value step raqnge or null if no restriction
+ */
+ public String getStepRangeValue() {
+ return stepRangeValue;
+ }
+
+ public static Class getDataTypeClassMapping( String dataType ) {
+ int hash = dataType.hashCode();
+ if ( hash == UI1_INT ) return Short.class;
+ else if ( hash == UI2_INT ) return Integer.class;
+ else if ( hash == UI4_INT ) return Long.class;
+ else if ( hash == I1_INT ) return Byte.class;
+ else if ( hash == I2_INT ) return Short.class;
+ else if ( hash == I4_INT ) return Integer.class;
+ else if ( hash == INT_INT ) return Integer.class;
+ else if ( hash == R4_INT ) return Float.class;
+ else if ( hash == R8_INT ) return Double.class;
+ else if ( hash == NUMBER_INT ) return Double.class;
+ else if ( hash == FIXED_14_4_INT ) return Double.class;
+ else if ( hash == FLOAT_INT ) return Float.class;
+ else if ( hash == CHAR_INT) return Character.class;
+ else if ( hash == STRING_INT ) return String.class;
+ else if ( hash == DATE_INT ) return Date.class;
+ else if ( hash == DATETIME_INT ) return Date.class;
+ else if ( hash == DATETIME_TZ_INT ) return Date.class;
+ else if ( hash == TIME_INT ) return Date.class;
+ else if ( hash == TIME_TZ_INT ) return Date.class;
+ else if ( hash == BOOLEAN_INT ) return Boolean.class;
+ else if ( hash == BIN_BASE64_INT ) return String.class;
+ else if ( hash == BIN_HEX_INT ) return String.class;
+ else if ( hash == URI_INT ) return URI.class;
+ else if ( hash == UUID_INT ) return String.class;
+ return null;
+ }
+
+ public static String getUPNPDataTypeMapping( String className ) {
+ if ( className.equals( Short.class.getName() ) || className.equals( "short" )) return I2;
+ else if ( className.equals( Byte.class.getName() ) || className.equals( "byte" )) return I1;
+ else if ( className.equals( Integer.class.getName() ) || className.equals( "int" ) ) return INT;
+ else if ( className.equals( Long.class.getName() ) || className.equals( "long" ) ) return UI4;
+ else if ( className.equals( Float.class.getName() ) || className.equals( "float" )) return FLOAT;
+ else if ( className.equals( Double.class.getName() ) || className.equals( "double" ) ) return NUMBER;
+ else if ( className.equals( Character.class.getName() ) || className.equals( "char" ) ) return CHAR;
+ else if ( className.equals( String.class.getName() ) || className.equals( "string" ) ) return STRING;
+ else if ( className.equals( Date.class.getName() ) ) return DATETIME;
+ else if ( className.equals( Boolean.class.getName() ) || className.equals( "boolean" ) ) return BOOLEAN;
+ else if ( className.equals( URI.class.getName() ) ) return URI;
+ return null;
+ }
+
+ public static Object UPNPToJavaObject( String dataType, String value ) throws Throwable {
+ if ( value == null ) throw new Exception( "null value" );
+ if ( dataType == null ) throw new Exception( "null dataType" );
+ int hash = dataType.hashCode();
+ if ( hash == UI1_INT ) return new Short( value );
+ else if ( hash == UI2_INT ) return new Integer( value );
+ else if ( hash == UI4_INT ) return new Long( value );
+ else if ( hash == I1_INT ) return new Byte( value );
+ else if ( hash == I2_INT ) return new Short( value );
+ else if ( hash == I4_INT ) return new Integer( value );
+ else if ( hash == INT_INT ) return new Integer( value );
+ else if ( hash == R4_INT ) return new Float( value );
+ else if ( hash == R8_INT ) return new Double( value );
+ else if ( hash == NUMBER_INT ) return new Double( value );
+ else if ( hash == FIXED_14_4_INT ) return new Double( value );
+ else if ( hash == FLOAT_INT ) return new Float( value );
+ else if ( hash == CHAR_INT) return new Character( value.charAt( 0 ) );
+ else if ( hash == STRING_INT ) return value;
+ else if ( hash == DATE_INT ) return ISO8601Date.parse( value );
+ else if ( hash == DATETIME_INT ) return ISO8601Date.parse( value );
+ else if ( hash == DATETIME_TZ_INT ) return ISO8601Date.parse( value );
+ else if ( hash == TIME_INT ) return ISO8601Date.parse( value );
+ else if ( hash == TIME_TZ_INT ) return ISO8601Date.parse( value );
+ else if ( hash == BOOLEAN_INT ) {
+ if ( value.equals( "1" ) || value.equalsIgnoreCase( "yes" ) || value.equals( "true" ) ) {
+ return Boolean.TRUE;
+ }
+ return Boolean.FALSE;
+ }
+ else if ( hash == BIN_BASE64_INT ) return value;
+ else if ( hash == BIN_HEX_INT ) return value;
+ else if ( hash == URI_INT ) return new URI( value );
+ else if ( hash == UUID_INT ) return value;
+ throw new Exception( "Unhandled data type " + dataType );
+ }
+
+}
diff --git a/source/net/sbbi/upnp/services/ServiceStateVariableTypes.java b/source/net/sbbi/upnp/services/ServiceStateVariableTypes.java
new file mode 100644
index 000000000..558d559c0
--- /dev/null
+++ b/source/net/sbbi/upnp/services/ServiceStateVariableTypes.java
@@ -0,0 +1,188 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+/**
+ * Interface to defined allowed values for service state variables data types
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public interface ServiceStateVariableTypes {
+
+ /**
+ * Unsigned 1 Byte int. Same format as int without leading sign.
+ */
+ public final static String UI1 = "ui1";
+ /**
+ * Unsigned 2 Byte int. Same format as int without leading sign.
+ */
+ public final static String UI2 = "ui2";
+ /**
+ * Unsigned 4 Byte int. Same format as int without leading sign.
+ */
+ public final static String UI4 = "ui4";
+ /**
+ * 1 Byte int. Same format as int.
+ */
+ public final static String I1 = "i1";
+ /**
+ * 2 Byte int. Same format as int.
+ */
+ public final static String I2 = "i2";
+ /**
+ * 4 Byte int. Same format as int.
+ */
+ public final static String I4 = "i4";
+ /**
+ * Fixed point, integer number. May have leading sign. May have leading zeros.
+ * (No currency symbol.) (No grouping of digits to the left of the decimal, e.g., no commas.)
+ */
+ public final static String INT = "int";
+ /**
+ * 4 Byte float. Same format as float. Must be between 3.40282347E+38 to 1.17549435E-38.
+ */
+ public final static String R4 = "r4";
+ /**
+ * 8 Byte float. Same format as float. Must be between -1.79769313486232E308 and -4.94065645841247E-324
+ * for negative values, and between 4.94065645841247E-324 and 1.79769313486232E308 for positive values,
+ * i.e., IEEE 64-bit (8-Byte) double.
+ */
+ public final static String R8 = "r8";
+ /**
+ * Same as r8.
+ */
+ public final static String NUMBER = "number";
+ /**
+ * Same as r8 but no more than 14 digits to the left of the decimal point and no more than 4 to the right.
+ */
+ public final static String FIXED_14_4 = "fixed.14.4";
+ /**
+ * Floating point number. Mantissa (left of the decimal) and/or exponent may have a leading sign.
+ * Mantissa and/or exponent may have leading zeros. Decimal character in mantissa is a period, i.e.,
+ * whole digits in mantissa separated from fractional digits by period. Mantissa separated from exponent
+ * by E. (No currency symbol.) (No grouping of digits in the mantissa, e.g., no commas.)
+ */
+ public final static String FLOAT = "float";
+ /**
+ * Unicode string. One character long.
+ */
+ public final static String CHAR = "char";
+ /**
+ * Unicode string. No limit on length.
+ */
+ public final static String STRING = "string";
+ /**
+ * Date in a subset of ISO 8601 format without time data.
+ */
+ public final static String DATE = "date";
+ /**
+ * Date in ISO 8601 format with optional time but no time zone.
+ */
+ public final static String DATETIME = "dateTime";
+ /**
+ * Date in ISO 8601 format with optional time and optional time zone.
+ */
+ public final static String DATETIME_TZ = "dateTime.tz";
+ /**
+ * Time in a subset of ISO 8601 format with no date and no time zone.
+ */
+ public final static String TIME = "time";
+ /**
+ * Time in a subset of ISO 8601 format with optional time zone but no date.
+ */
+ public final static String TIME_TZ = "time.tz";
+ /**
+ * 0, false, or no for false; 1, true, or yes for true.
+ */
+ public final static String BOOLEAN = "boolean";
+ /**
+ * MIME-style Base64 encoded binary BLOB. Takes 3 Bytes, splits them into 4 parts,
+ * and maps each 6 bit piece to an octet. (3 octets are encoded as 4.) No limit on size.
+ */
+ public final static String BIN_BASE64 = "bin.base64";
+ /**
+ * Hexadecimal digits representing octets. Treats each nibble as a hex digit and encodes
+ * as a separate Byte. (1 octet is encoded as 2.) No limit on size.
+ */
+ public final static String BIN_HEX = "bin.hex";
+ /**
+ * Universal Resource Identifier.
+ */
+ public final static String URI = "uri";
+ /**
+ * Universally Unique ID. Hexadecimal digits representing octets.
+ * Optional embedded hyphens are ignored.
+ */
+ public final static String UUID = "uuid";
+
+ public final static int UI1_INT = "ui1".hashCode();
+ public final static int UI2_INT = "ui2".hashCode();
+ public final static int UI4_INT = "ui4".hashCode();
+ public final static int I1_INT = "i1".hashCode();
+ public final static int I2_INT = "i2".hashCode();
+ public final static int I4_INT = "i4".hashCode();
+ public final static int INT_INT = "int".hashCode();
+ public final static int R4_INT = "r4".hashCode();
+ public final static int R8_INT = "r8".hashCode();
+ public final static int NUMBER_INT = "number".hashCode();
+ public final static int FIXED_14_4_INT = "fixed.14.4".hashCode();
+ public final static int FLOAT_INT = "float".hashCode();
+ public final static int CHAR_INT = "char".hashCode();
+ public final static int STRING_INT = "string".hashCode();
+ public final static int DATE_INT = "date".hashCode();
+ public final static int DATETIME_INT = "dateTime".hashCode();
+ public final static int DATETIME_TZ_INT = "dateTime.tz".hashCode();
+ public final static int TIME_INT = "time".hashCode();
+ public final static int TIME_TZ_INT = "time.tz".hashCode();
+ public final static int BOOLEAN_INT = "boolean".hashCode();
+ public final static int BIN_BASE64_INT = "bin.base64".hashCode();
+ public final static int BIN_HEX_INT = "bin.hex".hashCode();
+ public final static int URI_INT = "uri".hashCode();
+ public final static int UUID_INT = "uuid".hashCode();
+
+}
diff --git a/source/net/sbbi/upnp/services/UPNPService.java b/source/net/sbbi/upnp/services/UPNPService.java
new file mode 100644
index 000000000..4c3bf2d90
--- /dev/null
+++ b/source/net/sbbi/upnp/services/UPNPService.java
@@ -0,0 +1,326 @@
+/*
+ * ============================================================================
+ * 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 .
+ */
+package net.sbbi.upnp.services;
+
+import org.apache.commons.jxpath.*;
+import org.apache.commons.jxpath.xml.*;
+
+import java.util.*;
+import java.io.IOException;
+import java.net.*;
+
+import net.sbbi.upnp.JXPathParser;
+import net.sbbi.upnp.devices.*;
+
+/**
+ * Representation of an UPNP service
+ * @author SuperBonBon
+ * @version 1.0
+ */
+public class UPNPService {
+
+ protected String serviceType;
+ protected String serviceId;
+ private int specVersionMajor;
+ private int specVersionMinor;
+ protected URL SCPDURL;
+ protected String SCPDURLData;
+ protected URL controlURL;
+ protected URL eventSubURL;
+ protected UPNPDevice serviceOwnerDevice;
+
+ protected Map UPNPServiceActions;
+ protected Map UPNPServiceStateVariables;
+ private String USN;
+
+ private boolean parsedSCPD = false;
+ private DocumentContainer UPNPService;
+
+ public UPNPService( JXPathContext serviceCtx, URL baseDeviceURL, UPNPDevice serviceOwnerDevice ) throws MalformedURLException {
+ this.serviceOwnerDevice = serviceOwnerDevice;
+ serviceType = (String)serviceCtx.getValue( "serviceType" );
+ serviceId = (String)serviceCtx.getValue( "serviceId" );
+ SCPDURL = UPNPRootDevice.getURL( (String)serviceCtx.getValue( "SCPDURL" ), baseDeviceURL );
+ controlURL = UPNPRootDevice.getURL( (String)serviceCtx.getValue( "controlURL" ), baseDeviceURL );
+ eventSubURL = UPNPRootDevice.getURL( (String)serviceCtx.getValue( "eventSubURL" ), baseDeviceURL );
+ USN = serviceOwnerDevice.getUDN().concat( "::" ).concat( serviceType );
+ }
+
+ public String getServiceType() {
+ return serviceType;
+ }
+
+ public String getServiceId() {
+ return serviceId;
+ }
+
+ public String getUSN(){
+ return USN;
+ }
+
+ public URL getSCPDURL() {
+ return SCPDURL;
+ }
+
+ public URL getControlURL() {
+ return controlURL;
+ }
+
+ public URL getEventSubURL() {
+ return eventSubURL;
+ }
+
+ public int getSpecVersionMajor() {
+ lazyInitiate();
+ return specVersionMajor;
+ }
+
+ public int getSpecVersionMinor() {
+ lazyInitiate();
+ return specVersionMinor;
+ }
+
+ public UPNPDevice getServiceOwnerDevice() {
+ return serviceOwnerDevice;
+ }
+
+ /**
+ * Retreives a service action for its given name
+ * @param actionName the service action name
+ * @return a ServiceAction object or null if no matching action for this service has been found
+ */
+ public ServiceAction getUPNPServiceAction( String actionName ) {
+ lazyInitiate();
+ return (ServiceAction)UPNPServiceActions.get( actionName );
+ }
+
+ /**
+ * Retreives a service state variable for its given name
+ * @param stateVariableName the state variable name
+ * @return a ServiceStateVariable object or null if no matching state variable has been found
+ */
+ public ServiceStateVariable getUPNPServiceStateVariable( String stateVariableName ) {
+ lazyInitiate();
+ return (ServiceStateVariable)UPNPServiceStateVariables.get( stateVariableName );
+ }
+
+ public Iterator getAvailableActionsName() {
+ lazyInitiate();
+ return UPNPServiceActions.keySet().iterator();
+ }
+
+ public int getAvailableActionsSize() {
+ lazyInitiate();
+ return UPNPServiceActions.keySet().size();
+ }
+
+ public Iterator getAvailableStateVariableName() {
+ lazyInitiate();
+ return UPNPServiceStateVariables.keySet().iterator();
+ }
+
+ public int getAvailableStateVariableSize() {
+ lazyInitiate();
+ return UPNPServiceStateVariables.keySet().size();
+ }
+
+ private void parseSCPD() {
+ try {
+ DocumentContainer.registerXMLParser( DocumentContainer.MODEL_DOM, new JXPathParser() );
+ UPNPService = new DocumentContainer( SCPDURL, DocumentContainer.MODEL_DOM );
+ JXPathContext context = JXPathContext.newContext( this );
+ Pointer rootPtr = context.getPointer( "UPNPService/scpd" );
+ JXPathContext rootCtx = context.getRelativeContext( rootPtr );
+
+ specVersionMajor = Integer.parseInt( (String)rootCtx.getValue( "specVersion/major" ) );
+ specVersionMinor = Integer.parseInt( (String)rootCtx.getValue( "specVersion/minor" ) );
+
+ parseServiceStateVariables( rootCtx );
+
+ Pointer actionsListPtr = rootCtx.getPointer( "actionList" );
+ JXPathContext actionsListCtx = context.getRelativeContext( actionsListPtr );
+ Double arraySize = (Double)actionsListCtx.getValue( "count( action )" );
+ UPNPServiceActions = new HashMap();
+ for ( int i = 1; i <= arraySize.intValue(); i++ ) {
+ ServiceAction action = new ServiceAction();
+ action.name = (String)actionsListCtx.getValue( "action["+i+"]/name" );
+ action.parent = this;
+ Pointer argumentListPtr = null;
+ try {
+ argumentListPtr = actionsListCtx.getPointer( "action["+i+"]/argumentList" );
+ } catch ( JXPathException ex ) {
+ // there is no arguments list.
+ }
+ if ( argumentListPtr != null ) {
+ JXPathContext argumentListCtx = actionsListCtx.getRelativeContext( argumentListPtr );
+ Double arraySizeArgs = (Double)argumentListCtx.getValue( "count( argument )" );
+
+ List orderedActionArguments = new ArrayList();
+ for ( int z = 1; z <= arraySizeArgs.intValue(); z++ ) {
+ ServiceActionArgument arg = new ServiceActionArgument();
+ arg.name = (String)argumentListCtx.getValue( "argument["+z+"]/name" );
+ String direction = (String)argumentListCtx.getValue( "argument["+z+"]/direction" );
+ arg.direction = direction.equals( ServiceActionArgument.DIRECTION_IN ) ? ServiceActionArgument.DIRECTION_IN : ServiceActionArgument.DIRECTION_OUT;
+ String stateVarName = (String)argumentListCtx.getValue( "argument["+z+"]/relatedStateVariable" );
+ ServiceStateVariable stateVar = (ServiceStateVariable)UPNPServiceStateVariables.get( stateVarName );
+ if ( stateVar == null ) {
+ throw new IllegalArgumentException( "Unable to find any state variable named " + stateVarName + " for service " + getServiceId() + " action " + action.name + " argument " + arg.name );
+ }
+ arg.relatedStateVariable = stateVar;
+ orderedActionArguments.add( arg );
+ }
+
+ if ( arraySizeArgs.intValue() > 0 ) {
+ action.setActionArguments( orderedActionArguments );
+ }
+ }
+
+ UPNPServiceActions.put( action.getName(), action );
+ }
+ parsedSCPD = true;
+ } catch ( Throwable t ) {
+ throw new RuntimeException( "Error during lazy SCDP file parsing at " + SCPDURL, t );
+ }
+ }
+
+ private void parseServiceStateVariables( JXPathContext rootContext ) {
+ Pointer serviceStateTablePtr = rootContext.getPointer( "serviceStateTable" );
+ JXPathContext serviceStateTableCtx = rootContext.getRelativeContext( serviceStateTablePtr );
+ Double arraySize = (Double)serviceStateTableCtx.getValue( "count( stateVariable )" );
+ UPNPServiceStateVariables = new HashMap();
+ for ( int i = 1; i <= arraySize.intValue(); i++ ) {
+ ServiceStateVariable srvStateVar = new ServiceStateVariable();
+ String sendEventsLcl = null;
+ try {
+ sendEventsLcl = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/@sendEvents" );
+ } catch ( JXPathException defEx ) {
+ // sendEvents not provided defaulting according to specs to "yes"
+ sendEventsLcl = "yes";
+ }
+ srvStateVar.parent = this;
+ srvStateVar.sendEvents = sendEventsLcl.equalsIgnoreCase( "no" ) ? false : true;
+ srvStateVar.name = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/name" );
+ srvStateVar.dataType = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/dataType" );
+ try {
+ srvStateVar.defaultValue = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/defaultValue" );
+ } catch ( JXPathException defEx ) {
+ // can happend since default value is not
+ }
+ Pointer allowedValuesPtr = null;
+ try {
+ allowedValuesPtr = serviceStateTableCtx.getPointer( "stateVariable["+i+"]/allowedValueList" );
+ } catch ( JXPathException ex ) {
+ // there is no allowed values list.
+ }
+ if ( allowedValuesPtr != null ) {
+ JXPathContext allowedValuesCtx = serviceStateTableCtx.getRelativeContext( allowedValuesPtr );
+ Double arraySizeAllowed = (Double)allowedValuesCtx.getValue( "count( allowedValue )" );
+ srvStateVar.allowedvalues = new HashSet();
+ for ( int z = 1; z <= arraySizeAllowed.intValue(); z++ ) {
+ String allowedValue = (String)allowedValuesCtx.getValue( "allowedValue["+z+"]" );
+ srvStateVar.allowedvalues.add( allowedValue );
+ }
+ }
+
+ Pointer allowedValueRangePtr = null;
+ try {
+ allowedValueRangePtr = serviceStateTableCtx.getPointer( "stateVariable["+i+"]/allowedValueRange" );
+ } catch ( JXPathException ex ) {
+ // there is no allowed values list, can happen
+ }
+ if ( allowedValueRangePtr != null ) {
+
+ srvStateVar.minimumRangeValue = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/allowedValueRange/minimum" );
+ srvStateVar.maximumRangeValue = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/allowedValueRange/maximum" );
+ try {
+ srvStateVar.stepRangeValue = (String)serviceStateTableCtx.getValue( "stateVariable["+i+"]/allowedValueRange/step" );
+ } catch ( JXPathException stepEx ) {
+ // can happend since step is not mandatory
+ }
+ }
+ UPNPServiceStateVariables.put( srvStateVar.getName(), srvStateVar );
+ }
+
+ }
+
+ private void lazyInitiate() {
+ if ( !parsedSCPD )
+ synchronized( this ) {
+ if ( !parsedSCPD )
+ parseSCPD();
+ }
+ }
+
+ /**
+ * Used for JXPath parsing, do not use this method
+ * @return a Container object for Xpath parsing capabilities
+ */
+ public Container getUPNPService() {
+ return UPNPService;
+ }
+
+ public String getSCDPData() {
+ if ( SCPDURLData == null ) {
+ try {
+
+ java.io.InputStream in = SCPDURL.openConnection().getInputStream();
+ int readen = 0;
+ byte[] buff = new byte[512];
+ StringBuffer strBuff = new StringBuffer();
+ while( ( readen = in.read( buff ) ) != -1 ) {
+ strBuff.append( new String( buff, 0, readen ) );
+ }
+ in.close();
+ SCPDURLData = strBuff.toString();
+ } catch ( IOException ioEx ) {
+ return null;
+ }
+ }
+ return SCPDURLData;
+ }
+}
diff --git a/source/net/sbbi/upnp/version.properties b/source/net/sbbi/upnp/version.properties
new file mode 100644
index 000000000..d948172d8
--- /dev/null
+++ b/source/net/sbbi/upnp/version.properties
@@ -0,0 +1,8 @@
+# please make sur to update those values
+# when crating a new release
+# especially to create cvs changelogs
+release.version=1.0.4
+release.version.cvs.tag=V1_0_4
+release.version.publish.date=20 Nov 2006
+last.release.version.cvs.tag=V1_0_3
+last.release.version.publish.date=14 Feb 2006
\ No newline at end of file