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.protocol.sync;
17  
18  import org.fourthline.cling.UpnpService;
19  import org.fourthline.cling.model.action.ActionCancelledException;
20  import org.fourthline.cling.model.action.ActionException;
21  import org.fourthline.cling.model.action.ActionInvocation;
22  import org.fourthline.cling.model.message.StreamResponseMessage;
23  import org.fourthline.cling.model.message.UpnpResponse;
24  import org.fourthline.cling.model.message.control.IncomingActionResponseMessage;
25  import org.fourthline.cling.model.message.control.OutgoingActionRequestMessage;
26  import org.fourthline.cling.model.meta.Device;
27  import org.fourthline.cling.model.types.ErrorCode;
28  import org.fourthline.cling.protocol.SendingSync;
29  import org.fourthline.cling.model.UnsupportedDataException;
30  import org.fourthline.cling.transport.RouterException;
31  import org.seamless.util.Exceptions;
32  
33  import java.net.URL;
34  import java.util.logging.Level;
35  import java.util.logging.Logger;
36  
37  /**
38   * Sending control message, transforming a local {@link org.fourthline.cling.model.action.ActionInvocation}.
39   * <p>
40   * Writes the outgoing message's body with the {@link org.fourthline.cling.transport.spi.SOAPActionProcessor}.
41   * This protocol will return <code>null</code> if no response was received from the control target host.
42   * In all other cases, even if only the processing of message content failed, this protocol will
43   * return an {@link org.fourthline.cling.model.message.control.IncomingActionResponseMessage}. Any error
44   * details of a failed response ({@link org.fourthline.cling.model.message.UpnpResponse#isFailed()}) are
45   * available with
46   * {@link org.fourthline.cling.model.action.ActionInvocation#setFailure(org.fourthline.cling.model.action.ActionException)}.
47   * </p>
48   *
49   * @author Christian Bauer
50   */
51  public class SendingAction extends SendingSync<OutgoingActionRequestMessage, IncomingActionResponseMessage> {
52  
53      final private static Logger log = Logger.getLogger(SendingAction.class.getName());
54  
55      final protected ActionInvocation actionInvocation;
56  
57      public SendingAction(UpnpService upnpService, ActionInvocation actionInvocation, URL controlURL) {
58          super(upnpService, new OutgoingActionRequestMessage(actionInvocation, controlURL));
59          this.actionInvocation = actionInvocation;
60      }
61  
62      protected IncomingActionResponseMessage executeSync() throws RouterException {
63          return invokeRemote(getInputMessage());
64      }
65  
66      protected IncomingActionResponseMessage invokeRemote(OutgoingActionRequestMessage requestMessage) throws RouterException {
67          Device device = actionInvocation.getAction().getService().getDevice();
68  
69          log.fine("Sending outgoing action call '" + actionInvocation.getAction().getName() + "' to remote service of: " + device);
70          IncomingActionResponseMessage responseMessage = null;
71          try {
72  
73              StreamResponseMessage streamResponse = sendRemoteRequest(requestMessage);
74  
75              if (streamResponse == null) {
76                  log.fine("No connection or no no response received, returning null");
77                  actionInvocation.setFailure(new ActionException(ErrorCode.ACTION_FAILED, "Connection error or no response received"));
78                  return null;
79              }
80  
81              responseMessage = new IncomingActionResponseMessage(streamResponse);
82  
83              if (responseMessage.isFailedNonRecoverable()) {
84                  log.fine("Response was a non-recoverable failure: " + responseMessage);
85                  throw new ActionException(
86                          ErrorCode.ACTION_FAILED, "Non-recoverable remote execution failure: " + responseMessage.getOperation().getResponseDetails()
87                  );
88              } else if (responseMessage.isFailedRecoverable()) {
89                  handleResponseFailure(responseMessage);
90              } else {
91                  handleResponse(responseMessage);
92              }
93  
94              return responseMessage;
95  
96  
97          } catch (ActionException ex) {
98              log.fine("Remote action invocation failed, returning Internal Server Error message: " + ex.getMessage());
99              actionInvocation.setFailure(ex);
100             if (responseMessage == null || !responseMessage.getOperation().isFailed()) {
101                 return new IncomingActionResponseMessage(new UpnpResponse(UpnpResponse.Status.INTERNAL_SERVER_ERROR));
102             } else {
103                 return responseMessage;
104             }
105         }
106     }
107 
108     protected StreamResponseMessage sendRemoteRequest(OutgoingActionRequestMessage requestMessage)
109         throws ActionException, RouterException {
110 
111         try {
112             log.fine("Writing SOAP request body of: " + requestMessage);
113             getUpnpService().getConfiguration().getSoapActionProcessor().writeBody(requestMessage, actionInvocation);
114 
115             log.fine("Sending SOAP body of message as stream to remote device");
116             return getUpnpService().getRouter().send(requestMessage);
117         } catch (RouterException ex) {
118             Throwable cause = Exceptions.unwrap(ex);
119             if (cause instanceof InterruptedException) {
120                 if (log.isLoggable(Level.FINE)) {
121                     log.fine("Sending action request message was interrupted: " + cause);
122                 }
123                 throw new ActionCancelledException((InterruptedException)cause);
124             }
125             throw ex;
126         } catch (UnsupportedDataException ex) {
127             if (log.isLoggable(Level.FINE)) {
128                 log.fine("Error writing SOAP body: " + ex);
129                 log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
130             }
131             throw new ActionException(ErrorCode.ACTION_FAILED, "Error writing request message. " + ex.getMessage());
132         }
133     }
134 
135     protected void handleResponse(IncomingActionResponseMessage responseMsg) throws ActionException {
136 
137         try {
138             log.fine("Received response for outgoing call, reading SOAP response body: " + responseMsg);
139             getUpnpService().getConfiguration().getSoapActionProcessor().readBody(responseMsg, actionInvocation);
140         } catch (UnsupportedDataException ex) {
141             log.fine("Error reading SOAP body: " + ex);
142             log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
143             throw new ActionException(
144                 ErrorCode.ACTION_FAILED,
145                 "Error reading SOAP response message. " + ex.getMessage(),
146                 false
147             );
148         }
149     }
150 
151     protected void handleResponseFailure(IncomingActionResponseMessage responseMsg) throws ActionException {
152 
153         try {
154             log.fine("Received response with Internal Server Error, reading SOAP failure message");
155             getUpnpService().getConfiguration().getSoapActionProcessor().readBody(responseMsg, actionInvocation);
156         } catch (UnsupportedDataException ex) {
157             log.fine("Error reading SOAP body: " + ex);
158             log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
159             throw new ActionException(
160                 ErrorCode.ACTION_FAILED,
161                 "Error reading SOAP response failure message. " + ex.getMessage(),
162                 false
163             );
164         }
165     }
166 
167 }
168 
169 /*
170 
171 - send request
172    - UnsupportedDataException: Can't write body
173 
174 - streamResponseMessage is null: No response received, return null to client
175 
176 - streamResponseMessage >= 300 && !(405 || 500): Response was HTTP failure, set on anemic response and return
177 
178 - streamResponseMessage >= 300 && 405: Try request again with different headers
179    - UnsupportedDataException: Can't write body
180    - (The whole streamResponse conditions apply again but this time, ignore 405)
181 
182 - streamResponseMessage >= 300 && 500 && lastExecutionFailure != null: Try to read SOAP failure body
183    - UnsupportedDataException: Can't read body
184 
185 - streamResponseMessage < 300: Response was OK, try to read response body
186    - UnsupportedDataException: Can't read body
187 
188 
189 */