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.localservice;
16  
17  import example.binarylight.BinaryLightSampleData;
18  import example.controlpoint.EventSubscriptionTest;
19  import org.fourthline.cling.controlpoint.SubscriptionCallback;
20  import org.fourthline.cling.mock.MockRouter;
21  import org.fourthline.cling.mock.MockUpnpService;
22  import org.fourthline.cling.model.gena.CancelReason;
23  import org.fourthline.cling.model.gena.GENASubscription;
24  import org.fourthline.cling.model.message.StreamResponseMessage;
25  import org.fourthline.cling.model.message.UpnpResponse;
26  import org.fourthline.cling.model.meta.LocalDevice;
27  import org.fourthline.cling.model.meta.LocalService;
28  import org.fourthline.cling.test.data.SampleData;
29  import org.seamless.util.Reflections;
30  import org.testng.annotations.Test;
31  
32  import java.util.ArrayList;
33  import java.util.List;
34  
35  import static org.testng.Assert.*;
36  
37  /**
38   * Providing events on service state changes
39   * <p>
40   * The standard mechanism in the JDK for eventing is the <code>PropertyChangeListener</code> reacting
41   * on a <code>PropertyChangeEvent</code>. Cling utilizes this API for service eventing, thus avoiding
42   * a dependency between your service code and proprietary APIs.
43   * </p>
44   * <p>
45   * Consider the following modification of the original <a href="#section.SwitchPower">SwitchPower:1</a>
46   * implementation:
47   * </p>
48   * <a class="citation" href="javacode://example.localservice.SwitchPowerWithPropertyChangeSupport"/>
49   * <p>
50   * The only additional dependency is on <code>java.beans.PropertyChangeSupport</code>. Cling
51   * detects the <code>getPropertyChangeSupport()</code> method of your service class and automatically
52   * binds the service management on it. You will have to have this method for eventing to work with
53   * Cling. You can create the <code>PropertyChangeSupport</code> instance
54   * in your service's constructor or any other way, the only thing Cling is interested in are property
55   * change events with the "property" name of a UPnP state variable.
56   * </p>
57   * <p>
58   * Consequently, <code>firePropertyChange("NameOfAStateVariable")</code> is how you tell Cling that
59   * a state variable value has changed. It doesn't even matter if you call
60   * <code>firePropertyChange("Status", null, null)</code> or
61   * <code>firePropertyChange("Status", oldValue, newValue)</code>.
62   * Cling <em>only</em> cares about the state variable name; it will then check if the state variable is
63   * evented and pull the data out of your service implementation instance by accessing the appropriate
64   * field or a getter. Any "old" or "new" value you pass along is ignored.
65   * </p>
66   * <p>
67   * Also note that <code>firePropertyChange("Target", null, null)</code> would have no effect, because
68   * <code>Target</code> is mapped with <code>sendEvents="false"</code>.
69   * </p>
70   * <p>
71   * Most of the time a JavaBean property name is <em>not</em> the same as UPnP state variable
72   * name. For example, the JavaBean <code>status</code> property name is lowercase, while the UPnP state
73   * variable name is uppercase <code>Status</code>. The Cling eventing system ignores any property
74   * change event that doesn't exactly name a service state variable. This allows you to use
75   * JavaBean eventing independently from UPnP eventing, e.g. for GUI binding (Swing components also
76   * use the JavaBean eventing system).
77   * </p>
78   * <p>
79   * Let's assume for the sake of the next example that <code>Target</code> actually is also evented,
80   * like <code>Status</code>. If several evented state variables change in your service, but you don't
81   * want to trigger individual change events for each variable, you can combine them in a single event
82   * as a comma-separated list of state variable names:
83   * </p>
84   * <a class="citation" href="javacode://example.localservice.SwitchPowerWithBundledPropertyChange#setTarget(boolean)"/>
85   */
86  public class EventProviderTest extends EventSubscriptionTest {
87  
88      @Test
89      public void subscriptionLifecycleChangeSupport() throws Exception {
90  
91          MockUpnpService upnpService = createMockUpnpService();
92  
93          final List<Boolean> testAssertions = new ArrayList<>();
94  
95          // Register local device and its service
96          LocalDevice device = BinaryLightSampleData.createDevice(SwitchPowerWithPropertyChangeSupport.class);
97          upnpService.getRegistry().addDevice(device);
98  
99          LocalService<SwitchPowerWithPropertyChangeSupport> service = SampleData.getFirstService(device);
100 
101         SubscriptionCallback callback = new SubscriptionCallback(service, 180) {
102 
103             @Override
104             protected void failed(GENASubscription subscription,
105                                   UpnpResponse responseStatus,
106                                   Exception exception,
107                                   String defaultMsg) {
108                 testAssertions.add(false);
109             }
110 
111             @Override
112             public void established(GENASubscription subscription) {
113                 testAssertions.add(true);
114             }
115 
116             @Override
117             public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
118                 assertNotNull(subscription);
119                 assertNull(reason);
120                 assertNull(responseStatus);
121                 testAssertions.add(true);
122             }
123 
124             public void eventReceived(GENASubscription subscription) {
125                 if (subscription.getCurrentSequence().getValue() == 0) {
126                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
127                     testAssertions.add(true);
128                 } else if (subscription.getCurrentSequence().getValue() == 1) {
129                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "1");
130                     testAssertions.add(true);
131                 } else {
132                     testAssertions.add(false);
133                 }
134             }
135 
136             public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
137                 testAssertions.add(false);
138             }
139 
140         };
141 
142         upnpService.getControlPoint().execute(callback);
143 
144         // This triggers the internal PropertyChangeSupport of the service impl!
145         service.getManager().getImplementation().setTarget(true);
146 
147         assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
148         assertTrue(callback.getSubscription().getSubscriptionId().startsWith("uuid:"));
149         assertEquals(callback.getSubscription().getActualDurationSeconds(), Integer.MAX_VALUE);
150 
151         callback.end();
152 
153         assertEquals(testAssertions.size(), 4);
154         for (Boolean testAssertion : testAssertions) {
155             assertTrue(testAssertion);
156         }
157 
158         assertEquals(upnpService.getRouter().getSentStreamRequestMessages().size(), 0);
159     }
160 
161     @Test
162     public void bundleSeveralVariables() throws Exception {
163 
164         MockUpnpService upnpService = createMockUpnpService();
165 
166         final List<Boolean> testAssertions = new ArrayList<>();
167 
168         // Register local device and its service
169         LocalDevice device = BinaryLightSampleData.createDevice(SwitchPowerWithBundledPropertyChange.class);
170         upnpService.getRegistry().addDevice(device);
171 
172         LocalService<SwitchPowerWithBundledPropertyChange> service = SampleData.getFirstService(device);
173 
174         SubscriptionCallback callback = new SubscriptionCallback(service, 180) {
175 
176             @Override
177             protected void failed(GENASubscription subscription,
178                                   UpnpResponse responseStatus,
179                                   Exception exception,
180                                   String defaultMsg) {
181                 testAssertions.add(false);
182             }
183 
184             @Override
185             public void established(GENASubscription subscription) {
186                 testAssertions.add(true);
187             }
188 
189             @Override
190             public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
191                 assertNotNull(subscription);
192                 assertNull(reason);
193                 assertNull(responseStatus);
194                 testAssertions.add(true);
195             }
196 
197             public void eventReceived(GENASubscription subscription) {
198                 if (subscription.getCurrentSequence().getValue() == 0) {
199                     assertEquals(subscription.getCurrentValues().get("Target").toString(), "0");
200                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
201                     testAssertions.add(true);
202                 } else if (subscription.getCurrentSequence().getValue() == 1) {
203                     assertEquals(subscription.getCurrentValues().get("Target").toString(), "1");
204                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "1");
205                     testAssertions.add(true);
206                 } else {
207                     testAssertions.add(false);
208                 }
209             }
210 
211             public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
212                 testAssertions.add(false);
213             }
214 
215         };
216 
217         upnpService.getControlPoint().execute(callback);
218 
219         // This triggers the internal PropertyChangeSupport of the service impl!
220         service.getManager().getImplementation().setTarget(true);
221 
222         assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
223         assertTrue(callback.getSubscription().getSubscriptionId().startsWith("uuid:"));
224         assertEquals(callback.getSubscription().getActualDurationSeconds(), Integer.MAX_VALUE);
225 
226         callback.end();
227 
228         assertEquals(testAssertions.size(), 4);
229         for (Boolean testAssertion : testAssertions) {
230             assertTrue(testAssertion);
231         }
232 
233         assertEquals(upnpService.getRouter().getSentStreamRequestMessages().size(), 0);
234     }
235 
236     @Test
237     public void moderateMaxRate() throws Exception {
238 
239         MockUpnpService upnpService = new MockUpnpService() {
240             @Override
241             protected MockRouter createRouter() {
242                 return new MockRouter(getConfiguration(), getProtocolFactory()) {
243                     @Override
244                     public StreamResponseMessage[] getStreamResponseMessages() {
245                         return new StreamResponseMessage[]{
246                             createSubscribeResponseMessage(),
247                             createUnsubscribeResponseMessage()
248                         };
249                     }
250                 };
251             }
252         };
253 
254         final List<Boolean> testAssertions = new ArrayList<>();
255 
256         // Register local device and its service
257         LocalDevice device = BinaryLightSampleData.createDevice(SwitchPowerModerated.class);
258         upnpService.getRegistry().addDevice(device);
259 
260         LocalService service = SampleData.getFirstService(device);
261 
262         SubscriptionCallback callback = new SubscriptionCallback(service) {
263 
264             @Override
265             protected void failed(GENASubscription subscription,
266                                   UpnpResponse responseStatus,
267                                   Exception exception,
268                                   String defaultMsg) {
269                 testAssertions.add(false);
270             }
271 
272             @Override
273             public void established(GENASubscription subscription) {
274                 testAssertions.add(true);
275             }
276 
277             @Override
278             public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
279                 assertNotNull(subscription);
280                 assertNull(reason);
281                 assertNull(responseStatus);
282                 testAssertions.add(true);
283             }
284 
285             public void eventReceived(GENASubscription subscription) {
286                 if (subscription.getCurrentSequence().getValue() == 0) {
287 
288                     // Initial event contains all evented variables, snapshot of the service state
289                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
290                     assertEquals(subscription.getCurrentValues().get("ModeratedMinDeltaVar").toString(), "1");
291 
292                     // Initial state
293                     assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "one");
294 
295                     testAssertions.add(true);
296                 } else if (subscription.getCurrentSequence().getValue() == 1) {
297 
298                     // Subsequent events do NOT contain unchanged variables
299                     assertNull(subscription.getCurrentValues().get("Status"));
300                     assertNull(subscription.getCurrentValues().get("ModeratedMinDeltaVar"));
301 
302                     // We didn't see the intermediate values "two" and "three" because it's moderated
303                     assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "four");
304 
305                     testAssertions.add(true);
306                 } else {
307                     testAssertions.add(false);
308                 }
309             }
310 
311             public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
312                 testAssertions.add(false);
313             }
314 
315         };
316 
317         upnpService.getControlPoint().execute(callback);
318 
319         Thread.sleep(200);
320 
321         Object serviceImpl = service.getManager().getImplementation();
322 
323         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "two");
324         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", null, null);
325 
326         Thread.sleep(200);
327 
328         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "three");
329         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", null, null);
330 
331         Thread.sleep(200);
332 
333         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "four");
334         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", null, null);
335 
336         Thread.sleep(100);
337 
338         assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
339 
340         callback.end();
341 
342         assertEquals(testAssertions.size(), 4);
343         for (Boolean testAssertion : testAssertions) {
344             assertTrue(testAssertion);
345         }
346 
347         assertEquals(upnpService.getRouter().getSentStreamRequestMessages().size(), 0);
348     }
349 
350     @Test
351     public void moderateMinDelta() throws Exception {
352 
353         MockUpnpService upnpService = new MockUpnpService() {
354             @Override
355             protected MockRouter createRouter() {
356                 return new MockRouter(getConfiguration(), getProtocolFactory()) {
357                     @Override
358                     public StreamResponseMessage[] getStreamResponseMessages() {
359                         return new StreamResponseMessage[]{
360                             createSubscribeResponseMessage(),
361                             createUnsubscribeResponseMessage()
362                         };
363                     }
364                 };
365             }
366         };
367 
368         final List<Boolean> testAssertions = new ArrayList<>();
369 
370         // Register local device and its service
371         LocalDevice device = BinaryLightSampleData.createDevice(SwitchPowerModerated.class);
372         upnpService.getRegistry().addDevice(device);
373 
374         LocalService service = SampleData.getFirstService(device);
375 
376         SubscriptionCallback callback = new SubscriptionCallback(service) {
377 
378             @Override
379             protected void failed(GENASubscription subscription,
380                                   UpnpResponse responseStatus,
381                                   Exception exception,
382                                   String defaultMsg) {
383                 testAssertions.add(false);
384             }
385 
386             @Override
387             public void established(GENASubscription subscription) {
388                 testAssertions.add(true);
389             }
390 
391             @Override
392             public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
393                 assertNotNull(subscription);
394                 assertNull(reason);
395                 assertNull(responseStatus);
396                 testAssertions.add(true);
397             }
398 
399             public void eventReceived(GENASubscription subscription) {
400                 if (subscription.getCurrentSequence().getValue() == 0) {
401 
402                     // Initial event contains all evented variables, snapshot of the service state
403                     assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
404                     assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "one");
405 
406                     // Initial state
407                     assertEquals(subscription.getCurrentValues().get("ModeratedMinDeltaVar").toString(), "1");
408 
409                     testAssertions.add(true);
410                 } else if (subscription.getCurrentSequence().getValue() == 1) {
411 
412                     // Subsequent events do NOT contain unchanged variables
413                     assertNull(subscription.getCurrentValues().get("Status"));
414                     assertNull(subscription.getCurrentValues().get("ModeratedMaxRateVar"));
415 
416                     // We didn't get events for values 2 and 3
417                     assertEquals(subscription.getCurrentValues().get("ModeratedMinDeltaVar").toString(), "4");
418 
419                     testAssertions.add(true);
420                 } else {
421                     testAssertions.add(false);
422                 }
423             }
424 
425             public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
426                 testAssertions.add(false);
427             }
428 
429         };
430 
431         upnpService.getControlPoint().execute(callback);
432 
433         Object serviceImpl = service.getManager().getImplementation();
434 
435         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 2);
436         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 1, 2);
437 
438         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 3);
439         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 2, 3);
440 
441         Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 4);
442         service.getManager().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 3, 4);
443 
444         assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
445 
446         callback.end();
447 
448         assertEquals(testAssertions.size(), 4);
449         for (Boolean testAssertion : testAssertions) {
450             assertTrue(testAssertion);
451         }
452 
453         assertEquals(upnpService.getRouter().getSentStreamRequestMessages().size(), 0);
454     }
455 
456 
457 }