/* * This software copyright by various authors including the RPTools.net * development team, and licensed under the LGPL Version 3 or, at your * option, any later version. * * Portions of this software were originally covered under the Apache * Software License, Version 1.1 or Version 2.0. * * See the file LICENSE elsewhere in this distribution for license details. */ package net.sbbi.upnp.services; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import net.sbbi.upnp.JXPathParser; import net.sbbi.upnp.devices.UPNPDevice; import net.sbbi.upnp.devices.UPNPRootDevice; import org.apache.commons.jxpath.Container; import org.apache.commons.jxpath.JXPathContext; import org.apache.commons.jxpath.JXPathException; import org.apache.commons.jxpath.Pointer; import org.apache.commons.jxpath.xml.DocumentContainer; /** * Representation of an UPNP service * * @author <a href="mailto:superbonbon@sbbi.net">SuperBonBon</a> * @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<String, ServiceAction> UPNPServiceActions; protected Map<String, ServiceStateVariable> UPNPServiceStateVariables; private final 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("upnp:serviceType"); serviceId = (String) serviceCtx.getValue("upnp:serviceId"); SCPDURL = UPNPRootDevice.getURL((String) serviceCtx.getValue("upnp:SCPDURL"), baseDeviceURL); controlURL = UPNPRootDevice.getURL((String) serviceCtx.getValue("upnp:controlURL"), baseDeviceURL); eventSubURL = UPNPRootDevice.getURL((String) serviceCtx.getValue("upnp: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 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 UPNPServiceStateVariables.get(stateVariableName); } public Iterator<String> getAvailableActionsName() { lazyInitiate(); return UPNPServiceActions.keySet().iterator(); } public int getAvailableActionsSize() { lazyInitiate(); return UPNPServiceActions.keySet().size(); } public Iterator<String> 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); context.registerNamespace("upnp", "urn:schemas-upnp-org:service-1-0"); Pointer rootPtr = null; rootPtr = context.getPointer("UPNPService/upnp:scpd"); JXPathContext rootCtx = context.getRelativeContext(rootPtr); specVersionMajor = Integer.parseInt((String) rootCtx.getValue("upnp:specVersion/upnp:major")); specVersionMinor = Integer.parseInt((String) rootCtx.getValue("upnp:specVersion/upnp:minor")); parseServiceStateVariables(rootCtx); Pointer actionsListPtr = rootCtx.getPointer("upnp:actionList"); JXPathContext actionsListCtx = context.getRelativeContext(actionsListPtr); Double arraySize = (Double) actionsListCtx.getValue("count( upnp:action )"); UPNPServiceActions = new HashMap<String, ServiceAction>(); for (int idx = 1; idx <= arraySize.intValue(); idx++) { ServiceAction action = new ServiceAction(); action.name = (String) actionsListCtx.getValue("upnp:action[" + idx + "]/upnp:name"); action.parent = this; Pointer argumentListPtr = null; try { argumentListPtr = actionsListCtx.getPointer("upnp:action[" + idx + "]/upnp:argumentList"); } catch (JXPathException ex) { // there is no arguments list. } if (argumentListPtr != null) { JXPathContext argumentListCtx = actionsListCtx.getRelativeContext(argumentListPtr); Double arraySizeArgs = (Double) argumentListCtx.getValue("count( upnp:argument )"); List<ServiceActionArgument> orderedActionArguments = new ArrayList<ServiceActionArgument>(); for (int zed = 1; zed <= arraySizeArgs.intValue(); zed++) { ServiceActionArgument arg = new ServiceActionArgument(); arg.name = (String) argumentListCtx.getValue("upnp:argument[" + zed + "]/upnp:name"); String direction = (String) argumentListCtx.getValue("upnp:argument[" + zed + "]/upnp:direction"); arg.direction = direction.equals(ServiceActionArgument.DIRECTION_IN) ? ServiceActionArgument.DIRECTION_IN : ServiceActionArgument.DIRECTION_OUT; String stateVarName = (String) argumentListCtx.getValue("upnp:argument[" + zed + "]/upnp:relatedStateVariable"); ServiceStateVariable stateVar = 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("upnp:serviceStateTable"); JXPathContext serviceStateTableCtx = rootContext.getRelativeContext(serviceStateTablePtr); Double arraySize = (Double) serviceStateTableCtx.getValue("count( upnp:stateVariable )"); UPNPServiceStateVariables = new HashMap<String, ServiceStateVariable>(); for (int idx = 0; idx < arraySize.intValue(); idx++) { ServiceStateVariable srvStateVar = new ServiceStateVariable(); String sendEventsLcl = null; try { sendEventsLcl = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/@upnp: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("upnp:stateVariable[" + idx + "]/upnp:name"); srvStateVar.dataType = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/upnp:dataType"); try { srvStateVar.defaultValue = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/upnp:defaultValue"); } catch (JXPathException defEx) { // can happend since default value is not } Pointer allowedValuesPtr = null; try { allowedValuesPtr = serviceStateTableCtx.getPointer("upnp:stateVariable[" + idx + "]/upnp:allowedValueList"); } catch (JXPathException ex) { // there is no allowed values list. } if (allowedValuesPtr != null) { JXPathContext allowedValuesCtx = serviceStateTableCtx.getRelativeContext(allowedValuesPtr); Double arraySizeAllowed = (Double) allowedValuesCtx.getValue("count( upnp:allowedValue )"); srvStateVar.allowedvalues = new HashSet<String>(); for (int zed = 1; zed <= arraySizeAllowed.intValue(); zed++) { String allowedValue = (String) allowedValuesCtx.getValue("upnp:allowedValue[" + zed + "]"); srvStateVar.allowedvalues.add(allowedValue); } } Pointer allowedValueRangePtr = null; try { allowedValueRangePtr = serviceStateTableCtx.getPointer("upnp:stateVariable[" + idx + "]/upnp:allowedValueRange"); } catch (JXPathException ex) { // there is no allowed values list, can happen } if (allowedValueRangePtr != null) { srvStateVar.minimumRangeValue = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/upnp:allowedValueRange/upnp:minimum"); srvStateVar.maximumRangeValue = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/upnp:allowedValueRange/upnp:maximum"); try { srvStateVar.stepRangeValue = (String) serviceStateTableCtx.getValue("upnp:stateVariable[" + idx + "]/upnp:allowedValueRange/upnp: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; } }