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 }