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.*;
19  import java.util.logging.Logger;
20  
21  import org.fourthline.cling.model.action.ActionArgumentValue;
22  import org.fourthline.cling.model.action.ActionException;
23  import org.fourthline.cling.model.action.ActionInvocation;
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.transport.spi.SOAPActionProcessor;
29  import org.fourthline.cling.model.UnsupportedDataException;
30  import org.seamless.xml.XmlPullParserUtils;
31  import org.xmlpull.v1.XmlPullParser;
32  
33  import javax.enterprise.inject.Alternative;
34  
35  /**
36   * Implementation based on the <em>Xml Pull Parser</em> XML processing API.
37   * <p>
38   * This processor is more lenient with parsing, looking only for the required XML tags.
39   * </p>
40   * <p>
41   * To use this parser you need to install an implementation of the
42   * <a href="http://www.xmlpull.org/impls.shtml">XMLPull API</a>.
43   * </p>
44   *
45   * @author Michael Pujos
46   */
47  @Alternative
48  public class PullSOAPActionProcessorImpl extends SOAPActionProcessorImpl {
49  
50      protected static Logger log = Logger.getLogger(SOAPActionProcessor.class.getName());
51  
52      public void readBody(ActionRequestMessage requestMessage, ActionInvocation actionInvocation) throws UnsupportedDataException {
53          String body = getMessageBody(requestMessage);
54          try {
55              XmlPullParser xpp = XmlPullParserUtils.createParser(body);
56              readBodyRequest(xpp, requestMessage, actionInvocation);
57          } catch (Exception ex) {
58              throw new UnsupportedDataException("Can't transform message payload: " + ex, ex, body);
59          }
60      }
61  
62      public void readBody(ActionResponseMessage responseMsg, ActionInvocation actionInvocation) throws UnsupportedDataException {
63          String body = getMessageBody(responseMsg);
64          try {
65              XmlPullParser xpp = XmlPullParserUtils.createParser(body);
66              readBodyElement(xpp);
67              readBodyResponse(xpp, actionInvocation);
68          } catch (Exception ex) {
69              throw new UnsupportedDataException("Can't transform message payload: " + ex, ex, body);
70          }
71      }
72  
73      protected void readBodyElement(XmlPullParser xpp) throws Exception {
74          XmlPullParserUtils.searchTag(xpp, "Body");
75      }
76  
77      protected void readBodyRequest(XmlPullParser xpp, ActionRequestMessage requestMessage, ActionInvocation actionInvocation) throws Exception {
78          XmlPullParserUtils.searchTag(xpp, actionInvocation.getAction().getName());
79          readActionInputArguments(xpp, actionInvocation);
80      }
81  
82      protected void readBodyResponse(XmlPullParser xpp, ActionInvocation actionInvocation) throws Exception {
83          // We're in the "Body" tag
84          int event;
85          do {
86              event = xpp.next();
87              if (event == XmlPullParser.START_TAG) {
88                  if (xpp.getName().equals("Fault")) {
89                      ActionException e = readFaultElement(xpp);
90                      actionInvocation.setFailure(e);
91                      return;
92                  } else if (xpp.getName().equals(actionInvocation.getAction().getName() + "Response")) {
93                      readActionOutputArguments(xpp, actionInvocation);
94                      return;
95                  }
96              }
97  
98          }
99          while (event != XmlPullParser.END_DOCUMENT && (event != XmlPullParser.END_TAG || !xpp.getName().equals("Body")));
100 
101         throw new ActionException(
102             ErrorCode.ACTION_FAILED,
103             String.format("Action SOAP response do not contain %s element",
104                 actionInvocation.getAction().getName() + "Response"
105             )
106         );
107     }
108 
109     protected void readActionInputArguments(XmlPullParser xpp, ActionInvocation actionInvocation) throws Exception {
110         actionInvocation.setInput(readArgumentValues(xpp, actionInvocation.getAction().getInputArguments()));
111     }
112 
113     protected void readActionOutputArguments(XmlPullParser xpp, ActionInvocation actionInvocation) throws Exception {
114         actionInvocation.setOutput(readArgumentValues(xpp, actionInvocation.getAction().getOutputArguments()));
115     }
116 
117     protected Map<String, String> getMatchingNodes(XmlPullParser xpp, ActionArgument[] args) throws Exception {
118 
119         // This is a case-insensitive search!
120         List<String> names = new ArrayList<>();
121         for (ActionArgument argument : args) {
122             names.add(argument.getName().toUpperCase(Locale.ROOT));
123             for (String alias : Arrays.asList(argument.getAliases())) {
124                 names.add(alias.toUpperCase(Locale.ROOT));
125             }
126         }
127 
128         Map<String, String> matches = new HashMap<>();
129 
130         String enclosingTag = xpp.getName();
131 
132         int event;
133         do {
134             event = xpp.next();
135             if(event == XmlPullParser.START_TAG && names.contains(xpp.getName().toUpperCase(Locale.ROOT))) {
136                 matches.put(xpp.getName(), xpp.nextText());
137             }
138 
139         }
140         while (event != XmlPullParser.END_DOCUMENT && (event != XmlPullParser.END_TAG || !xpp.getName().equals(enclosingTag)));
141 
142         if (matches.size() < args.length) {
143             throw new ActionException(
144                 ErrorCode.ARGUMENT_VALUE_INVALID,
145                 "Invalid number of input or output arguments in XML message, expected "
146                     + args.length + " but found " + matches.size()
147             );
148         }
149         return matches;
150     }
151 
152     protected ActionArgumentValue[] readArgumentValues(XmlPullParser xpp, ActionArgument[] args) throws Exception {
153         // We're in the <ActionName>Response tag
154         Map<String, String> matches = getMatchingNodes(xpp, args);
155 
156         ActionArgumentValue[] values = new ActionArgumentValue[args.length];
157 
158         for (int i = 0; i < args.length; i++) {
159 
160             ActionArgument arg = args[i];
161             String value = findActionArgumentValue(matches, arg);
162             if (value == null) {
163                 throw new ActionException(
164                     ErrorCode.ARGUMENT_VALUE_INVALID,
165                     "Could not find argument '" + arg.getName() + "' node");
166             }
167 
168             log.fine("Reading action argument: " + arg.getName());
169             values[i] = createValue(arg, value);
170         }
171         return values;
172     }
173 
174     protected String findActionArgumentValue(Map<String, String> entries, ActionArgument arg) {
175         for (Map.Entry<String, String> entry : entries.entrySet()) {
176             if (arg.isNameOrAlias(entry.getKey())) return entry.getValue();
177         }
178         return null;
179     }
180 
181     protected ActionException readFaultElement(XmlPullParser xpp) throws Exception {
182         // We're in the "Fault" tag
183 
184         String errorCode = null;
185         String errorDescription = null;
186 
187         XmlPullParserUtils.searchTag(xpp, "UPnPError");
188 
189         int event;
190         do {
191             event = xpp.next();
192             if (event == XmlPullParser.START_TAG) {
193                 String tag = xpp.getName();
194                 if (tag.equals("errorCode")) {
195                     errorCode = xpp.nextText();
196                 } else if (tag.equals("errorDescription")) {
197                     errorDescription = xpp.nextText();
198                 }
199             }
200         }
201         while (event != XmlPullParser.END_DOCUMENT && (event != XmlPullParser.END_TAG || !xpp.getName().equals("UPnPError")));
202 
203         if (errorCode != null) {
204             try {
205                 int numericCode = Integer.valueOf(errorCode);
206                 ErrorCode standardErrorCode = ErrorCode.getByCode(numericCode);
207                 if (standardErrorCode != null) {
208                     log.fine("Reading fault element: " + standardErrorCode.getCode() + " - " + errorDescription);
209                     return new ActionException(standardErrorCode, errorDescription, false);
210                 } else {
211                     log.fine("Reading fault element: " + numericCode + " - " + errorDescription);
212                     return new ActionException(numericCode, errorDescription);
213                 }
214             } catch (NumberFormatException ex) {
215                 throw new RuntimeException("Error code was not a number");
216             }
217         }
218 
219         throw new RuntimeException("Received fault element but no error code");
220     }
221 }