Java tutorial
/* * ============================================================================ * The Apache Software License, Version 1.1 * ============================================================================ * * Copyright (C) 2002 The Apache Software Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without modifica- * tion, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by SuperBonBon Industries (http://www.sbbi.net/)." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "UPNPLib" and "SuperBonBon Industries" must not be * used to endorse or promote products derived from this software without * prior written permission. For written permission, please contact * info@sbbi.net. * * 5. Products derived from this software may not be called * "SuperBonBon Industries", nor may "SBBI" appear in their name, * without prior written permission of SuperBonBon Industries. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of SuperBonBon Industries. For more information on * SuperBonBon Industries, please see <http://www.sbbi.net/>. */ package net.sbbi.upnp.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 <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @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 (<device>) 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 (<device/serviceList>) 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 (<device/iconList>) 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; } }