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 org.fourthline.cling.binding.annotations.UpnpAction; 18 import org.fourthline.cling.binding.annotations.UpnpInputArgument; 19 import org.fourthline.cling.binding.annotations.UpnpOutputArgument; 20 import org.fourthline.cling.binding.annotations.UpnpService; 21 import org.fourthline.cling.binding.annotations.UpnpServiceId; 22 import org.fourthline.cling.binding.annotations.UpnpServiceType; 23 import org.fourthline.cling.binding.annotations.UpnpStateVariable; 24 import org.fourthline.cling.model.profile.RemoteClientInfo; 25 26 /** 27 * Reacting to cancellation on the server 28 * <p> 29 * By default, an action method of your service will run until it completes, it either returns or throws an exception. 30 * If you have to perform long-running tasks in a service, your action method can avoid doing unnecessary work by 31 * checking if processing should continue. Think about processing in batches: You work for a while, then you check if 32 * you should continue, then you work some more, check again, and so on. 33 * </p> 34 * <p> 35 * There are two checks you have to perform: 36 * </p> 37 * <ul> 38 * <li> 39 * If a local control point called your service, and meanwhile cancelled the action call, the thread running your action 40 * method will have its interruption flag set. When you see this flag you can stop processing, as any result of your 41 * action method will be ignored anyway. 42 * </li> 43 * <li> 44 * If a remote control point called your service, it might have dropped the connection while you were processing data 45 * to return. Unfortunately, checking if the client's connection is still open requires, on a TCP level, writing data 46 * on the socket. This is essentially a heartbeat signal: Every time you check if the client is still there, a byte of 47 * (hopefully) insignificant data will be send to the client. If there wasn't any error sending data, the connection 48 * is still alive. 49 * </li> 50 * </ul> 51 * <p> 52 * These checks look as follows in your service method: 53 * </p> 54 * <a class="citation" 55 * href="javacode://this" 56 * style="include: ACTION_METHOD; exclude: ACTUAL_WORK;"/> 57 * 58 * <p> 59 * You abort processing by throwing an <code>InterruptedException</code>, Cling will do the rest. Cling will send 60 * a heartbeat to the client whenever you check if the remote request was cancelled with the optional 61 * <code>RemoteClientInfo</code>, see <a href="#javadoc.example.localservice.RemoteClientInfoTest">this section</a>. 62 * </p> 63 * <p> 64 * <em>Danger:</em> Not all HTTP clients can deal with Cling's heartbeat signal. Not even all bundled 65 * <code>StreamClient</code>'s of Cling can handle such a signal. You should only use this feature if you are sure that 66 * all clients of your service will ignore the meaningless heartbeat signal. Cling sends a space character (this is 67 * configurable) to the HTTP client to check the connection. Hence, the HTTP client sees a response such as 68 * '[space][space][space]HTTP/1.0', with a space character for each alive check. If your HTTP client does not trim those 69 * space characters before parsing the response, it will fail processing your otherwise valid response. 70 * </p> 71 * <p> 72 * The following Cling-bundled client transports can deal with a heartbeat signal: 73 * </p> 74 * <table class="infotable fullwidth" border="1"> 75 * <thead> 76 * <tr> 77 * <th>Transport</th> 78 * <th class="thirdwidth">Accepts Heartbeat?</th> 79 * </tr> 80 * </thead> 81 * <tbody> 82 * <tr> 83 * <td class="nowrap"> 84 * <code>org.fourthline.cling.transport.impl.StreamClientImpl (default)</code> 85 * </td> 86 * <td>NO</td> 87 * </tr> 88 * <tr> 89 * <td class="nowrap"> 90 * <code>org.fourthline.cling.transport.impl.apache.StreamClientImpl</code> 91 * </td> 92 * <td>YES</td> 93 * </tr> 94 * <tr> 95 * <td class="nowrap"> 96 * <code>org.fourthline.cling.transport.impl.jetty.StreamClientImpl (default on Android)</code> 97 * </td> 98 * <td>YES</td> 99 * </tr> 100 * </tbody> 101 * </table> 102 * <p> 103 * Equally important, not all server transports in Cling can send heartbeat signals, as low-level socket access is 104 * required. Some server APIs do not provide this low-level access. If you check the connection state with those 105 * transports, the connection is always "alive": 106 * </p> 107 * <table class="infotable fullwidth" border="1"> 108 * <thead> 109 * <tr> 110 * <th>Transport</th> 111 * <th class="thirdwidth">Sends Heartbeat?</th> 112 * </tr> 113 * </thead> 114 * <tbody> 115 * <tr> 116 * <td class="nowrap"> 117 * <code>org.fourthline.cling.transport.impl.StreamServerImpl (default)</code> 118 * </td> 119 * <td>NO</td> 120 * </tr> 121 * <tr> 122 * <td class="nowrap"> 123 * <code>org.fourthline.cling.transport.impl.apache.StreamServerImpl</code> 124 * </td> 125 * <td>YES</td> 126 * </tr> 127 * <tr> 128 * <td class="nowrap"> 129 * <code>org.fourthline.cling.transport.impl.AsyncServletStreamServerImpl</code><br/> 130 * with <code>org.fourthline.cling.transport.impl.jetty.JettyServletContainer (default on Android)</code> 131 * </td> 132 * <td>YES</td> 133 * </tr> 134 * </tbody> 135 * </table> 136 * <p> 137 * In practice, this heartbeat feature is less useful than it sounds in theory: As you usually don't control which HTTP 138 * clients will access your server, sending them "garbage" bytes before responding properly will most likely cause 139 * interoperability problems. 140 * </p> 141 */ 142 @UpnpService( 143 serviceId = @UpnpServiceId("SwitchPower"), 144 serviceType = @UpnpServiceType(value = "SwitchPower", version = 1) 145 ) 146 public class SwitchPowerWithInterruption { 147 148 @UpnpStateVariable(sendEvents = false) 149 private boolean target = false; 150 151 @UpnpStateVariable 152 private boolean status = false; 153 154 // DOC:ACTION_METHOD 155 @UpnpAction 156 public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue, 157 RemoteClientInfo remoteClientInfo) throws InterruptedException { 158 // DOC:ACTUAL_WORK 159 target = newTargetValue; 160 status = newTargetValue; 161 // DOC:ACTUAL_WORK 162 163 boolean interrupted = false; 164 while (!interrupted) { 165 // Do some long-running work and periodically test if you should continue... 166 167 // ... for local service invocation 168 if (Thread.interrupted()) 169 interrupted = true; 170 171 // ... for remote service invocation 172 if (remoteClientInfo != null && remoteClientInfo.isRequestCancelled()) 173 interrupted = true; 174 } 175 throw new InterruptedException("Execution interrupted"); 176 } 177 // DOC:ACTION_METHOD 178 179 @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue")) 180 public boolean getTarget() { 181 return target; 182 } 183 184 @UpnpAction(out = @UpnpOutputArgument(name = "ResultStatus")) 185 public boolean getStatus() { 186 return status; 187 } 188 189 }