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.RemoteActionInvocation;
22  import org.fourthline.cling.model.message.StreamRequestMessage;
23  import org.fourthline.cling.model.message.StreamResponseMessage;
24  import org.fourthline.cling.model.message.UpnpResponse;
25  import org.fourthline.cling.model.message.control.IncomingActionRequestMessage;
26  import org.fourthline.cling.model.message.control.OutgoingActionResponseMessage;
27  import org.fourthline.cling.model.message.header.ContentTypeHeader;
28  import org.fourthline.cling.model.message.header.UpnpHeader;
29  import org.fourthline.cling.model.resource.ServiceControlResource;
30  import org.fourthline.cling.model.types.ErrorCode;
31  import org.fourthline.cling.protocol.ReceivingSync;
32  import org.fourthline.cling.model.UnsupportedDataException;
33  import org.fourthline.cling.transport.RouterException;
34  import org.seamless.util.Exceptions;
35  
36  import java.util.logging.Level;
37  import java.util.logging.Logger;
38  
39  /**
40   * Handles reception of control messages, invoking actions on local services.
41   * <p>
42   * Actions are invoked through the {@link org.fourthline.cling.model.action.ActionExecutor} returned
43   * by the registered {@link org.fourthline.cling.model.meta.LocalService#getExecutor(org.fourthline.cling.model.meta.Action)}
44   * method.
45   * </p>
46   *
47   * @author Christian Bauer
48   */
49  public class ReceivingAction extends ReceivingSync<StreamRequestMessage, StreamResponseMessage> {
50  
51      final private static Logger log = Logger.getLogger(ReceivingAction.class.getName());
52  
53      public ReceivingAction(UpnpService upnpService, StreamRequestMessage inputMessage) {
54          super(upnpService, inputMessage);
55      }
56  
57      protected StreamResponseMessage executeSync() throws RouterException{
58  
59          ContentTypeHeader contentTypeHeader =
60                  getInputMessage().getHeaders().getFirstHeader(UpnpHeader.Type.CONTENT_TYPE, ContentTypeHeader.class);
61  
62          // Special rules for action messages! UDA 1.0 says:
63          // 'If the CONTENT-TYPE header specifies an unsupported value (other then "text/xml") the
64          // device must return an HTTP status code "415 Unsupported Media Type".'
65          if (contentTypeHeader != null && !contentTypeHeader.isUDACompliantXML()) {
66              log.warning("Received invalid Content-Type '" + contentTypeHeader + "': " + getInputMessage());
67              return new StreamResponseMessage(new UpnpResponse(UpnpResponse.Status.UNSUPPORTED_MEDIA_TYPE));
68          }
69  
70          if (contentTypeHeader == null) {
71              log.warning("Received without Content-Type: " + getInputMessage());
72          }
73  
74          ServiceControlResource resource =
75                  getUpnpService().getRegistry().getResource(
76                          ServiceControlResource.class,
77                          getInputMessage().getUri()
78                  );
79  
80          if (resource == null) {
81              log.fine("No local resource found: " + getInputMessage());
82              return null;
83          }
84  
85          log.fine("Found local action resource matching relative request URI: " + getInputMessage().getUri());
86  
87          RemoteActionInvocation invocation;
88          OutgoingActionResponseMessage responseMessage = null;
89  
90          try {
91  
92              // Throws ActionException if the action can't be found
93              IncomingActionRequestMessage requestMessage =
94                      new IncomingActionRequestMessage(getInputMessage(), resource.getModel());
95  
96              log.finer("Created incoming action request message: " + requestMessage);
97              invocation = new RemoteActionInvocation(requestMessage.getAction(), getRemoteClientInfo());
98  
99              // Throws UnsupportedDataException if the body can't be read
100             log.fine("Reading body of request message");
101             getUpnpService().getConfiguration().getSoapActionProcessor().readBody(requestMessage, invocation);
102 
103             log.fine("Executing on local service: " + invocation);
104             resource.getModel().getExecutor(invocation.getAction()).execute(invocation);
105 
106             if (invocation.getFailure() == null) {
107                 responseMessage =
108                         new OutgoingActionResponseMessage(invocation.getAction());
109             } else {
110 
111                 if (invocation.getFailure() instanceof ActionCancelledException) {
112                     log.fine("Action execution was cancelled, returning 404 to client");
113                     // A 404 status is appropriate for this situation: The resource is gone/not available and it's
114                     // a temporary condition. Most likely the cancellation happened because the client connection
115                     // has been dropped, so it doesn't really matter what we return here anyway.
116                     return null;
117                 } else {
118                     responseMessage =
119                             new OutgoingActionResponseMessage(
120                                 UpnpResponse.Status.INTERNAL_SERVER_ERROR,
121                                 invocation.getAction()
122                             );
123                 }
124             }
125 
126         } catch (ActionException ex) {
127             log.finer("Error executing local action: " + ex);
128 
129             invocation = new RemoteActionInvocation(ex, getRemoteClientInfo());
130             responseMessage = new OutgoingActionResponseMessage(UpnpResponse.Status.INTERNAL_SERVER_ERROR);
131 
132         } catch (UnsupportedDataException ex) {
133         	log.log(Level.WARNING, "Error reading action request XML body: " + ex.toString(), Exceptions.unwrap(ex));
134 
135             invocation =
136                     new RemoteActionInvocation(
137                         Exceptions.unwrap(ex) instanceof ActionException
138                                 ? (ActionException)Exceptions.unwrap(ex)
139                                 : new ActionException(ErrorCode.ACTION_FAILED, ex.getMessage()),
140                         getRemoteClientInfo()
141                     );
142             responseMessage = new OutgoingActionResponseMessage(UpnpResponse.Status.INTERNAL_SERVER_ERROR);
143 
144         }
145 
146         try {
147 
148             log.fine("Writing body of response message");
149             getUpnpService().getConfiguration().getSoapActionProcessor().writeBody(responseMessage, invocation);
150 
151             log.fine("Returning finished response message: " + responseMessage);
152             return responseMessage;
153 
154         } catch (UnsupportedDataException ex) {
155             log.warning("Failure writing body of response message, sending '500 Internal Server Error' without body");
156             log.log(Level.WARNING, "Exception root cause: ", Exceptions.unwrap(ex));
157             return new StreamResponseMessage(UpnpResponse.Status.INTERNAL_SERVER_ERROR);
158         }
159     }
160 
161 }