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 }