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 }