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 }