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  package example.controlpoint;
16  
17  import example.binarylight.BinaryLightSampleData;
18  import org.fourthline.cling.binding.LocalServiceBinder;
19  import org.fourthline.cling.binding.annotations.AnnotationLocalServiceBinder;
20  import org.fourthline.cling.controlpoint.ActionCallback;
21  import org.fourthline.cling.mock.MockUpnpService;
22  import org.fourthline.cling.model.DefaultServiceManager;
23  import org.fourthline.cling.model.action.ActionCancelledException;
24  import org.fourthline.cling.model.action.ActionInvocation;
25  import org.fourthline.cling.model.message.UpnpResponse;
26  import org.fourthline.cling.model.meta.Action;
27  import org.fourthline.cling.model.meta.LocalDevice;
28  import org.fourthline.cling.model.meta.LocalService;
29  import org.fourthline.cling.model.types.UDAServiceId;
30  import org.testng.annotations.DataProvider;
31  import org.testng.annotations.Test;
32  
33  import java.util.concurrent.Future;
34  
35  import static org.testng.Assert.assertEquals;
36  
37  /**
38   * Cancelling an action invocation
39   * <p>
40   * You call actions of services with the <code>ControlPoint#execute(myCallback)</code> method. So far you probably
41   * haven't considered the optional return value of this method, a <code>Future</code> which can be used to cancel
42   * the invocation:
43   * </p>
44   * <p/>
45   * <a class="citation"
46   * href="javacode://this#invokeActions(LocalDevice)"
47   * style="include: EXECUTE_CANCEL;"/>
48   * <p/>
49   * <p>
50   * Here we are calling the <code>SetTarget</code> action of a <em>SwitchPower:1</em> service, and after waiting a
51   * (short) time period, we cancel the request. What happens now depends on the invocation and what service you are
52   * calling. If it's a local service, and no network access is needed, the thread calling the local service (method)
53   * will simply be interrupted. If you are calling a remote service, Cling will abort the HTTP request to the server.
54   * </p>
55   * <p>
56   * Most likely you want to handle this explicit cancellation of an action call in your action invocation callback, so
57   * you can present the result to your user. Override the <code>failure()</code> method to handle the interruption:
58   * </p>
59   * <p/>
60   * <a class="citation" href="javacode://this#invokeActions(LocalDevice)"
61   *    id="ActionCancellationTest_invokeActions2"
62   *    style="include: CALLBACK; exclude: TEST;"/>
63   * <p>
64   * A special exception type is provided if the action call was indeed cancelled.
65   * </p>
66   * <p>
67   * Several important issues have to be considered when you try to cancel action calls to remote services:
68   * </p>
69   * <p>
70   * There is no guarantee that the server will actually stop processing your request. When the client closes the
71   * connection, the server doesn't get notified. The server will complete the action call and only fail when trying to
72   * return the response to the client on the closed connection. Cling's server transports offer a special heartbeat
73   * feature for checking client connections, we'll discuss this feature later in this chapter. Other UPnP servers will
74   * most likely not detect a dropped client connection immediately.
75   * </p>
76   * <p>
77   * Not all HTTP client transports in Cling support interruption of requests:
78   * </p>
79   * <table class="infotable fullwidth" border="1">
80   * <thead>
81   * <tr>
82   * <th>Transport</th>
83   * <th class="thirdwidth">Supports Interruption?</th>
84   * </tr>
85   * </thead>
86   * <tbody>
87   * <tr>
88   * <td class="nowrap">
89   * <code>org.fourthline.cling.transport.impl.StreamClientImpl (default)</code>
90   * </td>
91   * <td>NO</td>
92   * </tr>
93   * <tr>
94   * <td class="nowrap">
95   * <code>org.fourthline.cling.transport.impl.apache.StreamClientImpl</code>
96   * </td>
97   * <td>YES</td>
98   * </tr>
99   * <tr>
100  * <td class="nowrap">
101  * <code>org.fourthline.cling.transport.impl.jetty.StreamClientImpl (default on Android)</code>
102  * </td>
103  * <td>YES</td>
104  * </tr>
105  * </tbody>
106  * </table>
107  * <p>
108  * Transports which do not support cancellation won't produce an error when you abort an action invocation, they
109  * silently ignore the interruption and continue waiting for the server to respond.
110  * </p>
111  */
112 public class ActionCancellationTest {
113 
114     protected LocalService bindService(Class<?> clazz) throws Exception {
115         LocalServiceBinder binder = new AnnotationLocalServiceBinder();
116         LocalService svc = binder.read(clazz);
117         svc.setManager(
118             new DefaultServiceManager(svc, clazz)
119         );
120         return svc;
121     }
122 
123     @DataProvider(name = "devices")
124     public Object[][] getDevices() throws Exception {
125         return new LocalDevice[][]{
126             {BinaryLightSampleData.createDevice(bindService(SwitchPowerWithInterruption.class))},
127         };
128     }
129 
130     @Test(dataProvider = "devices")
131     public void invokeActions(LocalDevice device) throws Exception {
132         final boolean[] tests = new boolean[1];
133 
134         MockUpnpService upnpService = new MockUpnpService(false, false, true);
135         LocalService service = device.findService(new UDAServiceId("SwitchPower"));
136         Action action = service.getAction("SetTarget");
137 
138         ActionInvocation setTargetInvocation = new ActionInvocation(action);
139         setTargetInvocation.setInput("NewTargetValue", true);
140 
141         // DOC:CALLBACK
142         ActionCallback setTargetCallback = new ActionCallback(setTargetInvocation) {
143 
144             @Override
145             public void success(ActionInvocation invocation) {
146                 // Will not be called if invocation has been cancelled
147             }
148 
149             @Override
150             public void failure(ActionInvocation invocation,
151                                 UpnpResponse operation,
152                                 String defaultMsg) {
153                 if (invocation.getFailure() instanceof ActionCancelledException) {
154                     // Handle the cancellation here...
155                     tests[0] = true; // DOC:TEST
156                 }
157             }
158         };
159         // DOC:CALLBACK
160 
161         // DOC:EXECUTE_CANCEL
162         Future future = upnpService.getControlPoint().execute(setTargetCallback);
163         Thread.sleep(500); // DOC:WAIT_FOR_THREAD
164         future.cancel(true);
165         // DOC:EXECUTE_CANCEL
166 
167         Thread.sleep(500);
168         for (boolean test : tests) {
169             assertEquals(test, true);
170         }
171     }
172 }