You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
19 KiB
451 lines
19 KiB
15 years ago
|
/*
|
||
|
* ============================================================================
|
||
|
* 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.yacy.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.yacy.upnp.JXPathParser;
|
||
|
import net.yacy.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;
|
||
|
}
|
||
|
|
||
|
}
|