1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.transport.impl;
17
18 import org.fourthline.cling.model.Constants;
19 import org.fourthline.cling.model.XMLUtil;
20 import org.fourthline.cling.model.action.ActionArgumentValue;
21 import org.fourthline.cling.model.action.ActionException;
22 import org.fourthline.cling.model.action.ActionInvocation;
23 import org.fourthline.cling.model.message.control.ActionMessage;
24 import org.fourthline.cling.model.message.control.ActionRequestMessage;
25 import org.fourthline.cling.model.message.control.ActionResponseMessage;
26 import org.fourthline.cling.model.meta.ActionArgument;
27 import org.fourthline.cling.model.types.ErrorCode;
28 import org.fourthline.cling.model.types.InvalidValueException;
29 import org.fourthline.cling.transport.spi.SOAPActionProcessor;
30 import org.fourthline.cling.model.UnsupportedDataException;
31 import org.w3c.dom.Attr;
32 import org.w3c.dom.Document;
33 import org.w3c.dom.Element;
34 import org.w3c.dom.Node;
35 import org.w3c.dom.NodeList;
36 import org.xml.sax.ErrorHandler;
37 import org.xml.sax.InputSource;
38 import org.xml.sax.SAXException;
39 import org.xml.sax.SAXParseException;
40
41 import javax.xml.parsers.DocumentBuilder;
42 import javax.xml.parsers.DocumentBuilderFactory;
43 import javax.xml.parsers.FactoryConfigurationError;
44
45 import java.io.StringReader;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 import java.util.logging.Level;
50 import java.util.logging.Logger;
51
52
53
54
55
56
57 public class SOAPActionProcessorImpl implements SOAPActionProcessor, ErrorHandler {
58
59 private static Logger log = Logger.getLogger(SOAPActionProcessor.class.getName());
60
61 protected DocumentBuilderFactory createDocumentBuilderFactory() throws FactoryConfigurationError {
62 return DocumentBuilderFactory.newInstance();
63 }
64
65 public void writeBody(ActionRequestMessage requestMessage, ActionInvocation actionInvocation) throws UnsupportedDataException {
66
67 log.fine("Writing body of " + requestMessage + " for: " + actionInvocation);
68
69 try {
70
71 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
72 factory.setNamespaceAware(true);
73 Document d = factory.newDocumentBuilder().newDocument();
74 Element body = writeBodyElement(d);
75
76 writeBodyRequest(d, body, requestMessage, actionInvocation);
77
78 if (log.isLoggable(Level.FINER)) {
79 log.finer("===================================== SOAP BODY BEGIN ============================================");
80 log.finer(requestMessage.getBodyString());
81 log.finer("-===================================== SOAP BODY END ============================================");
82 }
83
84 } catch (Exception ex) {
85 throw new UnsupportedDataException("Can't transform message payload: " + ex, ex);
86 }
87 }
88
89 public void writeBody(ActionResponseMessage responseMessage, ActionInvocation actionInvocation) throws UnsupportedDataException {
90
91 log.fine("Writing body of " + responseMessage + " for: " + actionInvocation);
92
93 try {
94
95 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
96 factory.setNamespaceAware(true);
97 Document d = factory.newDocumentBuilder().newDocument();
98 Element body = writeBodyElement(d);
99
100 if (actionInvocation.getFailure() != null) {
101 writeBodyFailure(d, body, responseMessage, actionInvocation);
102 } else {
103 writeBodyResponse(d, body, responseMessage, actionInvocation);
104 }
105
106 if (log.isLoggable(Level.FINER)) {
107 log.finer("===================================== SOAP BODY BEGIN ============================================");
108 log.finer(responseMessage.getBodyString());
109 log.finer("-===================================== SOAP BODY END ============================================");
110 }
111
112 } catch (Exception ex) {
113 throw new UnsupportedDataException("Can't transform message payload: " + ex, ex);
114 }
115 }
116
117 public void readBody(ActionRequestMessage requestMessage, ActionInvocation actionInvocation) throws UnsupportedDataException {
118
119 log.fine("Reading body of " + requestMessage + " for: " + actionInvocation);
120 if (log.isLoggable(Level.FINER)) {
121 log.finer("===================================== SOAP BODY BEGIN ============================================");
122 log.finer(requestMessage.getBodyString());
123 log.finer("-===================================== SOAP BODY END ============================================");
124 }
125
126 String body = getMessageBody(requestMessage);
127 try {
128
129 DocumentBuilderFactory factory = createDocumentBuilderFactory();
130 factory.setNamespaceAware(true);
131 DocumentBuilder documentBuilder = factory.newDocumentBuilder();
132 documentBuilder.setErrorHandler(this);
133
134 Document d = documentBuilder.parse(new InputSource(new StringReader(body)));
135
136 Element bodyElement = readBodyElement(d);
137
138 readBodyRequest(d, bodyElement, requestMessage, actionInvocation);
139
140 } catch (Exception ex) {
141 throw new UnsupportedDataException("Can't transform message payload: " + ex, ex, body);
142 }
143 }
144
145 public void readBody(ActionResponseMessage responseMsg, ActionInvocation actionInvocation) throws UnsupportedDataException {
146
147 log.fine("Reading body of " + responseMsg + " for: " + actionInvocation);
148 if (log.isLoggable(Level.FINER)) {
149 log.finer("===================================== SOAP BODY BEGIN ============================================");
150 log.finer(responseMsg.getBodyString());
151 log.finer("-===================================== SOAP BODY END ============================================");
152 }
153
154 String body = getMessageBody(responseMsg);
155 try {
156
157 DocumentBuilderFactory factory = createDocumentBuilderFactory();
158 factory.setNamespaceAware(true);
159 DocumentBuilder documentBuilder = factory.newDocumentBuilder();
160 documentBuilder.setErrorHandler(this);
161
162 Document d = documentBuilder.parse(new InputSource(new StringReader(body)));
163
164 Element bodyElement = readBodyElement(d);
165
166 ActionException failure = readBodyFailure(d, bodyElement);
167
168 if (failure == null) {
169 readBodyResponse(d, bodyElement, responseMsg, actionInvocation);
170 } else {
171 actionInvocation.setFailure(failure);
172 }
173
174 } catch (Exception ex) {
175 throw new UnsupportedDataException("Can't transform message payload: " + ex, ex, body);
176 }
177 }
178
179
180
181 protected void writeBodyFailure(Document d,
182 Element bodyElement,
183 ActionResponseMessage message,
184 ActionInvocation actionInvocation) throws Exception {
185
186 writeFaultElement(d, bodyElement, actionInvocation);
187 message.setBody(toString(d));
188 }
189
190 protected void writeBodyRequest(Document d,
191 Element bodyElement,
192 ActionRequestMessage message,
193 ActionInvocation actionInvocation) throws Exception {
194
195 Element actionRequestElement = writeActionRequestElement(d, bodyElement, message, actionInvocation);
196 writeActionInputArguments(d, actionRequestElement, actionInvocation);
197 message.setBody(toString(d));
198
199 }
200
201 protected void writeBodyResponse(Document d,
202 Element bodyElement,
203 ActionResponseMessage message,
204 ActionInvocation actionInvocation) throws Exception {
205
206 Element actionResponseElement = writeActionResponseElement(d, bodyElement, message, actionInvocation);
207 writeActionOutputArguments(d, actionResponseElement, actionInvocation);
208 message.setBody(toString(d));
209 }
210
211 protected ActionException readBodyFailure(Document d, Element bodyElement) throws Exception {
212 return readFaultElement(bodyElement);
213 }
214
215 protected void readBodyRequest(Document d,
216 Element bodyElement,
217 ActionRequestMessage message,
218 ActionInvocation actionInvocation) throws Exception {
219
220 Element actionRequestElement = readActionRequestElement(bodyElement, message, actionInvocation);
221 readActionInputArguments(actionRequestElement, actionInvocation);
222 }
223
224 protected void readBodyResponse(Document d,
225 Element bodyElement,
226 ActionResponseMessage message,
227 ActionInvocation actionInvocation) throws Exception {
228
229 Element actionResponse = readActionResponseElement(bodyElement, actionInvocation);
230 readActionOutputArguments(actionResponse, actionInvocation);
231 }
232
233
234
235 protected Element writeBodyElement(Document d) {
236
237 Element envelopeElement = d.createElementNS(Constants.SOAP_NS_ENVELOPE, "s:Envelope");
238 Attr encodingStyleAttr = d.createAttributeNS(Constants.SOAP_NS_ENVELOPE, "s:encodingStyle");
239 encodingStyleAttr.setValue(Constants.SOAP_URI_ENCODING_STYLE);
240 envelopeElement.setAttributeNode(encodingStyleAttr);
241 d.appendChild(envelopeElement);
242
243 Element bodyElement = d.createElementNS(Constants.SOAP_NS_ENVELOPE, "s:Body");
244 envelopeElement.appendChild(bodyElement);
245
246 return bodyElement;
247 }
248
249 protected Element readBodyElement(Document d) {
250
251 Element envelopeElement = d.getDocumentElement();
252
253 if (envelopeElement == null || !getUnprefixedNodeName(envelopeElement).equals("Envelope")) {
254 throw new RuntimeException("Response root element was not 'Envelope'");
255 }
256
257 NodeList envelopeElementChildren = envelopeElement.getChildNodes();
258 for (int i = 0; i < envelopeElementChildren.getLength(); i++) {
259 Node envelopeChild = envelopeElementChildren.item(i);
260
261 if (envelopeChild.getNodeType() != Node.ELEMENT_NODE)
262 continue;
263
264 if (getUnprefixedNodeName(envelopeChild).equals("Body")) {
265 return (Element) envelopeChild;
266 }
267 }
268
269 throw new RuntimeException("Response envelope did not contain 'Body' child element");
270 }
271
272
273
274 protected Element writeActionRequestElement(Document d,
275 Element bodyElement,
276 ActionRequestMessage message,
277 ActionInvocation actionInvocation) {
278
279 log.fine("Writing action request element: " + actionInvocation.getAction().getName());
280
281 Element actionRequestElement = d.createElementNS(
282 message.getActionNamespace(),
283 "u:" + actionInvocation.getAction().getName()
284 );
285 bodyElement.appendChild(actionRequestElement);
286
287 return actionRequestElement;
288 }
289
290 protected Element readActionRequestElement(Element bodyElement,
291 ActionRequestMessage message,
292 ActionInvocation actionInvocation) {
293 NodeList bodyChildren = bodyElement.getChildNodes();
294
295 log.fine("Looking for action request element matching namespace:" + message.getActionNamespace());
296
297 for (int i = 0; i < bodyChildren.getLength(); i++) {
298 Node bodyChild = bodyChildren.item(i);
299
300 if (bodyChild.getNodeType() != Node.ELEMENT_NODE)
301 continue;
302
303 String unprefixedName = getUnprefixedNodeName(bodyChild);
304 if (unprefixedName.equals(actionInvocation.getAction().getName())) {
305 if (bodyChild.getNamespaceURI() == null
306 || !bodyChild.getNamespaceURI().equals(message.getActionNamespace()))
307 throw new UnsupportedDataException(
308 "Illegal or missing namespace on action request element: " + bodyChild
309 );
310 log.fine("Reading action request element: " + unprefixedName);
311 return (Element) bodyChild;
312 }
313 }
314 throw new UnsupportedDataException(
315 "Could not read action request element matching namespace: " + message.getActionNamespace()
316 );
317 }
318
319
320
321 protected Element writeActionResponseElement(Document d,
322 Element bodyElement,
323 ActionResponseMessage message,
324 ActionInvocation actionInvocation) {
325
326 log.fine("Writing action response element: " + actionInvocation.getAction().getName());
327 Element actionResponseElement = d.createElementNS(
328 message.getActionNamespace(),
329 "u:" + actionInvocation.getAction().getName() + "Response"
330 );
331 bodyElement.appendChild(actionResponseElement);
332
333 return actionResponseElement;
334 }
335
336 protected Element readActionResponseElement(Element bodyElement, ActionInvocation actionInvocation) {
337 NodeList bodyChildren = bodyElement.getChildNodes();
338
339 for (int i = 0; i < bodyChildren.getLength(); i++) {
340 Node bodyChild = bodyChildren.item(i);
341
342 if (bodyChild.getNodeType() != Node.ELEMENT_NODE)
343 continue;
344
345 if (getUnprefixedNodeName(bodyChild).equals(actionInvocation.getAction().getName() + "Response")) {
346 log.fine("Reading action response element: " + getUnprefixedNodeName(bodyChild));
347 return (Element) bodyChild;
348 }
349 }
350 log.fine("Could not read action response element");
351 return null;
352 }
353
354
355
356 protected void writeActionInputArguments(Document d,
357 Element actionRequestElement,
358 ActionInvocation actionInvocation) {
359
360 for (ActionArgument argument : actionInvocation.getAction().getInputArguments()) {
361 log.fine("Writing action input argument: " + argument.getName());
362 String value = actionInvocation.getInput(argument) != null ? actionInvocation.getInput(argument).toString() : "";
363 XMLUtil.appendNewElement(d, actionRequestElement, argument.getName(), value);
364 }
365 }
366
367 public void readActionInputArguments(Element actionRequestElement,
368 ActionInvocation actionInvocation) throws ActionException {
369 actionInvocation.setInput(
370 readArgumentValues(
371 actionRequestElement.getChildNodes(),
372 actionInvocation.getAction().getInputArguments()
373 )
374 );
375 }
376
377
378
379 protected void writeActionOutputArguments(Document d,
380 Element actionResponseElement,
381 ActionInvocation actionInvocation) {
382
383 for (ActionArgument argument : actionInvocation.getAction().getOutputArguments()) {
384 log.fine("Writing action output argument: " + argument.getName());
385 String value = actionInvocation.getOutput(argument) != null ? actionInvocation.getOutput(argument).toString() : "";
386 XMLUtil.appendNewElement(d, actionResponseElement, argument.getName(), value);
387 }
388 }
389
390 protected void readActionOutputArguments(Element actionResponseElement,
391 ActionInvocation actionInvocation) throws ActionException {
392
393 actionInvocation.setOutput(
394 readArgumentValues(
395 actionResponseElement.getChildNodes(),
396 actionInvocation.getAction().getOutputArguments()
397 )
398 );
399 }
400
401
402
403 protected void writeFaultElement(Document d, Element bodyElement, ActionInvocation actionInvocation) {
404
405 Element faultElement = d.createElementNS(Constants.SOAP_NS_ENVELOPE, "s:Fault");
406 bodyElement.appendChild(faultElement);
407
408
409 XMLUtil.appendNewElement(d, faultElement, "faultcode", "s:Client");
410 XMLUtil.appendNewElement(d, faultElement, "faultstring", "UPnPError");
411
412 Element detailElement = d.createElement("detail");
413 faultElement.appendChild(detailElement);
414
415 Element upnpErrorElement = d.createElementNS(Constants.NS_UPNP_CONTROL_10, "UPnPError");
416 detailElement.appendChild(upnpErrorElement);
417
418 int errorCode = actionInvocation.getFailure().getErrorCode();
419 String errorDescription = actionInvocation.getFailure().getMessage();
420
421 log.fine("Writing fault element: " + errorCode + " - " + errorDescription);
422
423 XMLUtil.appendNewElement(d, upnpErrorElement, "errorCode", Integer.toString(errorCode));
424 XMLUtil.appendNewElement(d, upnpErrorElement, "errorDescription", errorDescription);
425
426 }
427
428 protected ActionException readFaultElement(Element bodyElement) {
429
430 boolean receivedFaultElement = false;
431 String errorCode = null;
432 String errorDescription = null;
433
434 NodeList bodyChildren = bodyElement.getChildNodes();
435
436 for (int i = 0; i < bodyChildren.getLength(); i++) {
437 Node bodyChild = bodyChildren.item(i);
438
439 if (bodyChild.getNodeType() != Node.ELEMENT_NODE)
440 continue;
441
442 if (getUnprefixedNodeName(bodyChild).equals("Fault")) {
443
444 receivedFaultElement = true;
445
446 NodeList faultChildren = bodyChild.getChildNodes();
447
448 for (int j = 0; j < faultChildren.getLength(); j++) {
449 Node faultChild = faultChildren.item(j);
450
451 if (faultChild.getNodeType() != Node.ELEMENT_NODE)
452 continue;
453
454 if (getUnprefixedNodeName(faultChild).equals("detail")) {
455
456 NodeList detailChildren = faultChild.getChildNodes();
457 for (int x = 0; x < detailChildren.getLength(); x++) {
458 Node detailChild = detailChildren.item(x);
459
460 if (detailChild.getNodeType() != Node.ELEMENT_NODE)
461 continue;
462
463 if (getUnprefixedNodeName(detailChild).equals("UPnPError")) {
464
465 NodeList errorChildren = detailChild.getChildNodes();
466 for (int y = 0; y < errorChildren.getLength(); y++) {
467 Node errorChild = errorChildren.item(y);
468
469 if (errorChild.getNodeType() != Node.ELEMENT_NODE)
470 continue;
471
472 if (getUnprefixedNodeName(errorChild).equals("errorCode"))
473 errorCode = XMLUtil.getTextContent(errorChild);
474
475 if (getUnprefixedNodeName(errorChild).equals("errorDescription"))
476 errorDescription = XMLUtil.getTextContent(errorChild);
477 }
478 }
479 }
480 }
481 }
482 }
483 }
484
485 if (errorCode != null) {
486 try {
487 int numericCode = Integer.valueOf(errorCode);
488 ErrorCode standardErrorCode = ErrorCode.getByCode(numericCode);
489 if (standardErrorCode != null) {
490 log.fine("Reading fault element: " + standardErrorCode.getCode() + " - " + errorDescription);
491 return new ActionException(standardErrorCode, errorDescription, false);
492 } else {
493 log.fine("Reading fault element: " + numericCode + " - " + errorDescription);
494 return new ActionException(numericCode, errorDescription);
495 }
496 } catch (NumberFormatException ex) {
497 throw new RuntimeException("Error code was not a number");
498 }
499 } else if (receivedFaultElement) {
500 throw new RuntimeException("Received fault element but no error code");
501 }
502 return null;
503 }
504
505
506
507
508 protected String getMessageBody(ActionMessage message) throws UnsupportedDataException {
509 if (!message.isBodyNonEmptyString())
510 throw new UnsupportedDataException(
511 "Can't transform null or non-string/zero-length body of: " + message
512 );
513 return message.getBodyString().trim();
514 }
515
516 protected String toString(Document d) throws Exception {
517
518 String output = XMLUtil.documentToString(d);
519 while (output.endsWith("\n") || output.endsWith("\r")) {
520 output = output.substring(0, output.length() - 1);
521 }
522
523 return output;
524 }
525
526 protected String getUnprefixedNodeName(Node node) {
527 return node.getPrefix() != null
528 ? node.getNodeName().substring(node.getPrefix().length() + 1)
529 : node.getNodeName();
530 }
531
532
533
534
535
536
537
538 protected ActionArgumentValue[] readArgumentValues(NodeList nodeList, ActionArgument[] args)
539 throws ActionException {
540
541 List<Node> nodes = getMatchingNodes(nodeList, args);
542
543 ActionArgumentValue[] values = new ActionArgumentValue[args.length];
544
545 for (int i = 0; i < args.length; i++) {
546
547 ActionArgument arg = args[i];
548 Node node = findActionArgumentNode(nodes, arg);
549 if(node == null) {
550 throw new ActionException(
551 ErrorCode.ARGUMENT_VALUE_INVALID,
552 "Could not find argument '" + arg.getName() + "' node");
553 }
554 log.fine("Reading action argument: " + arg.getName());
555 String value = XMLUtil.getTextContent(node);
556 values[i] = createValue(arg, value);
557 }
558 return values;
559 }
560
561
562
563
564
565 protected List<Node> getMatchingNodes(NodeList nodeList, ActionArgument[] args) throws ActionException {
566
567 List<String> names = new ArrayList<>();
568 for (ActionArgument argument : args) {
569 names.add(argument.getName());
570 names.addAll(Arrays.asList(argument.getAliases()));
571 }
572
573 List<Node> matches = new ArrayList<>();
574 for (int i = 0; i < nodeList.getLength(); i++) {
575 Node child = nodeList.item(i);
576
577 if (child.getNodeType() != Node.ELEMENT_NODE)
578 continue;
579
580 if (names.contains(getUnprefixedNodeName(child)))
581 matches.add(child);
582 }
583
584 if (matches.size() < args.length) {
585 throw new ActionException(
586 ErrorCode.ARGUMENT_VALUE_INVALID,
587 "Invalid number of input or output arguments in XML message, expected " + args.length + " but found " + matches.size()
588 );
589 }
590 return matches;
591 }
592
593
594
595
596
597
598 protected ActionArgumentValue createValue(ActionArgument arg, String value) throws ActionException {
599 try {
600 return new ActionArgumentValue(arg, value);
601 } catch (InvalidValueException ex) {
602 throw new ActionException(
603 ErrorCode.ARGUMENT_VALUE_INVALID,
604 "Wrong type or invalid value for '" + arg.getName() + "': " + ex.getMessage(),
605 ex
606 );
607 }
608 }
609
610
611
612
613
614 protected Node findActionArgumentNode(List<Node> nodes, ActionArgument arg) {
615 for(Node node : nodes) {
616 if(arg.isNameOrAlias(getUnprefixedNodeName(node))) return node;
617 }
618 return null;
619 }
620
621 public void warning(SAXParseException e) throws SAXException {
622 log.warning(e.toString());
623 }
624
625 public void error(SAXParseException e) throws SAXException {
626 throw e;
627 }
628
629 public void fatalError(SAXParseException e) throws SAXException {
630 throw e;
631 }
632 }