1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.binding.xml;
17
18 import org.fourthline.cling.binding.staging.MutableAction;
19 import org.fourthline.cling.binding.staging.MutableActionArgument;
20 import org.fourthline.cling.binding.staging.MutableAllowedValueRange;
21 import org.fourthline.cling.binding.staging.MutableService;
22 import org.fourthline.cling.binding.staging.MutableStateVariable;
23 import org.fourthline.cling.model.ValidationException;
24 import org.fourthline.cling.model.XMLUtil;
25 import org.fourthline.cling.model.meta.Action;
26 import org.fourthline.cling.model.meta.ActionArgument;
27 import org.fourthline.cling.model.meta.QueryStateVariableAction;
28 import org.fourthline.cling.model.meta.RemoteService;
29 import org.fourthline.cling.model.meta.Service;
30 import org.fourthline.cling.model.meta.StateVariable;
31 import org.fourthline.cling.model.meta.StateVariableEventDetails;
32 import org.fourthline.cling.model.types.CustomDatatype;
33 import org.fourthline.cling.model.types.Datatype;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.Element;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.NodeList;
38 import org.xml.sax.ErrorHandler;
39 import org.xml.sax.InputSource;
40 import org.xml.sax.SAXException;
41 import org.xml.sax.SAXParseException;
42
43 import javax.xml.parsers.DocumentBuilder;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import java.io.StringReader;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.logging.Logger;
50
51 import static org.fourthline.cling.binding.xml.Descriptor.Service.ATTRIBUTE;
52 import static org.fourthline.cling.binding.xml.Descriptor.Service.ELEMENT;
53 import static org.fourthline.cling.model.XMLUtil.appendNewElement;
54 import static org.fourthline.cling.model.XMLUtil.appendNewElementIfNotNull;
55
56
57
58
59
60
61 public class UDA10ServiceDescriptorBinderImpl implements ServiceDescriptorBinder, ErrorHandler {
62
63 private static Logger log = Logger.getLogger(ServiceDescriptorBinder.class.getName());
64
65 public <S extends Service> S describe(S undescribedService, String descriptorXml) throws DescriptorBindingException, ValidationException {
66 if (descriptorXml == null || descriptorXml.length() == 0) {
67 throw new DescriptorBindingException("Null or empty descriptor");
68 }
69
70 try {
71 log.fine("Populating service from XML descriptor: " + undescribedService);
72
73 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
74 factory.setNamespaceAware(true);
75 DocumentBuilder documentBuilder = factory.newDocumentBuilder();
76 documentBuilder.setErrorHandler(this);
77
78 Document d = documentBuilder.parse(
79 new InputSource(
80
81 new StringReader(descriptorXml.trim())
82 )
83 );
84
85 return describe(undescribedService, d);
86
87 } catch (ValidationException ex) {
88 throw ex;
89 } catch (Exception ex) {
90 throw new DescriptorBindingException("Could not parse service descriptor: " + ex.toString(), ex);
91 }
92 }
93
94 public <S extends Service> S describe(S undescribedService, Document dom) throws DescriptorBindingException, ValidationException {
95 try {
96 log.fine("Populating service from DOM: " + undescribedService);
97
98
99 MutableService descriptor = new MutableService();
100
101 hydrateBasic(descriptor, undescribedService);
102
103 Element rootElement = dom.getDocumentElement();
104 hydrateRoot(descriptor, rootElement);
105
106
107 return buildInstance(undescribedService, descriptor);
108
109 } catch (ValidationException ex) {
110 throw ex;
111 } catch (Exception ex) {
112 throw new DescriptorBindingException("Could not parse service DOM: " + ex.toString(), ex);
113 }
114 }
115
116 protected <S extends Service> S buildInstance(S undescribedService, MutableService descriptor) throws ValidationException {
117 return (S)descriptor.build(undescribedService.getDevice());
118 }
119
120 protected void hydrateBasic(MutableService descriptor, Service undescribedService) {
121 descriptor.serviceId = undescribedService.getServiceId();
122 descriptor.serviceType = undescribedService.getServiceType();
123 if (undescribedService instanceof RemoteService) {
124 RemoteService rs = (RemoteService) undescribedService;
125 descriptor.controlURI = rs.getControlURI();
126 descriptor.eventSubscriptionURI = rs.getEventSubscriptionURI();
127 descriptor.descriptorURI = rs.getDescriptorURI();
128 }
129 }
130
131 protected void hydrateRoot(MutableService descriptor, Element rootElement)
132 throws DescriptorBindingException {
133
134
135
136 if (!ELEMENT.scpd.equals(rootElement)) {
137 throw new DescriptorBindingException("Root element name is not <scpd>: " + rootElement.getNodeName());
138 }
139
140 NodeList rootChildren = rootElement.getChildNodes();
141
142 for (int i = 0; i < rootChildren.getLength(); i++) {
143 Node rootChild = rootChildren.item(i);
144
145 if (rootChild.getNodeType() != Node.ELEMENT_NODE)
146 continue;
147
148 if (ELEMENT.specVersion.equals(rootChild)) {
149
150
151
152
153 } else if (ELEMENT.actionList.equals(rootChild)) {
154 hydrateActionList(descriptor, rootChild);
155 } else if (ELEMENT.serviceStateTable.equals(rootChild)) {
156 hydrateServiceStateTableList(descriptor, rootChild);
157 } else {
158 log.finer("Ignoring unknown element: " + rootChild.getNodeName());
159 }
160 }
161
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 public void hydrateActionList(MutableService descriptor, Node actionListNode) throws DescriptorBindingException {
186
187 NodeList actionListChildren = actionListNode.getChildNodes();
188 for (int i = 0; i < actionListChildren.getLength(); i++) {
189 Node actionListChild = actionListChildren.item(i);
190
191 if (actionListChild.getNodeType() != Node.ELEMENT_NODE)
192 continue;
193
194 if (ELEMENT.action.equals(actionListChild)) {
195 MutableAction action = new MutableAction();
196 hydrateAction(action, actionListChild);
197 descriptor.actions.add(action);
198 }
199 }
200 }
201
202 public void hydrateAction(MutableAction action, Node actionNode) {
203
204 NodeList actionNodeChildren = actionNode.getChildNodes();
205 for (int i = 0; i < actionNodeChildren.getLength(); i++) {
206 Node actionNodeChild = actionNodeChildren.item(i);
207
208 if (actionNodeChild.getNodeType() != Node.ELEMENT_NODE)
209 continue;
210
211 if (ELEMENT.name.equals(actionNodeChild)) {
212 action.name = XMLUtil.getTextContent(actionNodeChild);
213 } else if (ELEMENT.argumentList.equals(actionNodeChild)) {
214
215
216 NodeList argumentChildren = actionNodeChild.getChildNodes();
217 for (int j = 0; j < argumentChildren.getLength(); j++) {
218 Node argumentChild = argumentChildren.item(j);
219
220 if (argumentChild.getNodeType() != Node.ELEMENT_NODE)
221 continue;
222
223 MutableActionArgument actionArgument = new MutableActionArgument();
224 hydrateActionArgument(actionArgument, argumentChild);
225 action.arguments.add(actionArgument);
226 }
227 }
228 }
229
230 }
231
232 public void hydrateActionArgument(MutableActionArgument actionArgument, Node actionArgumentNode) {
233
234 NodeList argumentNodeChildren = actionArgumentNode.getChildNodes();
235 for (int i = 0; i < argumentNodeChildren.getLength(); i++) {
236 Node argumentNodeChild = argumentNodeChildren.item(i);
237
238 if (argumentNodeChild.getNodeType() != Node.ELEMENT_NODE)
239 continue;
240
241 if (ELEMENT.name.equals(argumentNodeChild)) {
242 actionArgument.name = XMLUtil.getTextContent(argumentNodeChild);
243 } else if (ELEMENT.direction.equals(argumentNodeChild)) {
244 String directionString = XMLUtil.getTextContent(argumentNodeChild);
245 try {
246 actionArgument.direction = ActionArgument.Direction.valueOf(directionString.toUpperCase(Locale.ROOT));
247 } catch (IllegalArgumentException ex) {
248
249 log.warning("UPnP specification violation: Invalid action argument direction, assuming 'IN': " + directionString);
250 actionArgument.direction = ActionArgument.Direction.IN;
251 }
252 } else if (ELEMENT.relatedStateVariable.equals(argumentNodeChild)) {
253 actionArgument.relatedStateVariable = XMLUtil.getTextContent(argumentNodeChild);
254 } else if (ELEMENT.retval.equals(argumentNodeChild)) {
255 actionArgument.retval = true;
256 }
257 }
258 }
259
260 public void hydrateServiceStateTableList(MutableService descriptor, Node serviceStateTableNode) {
261
262 NodeList serviceStateTableChildren = serviceStateTableNode.getChildNodes();
263 for (int i = 0; i < serviceStateTableChildren.getLength(); i++) {
264 Node serviceStateTableChild = serviceStateTableChildren.item(i);
265
266 if (serviceStateTableChild.getNodeType() != Node.ELEMENT_NODE)
267 continue;
268
269 if (ELEMENT.stateVariable.equals(serviceStateTableChild)) {
270 MutableStateVariable stateVariable = new MutableStateVariable();
271 hydrateStateVariable(stateVariable, (Element) serviceStateTableChild);
272 descriptor.stateVariables.add(stateVariable);
273 }
274 }
275 }
276
277 public void hydrateStateVariable(MutableStateVariable stateVariable, Element stateVariableElement) {
278
279 stateVariable.eventDetails = new StateVariableEventDetails(
280 stateVariableElement.getAttribute("sendEvents") != null &&
281 stateVariableElement.getAttribute(ATTRIBUTE.sendEvents.toString()).toUpperCase(Locale.ROOT).equals("YES")
282 );
283
284 NodeList stateVariableChildren = stateVariableElement.getChildNodes();
285 for (int i = 0; i < stateVariableChildren.getLength(); i++) {
286 Node stateVariableChild = stateVariableChildren.item(i);
287
288 if (stateVariableChild.getNodeType() != Node.ELEMENT_NODE)
289 continue;
290
291 if (ELEMENT.name.equals(stateVariableChild)) {
292 stateVariable.name = XMLUtil.getTextContent(stateVariableChild);
293 } else if (ELEMENT.dataType.equals(stateVariableChild)) {
294 String dtName = XMLUtil.getTextContent(stateVariableChild);
295 Datatype.Builtin builtin = Datatype.Builtin.getByDescriptorName(dtName);
296 stateVariable.dataType = builtin != null ? builtin.getDatatype() : new CustomDatatype(dtName);
297 } else if (ELEMENT.defaultValue.equals(stateVariableChild)) {
298 stateVariable.defaultValue = XMLUtil.getTextContent(stateVariableChild);
299 } else if (ELEMENT.allowedValueList.equals(stateVariableChild)) {
300
301 List<String> allowedValues = new ArrayList<>();
302
303 NodeList allowedValueListChildren = stateVariableChild.getChildNodes();
304 for (int j = 0; j < allowedValueListChildren.getLength(); j++) {
305 Node allowedValueListChild = allowedValueListChildren.item(j);
306
307 if (allowedValueListChild.getNodeType() != Node.ELEMENT_NODE)
308 continue;
309
310 if (ELEMENT.allowedValue.equals(allowedValueListChild))
311 allowedValues.add(XMLUtil.getTextContent(allowedValueListChild));
312 }
313
314 stateVariable.allowedValues = allowedValues;
315
316 } else if (ELEMENT.allowedValueRange.equals(stateVariableChild)) {
317
318 MutableAllowedValueRange range = new MutableAllowedValueRange();
319
320 NodeList allowedValueRangeChildren = stateVariableChild.getChildNodes();
321 for (int j = 0; j < allowedValueRangeChildren.getLength(); j++) {
322 Node allowedValueRangeChild = allowedValueRangeChildren.item(j);
323
324 if (allowedValueRangeChild.getNodeType() != Node.ELEMENT_NODE)
325 continue;
326
327 if (ELEMENT.minimum.equals(allowedValueRangeChild)) {
328 try {
329 range.minimum = Long.valueOf(XMLUtil.getTextContent(allowedValueRangeChild));
330 } catch (Exception ex) {
331 }
332 } else if (ELEMENT.maximum.equals(allowedValueRangeChild)) {
333 try {
334 range.maximum = Long.valueOf(XMLUtil.getTextContent(allowedValueRangeChild));
335 } catch (Exception ex) {
336 }
337 } else if (ELEMENT.step.equals(allowedValueRangeChild)) {
338 try {
339 range.step = Long.valueOf(XMLUtil.getTextContent(allowedValueRangeChild));
340 } catch (Exception ex) {
341 }
342 }
343 }
344
345 stateVariable.allowedValueRange = range;
346 }
347 }
348 }
349
350 public String generate(Service service) throws DescriptorBindingException {
351 try {
352 log.fine("Generating XML descriptor from service model: " + service);
353
354 return XMLUtil.documentToString(buildDOM(service));
355
356 } catch (Exception ex) {
357 throw new DescriptorBindingException("Could not build DOM: " + ex.getMessage(), ex);
358 }
359 }
360
361 public Document buildDOM(Service service) throws DescriptorBindingException {
362
363 try {
364 log.fine("Generating XML descriptor from service model: " + service);
365
366 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
367 factory.setNamespaceAware(true);
368
369 Document d = factory.newDocumentBuilder().newDocument();
370 generateScpd(service, d);
371
372 return d;
373
374 } catch (Exception ex) {
375 throw new DescriptorBindingException("Could not generate service descriptor: " + ex.getMessage(), ex);
376 }
377 }
378
379 private void generateScpd(Service serviceModel, Document descriptor) {
380
381 Element scpdElement = descriptor.createElementNS(Descriptor.Service.NAMESPACE_URI, ELEMENT.scpd.toString());
382 descriptor.appendChild(scpdElement);
383
384 generateSpecVersion(serviceModel, descriptor, scpdElement);
385 if (serviceModel.hasActions()) {
386 generateActionList(serviceModel, descriptor, scpdElement);
387 }
388 generateServiceStateTable(serviceModel, descriptor, scpdElement);
389 }
390
391 private void generateSpecVersion(Service serviceModel, Document descriptor, Element rootElement) {
392 Element specVersionElement = appendNewElement(descriptor, rootElement, ELEMENT.specVersion);
393 appendNewElementIfNotNull(descriptor, specVersionElement, ELEMENT.major, serviceModel.getDevice().getVersion().getMajor());
394 appendNewElementIfNotNull(descriptor, specVersionElement, ELEMENT.minor, serviceModel.getDevice().getVersion().getMinor());
395 }
396
397 private void generateActionList(Service serviceModel, Document descriptor, Element scpdElement) {
398
399 Element actionListElement = appendNewElement(descriptor, scpdElement, ELEMENT.actionList);
400
401 for (Action action : serviceModel.getActions()) {
402 if (!action.getName().equals(QueryStateVariableAction.ACTION_NAME))
403 generateAction(action, descriptor, actionListElement);
404 }
405 }
406
407 private void generateAction(Action action, Document descriptor, Element actionListElement) {
408
409 Element actionElement = appendNewElement(descriptor, actionListElement, ELEMENT.action);
410
411 appendNewElementIfNotNull(descriptor, actionElement, ELEMENT.name, action.getName());
412
413 if (action.hasArguments()) {
414 Element argumentListElement = appendNewElement(descriptor, actionElement, ELEMENT.argumentList);
415 for (ActionArgument actionArgument : action.getArguments()) {
416 generateActionArgument(actionArgument, descriptor, argumentListElement);
417 }
418 }
419 }
420
421 private void generateActionArgument(ActionArgument actionArgument, Document descriptor, Element actionElement) {
422
423 Element actionArgumentElement = appendNewElement(descriptor, actionElement, ELEMENT.argument);
424
425 appendNewElementIfNotNull(descriptor, actionArgumentElement, ELEMENT.name, actionArgument.getName());
426 appendNewElementIfNotNull(descriptor, actionArgumentElement, ELEMENT.direction, actionArgument.getDirection().toString().toLowerCase(Locale.ROOT));
427 if (actionArgument.isReturnValue()) {
428
429 log.warning("UPnP specification violation: Not producing <retval> element to be compatible with WMP12: " + actionArgument);
430
431 }
432 appendNewElementIfNotNull(descriptor, actionArgumentElement, ELEMENT.relatedStateVariable, actionArgument.getRelatedStateVariableName());
433 }
434
435 private void generateServiceStateTable(Service serviceModel, Document descriptor, Element scpdElement) {
436
437 Element serviceStateTableElement = appendNewElement(descriptor, scpdElement, ELEMENT.serviceStateTable);
438
439 for (StateVariable stateVariable : serviceModel.getStateVariables()) {
440 generateStateVariable(stateVariable, descriptor, serviceStateTableElement);
441 }
442 }
443
444 private void generateStateVariable(StateVariable stateVariable, Document descriptor, Element serviveStateTableElement) {
445
446 Element stateVariableElement = appendNewElement(descriptor, serviveStateTableElement, ELEMENT.stateVariable);
447
448 appendNewElementIfNotNull(descriptor, stateVariableElement, ELEMENT.name, stateVariable.getName());
449
450 if (stateVariable.getTypeDetails().getDatatype() instanceof CustomDatatype) {
451 appendNewElementIfNotNull(descriptor, stateVariableElement, ELEMENT.dataType,
452 ((CustomDatatype)stateVariable.getTypeDetails().getDatatype()).getName());
453 } else {
454 appendNewElementIfNotNull(descriptor, stateVariableElement, ELEMENT.dataType,
455 stateVariable.getTypeDetails().getDatatype().getBuiltin().getDescriptorName());
456 }
457
458 appendNewElementIfNotNull(descriptor, stateVariableElement, ELEMENT.defaultValue,
459 stateVariable.getTypeDetails().getDefaultValue());
460
461
462 if (stateVariable.getEventDetails().isSendEvents()) {
463 stateVariableElement.setAttribute(ATTRIBUTE.sendEvents.toString(), "yes");
464 } else {
465 stateVariableElement.setAttribute(ATTRIBUTE.sendEvents.toString(), "no");
466 }
467
468 if (stateVariable.getTypeDetails().getAllowedValues() != null) {
469 Element allowedValueListElement = appendNewElement(descriptor, stateVariableElement, ELEMENT.allowedValueList);
470 for (String allowedValue : stateVariable.getTypeDetails().getAllowedValues()) {
471 appendNewElementIfNotNull(descriptor, allowedValueListElement, ELEMENT.allowedValue, allowedValue);
472 }
473 }
474
475 if (stateVariable.getTypeDetails().getAllowedValueRange() != null) {
476 Element allowedValueRangeElement = appendNewElement(descriptor, stateVariableElement, ELEMENT.allowedValueRange);
477 appendNewElementIfNotNull(
478 descriptor, allowedValueRangeElement, ELEMENT.minimum, stateVariable.getTypeDetails().getAllowedValueRange().getMinimum()
479 );
480 appendNewElementIfNotNull(
481 descriptor, allowedValueRangeElement, ELEMENT.maximum, stateVariable.getTypeDetails().getAllowedValueRange().getMaximum()
482 );
483 if (stateVariable.getTypeDetails().getAllowedValueRange().getStep() >= 1l) {
484 appendNewElementIfNotNull(
485 descriptor, allowedValueRangeElement, ELEMENT.step, stateVariable.getTypeDetails().getAllowedValueRange().getStep()
486 );
487 }
488 }
489
490 }
491
492 public void warning(SAXParseException e) throws SAXException {
493 log.warning(e.toString());
494 }
495
496 public void error(SAXParseException e) throws SAXException {
497 throw e;
498 }
499
500 public void fatalError(SAXParseException e) throws SAXException {
501 throw e;
502 }
503 }
504