View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.transport.impl;
17  
18  import java.util.logging.Logger;
19  
20  import org.fourthline.cling.model.action.ActionInvocation;
21  import org.fourthline.cling.model.message.control.ActionRequestMessage;
22  import org.fourthline.cling.model.message.control.ActionResponseMessage;
23  import org.fourthline.cling.transport.spi.SOAPActionProcessor;
24  import org.fourthline.cling.model.UnsupportedDataException;
25  import org.seamless.xml.XmlPullParserUtils;
26  
27  import javax.enterprise.inject.Alternative;
28  
29  /**
30   * Implementation based on the <em>Xml Pull Parser</em> XML processing API.
31   * <p>
32   * This processor extends {@link PullSOAPActionProcessorImpl}, it will always
33   * first try to read messages regularly with the superclass' methods before
34   * trying to recover from a failure.
35   * </p>
36   * <p>
37   * When the superclass can't read the message, this processor will try to
38   * recover from broken XML by for example, detecting wrongly encoded XML entities,
39   * and working around other vendor-specific bugs caused by incompatible UPnP
40   * stacks in the wild.
41   * </p>
42   * <p>
43   * Additionally any {@link UnsupportedDataException} thrown while reading an
44   * XML message will be passed on to the
45   * {@link #handleInvalidMessage(org.fourthline.cling.model.action.ActionInvocation, org.fourthline.cling.model.UnsupportedDataException, org.fourthline.cling.model.UnsupportedDataException)}
46   * method for you to handle. The default implementation will simply throw the
47   * original exception from the first processing attempt.
48   * </p>
49   *
50   * @author Michael Pujos
51   */
52  @Alternative
53  public class RecoveringSOAPActionProcessorImpl extends PullSOAPActionProcessorImpl {
54  
55      private static Logger log = Logger.getLogger(SOAPActionProcessor.class.getName());
56  
57      public void readBody(ActionRequestMessage requestMessage, ActionInvocation actionInvocation) throws UnsupportedDataException {
58          try {
59              super.readBody(requestMessage, actionInvocation);
60          } catch (UnsupportedDataException ex) {
61  
62              // Can't recover from this
63              if (!requestMessage.isBodyNonEmptyString())
64                  throw ex;
65  
66              log.warning("Trying to recover from invalid SOAP XML request: " + ex);
67              String body = getMessageBody(requestMessage);
68  
69              // TODO: UPNP VIOLATION: TwonkyMobile sends unencoded '&' in SetAVTransportURI action calls:
70              // <CurrentURI>http://192.168.1.14:56923/content/12a470d854dbc6887e4103e3140783fd.wav?profile_id=0&convert=wav</CurrentURI>
71              String fixedBody = XmlPullParserUtils.fixXMLEntities(body);
72  
73              try {
74                  // Try again, if this fails, we are done...
75                  requestMessage.setBody(fixedBody);
76                  super.readBody(requestMessage, actionInvocation);
77              } catch (UnsupportedDataException ex2) {
78                  handleInvalidMessage(actionInvocation, ex, ex2);
79              }
80          }
81      }
82  
83      public void readBody(ActionResponseMessage responseMsg, ActionInvocation actionInvocation) throws UnsupportedDataException {
84          try {
85              super.readBody(responseMsg, actionInvocation);
86          } catch (UnsupportedDataException ex) {
87  
88              // Can't recover from this
89              if (!responseMsg.isBodyNonEmptyString())
90                  throw ex;
91  
92              log.warning("Trying to recover from invalid SOAP XML response: " + ex);
93              String body = getMessageBody(responseMsg);
94  
95              // TODO: UPNP VIOLATION: TwonkyMobile doesn't properly encode '&'
96              String fixedBody = XmlPullParserUtils.fixXMLEntities(body);
97  
98              // TODO: UPNP VIOLATION: YAMAHA NP-S2000 does not terminate XML with </s:Envelope>
99              // (at least for action GetPositionInfo)
100             if (fixedBody.endsWith("</s:Envelop")) {
101                 fixedBody += "e>";
102             }
103 
104             try {
105                 // Try again, if this fails, we are done...
106                 responseMsg.setBody(fixedBody);
107                 super.readBody(responseMsg, actionInvocation);
108             } catch (UnsupportedDataException ex2) {
109                 handleInvalidMessage(actionInvocation, ex, ex2);
110             }
111         }
112     }
113 
114     /**
115      * Handle processing errors while reading of XML messages.
116      *
117      * <p>
118      * Typically you want to log this problem or create an error report, and in any
119      * case, throw an {@link UnsupportedDataException} to notify the caller of the
120      * processor of this failure.
121      * </p>
122      * <p>
123      * You can access the invalid XML with
124      * {@link org.fourthline.cling.model.UnsupportedDataException#getData()}.
125      * </p>
126      *
127      * @param originalException   The original exception throw by the first parsing attempt
128      * @param recoveringException The exception thrown after trying to fix the XML.
129      */
130     protected void handleInvalidMessage(ActionInvocation actionInvocation,
131                                         UnsupportedDataException originalException,
132                                         UnsupportedDataException recoveringException) throws UnsupportedDataException {
133         throw originalException;
134     }
135 }