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 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 }