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.controlpoint;
17  
18  import org.fourthline.cling.model.action.ActionException;
19  import org.fourthline.cling.model.action.ActionInvocation;
20  import org.fourthline.cling.model.message.UpnpResponse;
21  import org.fourthline.cling.model.message.control.IncomingActionResponseMessage;
22  import org.fourthline.cling.model.meta.LocalService;
23  import org.fourthline.cling.model.meta.RemoteService;
24  import org.fourthline.cling.model.meta.Service;
25  import org.fourthline.cling.protocol.sync.SendingAction;
26  
27  import java.net.URL;
28  
29  /**
30   * Execute actions on any service.
31   * <p>
32   * Usage example for asynchronous execution in a background thread:
33   * </p>
34   * <pre>
35   * Service service = device.findService(new UDAServiceId("SwitchPower"));
36   * Action getStatusAction = service.getAction("GetStatus");
37   * ActionInvocation getStatusInvocation = new ActionInvocation(getStatusAction);
38   *
39   * ActionCallback getStatusCallback = new ActionCallback(getStatusInvocation) {
40   *
41   *      public void success(ActionInvocation invocation) {
42   *          ActionArgumentValue status  = invocation.getOutput("ResultStatus");
43   *          assertEquals((Boolean) status.getValue(), Boolean.valueOf(false));
44   *      }
45   *
46   *      public void failure(ActionInvocation invocation, UpnpResponse res) {
47   *          System.err.println(
48   *              createDefaultFailureMessage(invocation, res)
49   *          );
50   *      }
51   * };
52   *
53   * upnpService.getControlPoint().execute(getStatusCallback)
54   * </pre>
55   * <p>
56   * You can also execute the action synchronously in the same thread using the
57   * {@link org.fourthline.cling.controlpoint.ActionCallback.Default} implementation:
58   * </p>
59   * <pre>
60   * myActionInvocation.setInput("foo", bar);
61   * new ActionCallback.Default(myActionInvocation, upnpService.getControlPoint()).run();
62   * myActionInvocation.getOutput("baz");
63   * </pre>
64   *
65   * @author Christian Bauer
66   */
67  public abstract class ActionCallback implements Runnable {
68  
69      /**
70       * Empty implementation of callback methods, simplifies synchronous
71       * execution of an {@link org.fourthline.cling.model.action.ActionInvocation}.
72       */
73      public static final class Default extends ActionCallback {
74  
75          public Default(ActionInvocation actionInvocation, ControlPoint controlPoint) {
76              super(actionInvocation, controlPoint);
77          }
78  
79          @Override
80          public void success(ActionInvocation invocation) {
81          }
82  
83          @Override
84          public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
85  
86          }
87      }
88  
89      protected final ActionInvocation actionInvocation;
90  
91      protected ControlPoint controlPoint;
92  
93      protected ActionCallback(ActionInvocation actionInvocation, ControlPoint controlPoint) {
94          this.actionInvocation = actionInvocation;
95          this.controlPoint = controlPoint;
96      }
97  
98      protected ActionCallback(ActionInvocation actionInvocation) {
99          this.actionInvocation = actionInvocation;
100     }
101 
102     public ActionInvocation getActionInvocation() {
103         return actionInvocation;
104     }
105 
106     synchronized public ControlPoint getControlPoint() {
107         return controlPoint;
108     }
109 
110     synchronized public ActionCallback setControlPoint(ControlPoint controlPoint) {
111         this.controlPoint = controlPoint;
112         return this;
113     }
114 
115     public void run() {
116         Service service = actionInvocation.getAction().getService();
117 
118         // Local execution
119         if (service instanceof LocalService) {
120             LocalService localService = (LocalService)service;
121 
122             // Executor validates input inside the execute() call immediately
123             localService.getExecutor(actionInvocation.getAction()).execute(actionInvocation);
124 
125             if (actionInvocation.getFailure() != null) {
126                 failure(actionInvocation, null);
127             } else {
128                 success(actionInvocation);
129             }
130 
131         // Remote execution
132         } else if (service instanceof RemoteService){
133 
134             if (getControlPoint()  == null) {
135                 throw new IllegalStateException("Callback must be executed through ControlPoint");
136             }
137 
138             RemoteService remoteService = (RemoteService)service;
139 
140             // Figure out the remote URL where we'd like to send the action request to
141             URL controLURL;
142             try {
143             	controLURL = remoteService.getDevice().normalizeURI(remoteService.getControlURI());
144             } catch(IllegalArgumentException e) {
145             	failure(actionInvocation, null, "bad control URL: " + remoteService.getControlURI());
146             	return ;
147             }
148 
149             // Do it
150             SendingAction prot = getControlPoint().getProtocolFactory().createSendingAction(actionInvocation, controLURL);
151             prot.run();
152 
153             IncomingActionResponseMessage response = prot.getOutputMessage();
154 
155             if (response == null) {
156                 failure(actionInvocation, null);
157             } else if (response.getOperation().isFailed()) {
158                 failure(actionInvocation, response.getOperation());
159             } else {
160                 success(actionInvocation);
161             }
162         }
163     }
164 
165     protected String createDefaultFailureMessage(ActionInvocation invocation, UpnpResponse operation) {
166         String message = "Error: ";
167         final ActionException exception = invocation.getFailure();
168         if (exception != null) {
169             message = message + exception.getMessage();
170         }
171         if (operation != null) {
172             message = message + " (HTTP response was: " + operation.getResponseDetails() + ")";
173         }
174         return message;
175     }
176 
177     protected void failure(ActionInvocation invocation, UpnpResponse operation) {
178         failure(invocation, operation, createDefaultFailureMessage(invocation, operation));
179     }
180 
181     /**
182      * Called when the action invocation succeeded.
183      *
184      * @param invocation The successful invocation, call its <code>getOutput()</code> method for results.
185      */
186     public abstract void success(ActionInvocation invocation);
187 
188     /**
189      * Called when the action invocation failed.
190      *
191      * @param invocation The failed invocation, call its <code>getFailure()</code> method for more details.
192      * @param operation If the invocation was on a remote service, the response message, otherwise null.
193      * @param defaultMsg A user-friendly error message generated from the invocation exception and response.
194      * @see #createDefaultFailureMessage
195      */
196     public abstract void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg);
197 
198     @Override
199     public String toString() {
200         return "(ActionCallback) " + actionInvocation;
201     }
202 }