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.registry;
16  
17  import org.fourthline.cling.binding.xml.DeviceDescriptorBinder;
18  import org.fourthline.cling.mock.MockRouter;
19  import org.fourthline.cling.mock.MockUpnpService;
20  import org.fourthline.cling.model.message.StreamResponseMessage;
21  import org.fourthline.cling.model.message.header.ContentTypeHeader;
22  import org.fourthline.cling.model.meta.LocalDevice;
23  import org.fourthline.cling.model.meta.RemoteDevice;
24  import org.fourthline.cling.model.meta.RemoteService;
25  import org.fourthline.cling.model.meta.Service;
26  import org.fourthline.cling.model.profile.RemoteClientInfo;
27  import org.fourthline.cling.model.types.UDAServiceId;
28  import org.fourthline.cling.protocol.RetrieveRemoteDescriptors;
29  import org.fourthline.cling.registry.DefaultRegistryListener;
30  import org.fourthline.cling.registry.Registry;
31  import org.fourthline.cling.test.data.SampleData;
32  import org.testng.annotations.Test;
33  import org.xml.sax.SAXParseException;
34  
35  import static org.testng.Assert.assertEquals;
36  
37  /**
38   * Listening to registry changes
39   * <p>
40   * The <code>RegistryListener</code> is your primary API when discovering devices and services with your
41   * control point. UPnP operates asynchronous, so advertisements (either <em>alive</em> or <em>byebye</em>)
42   * of devices can occur at any time. Responses to your network search messages are also asynchronous.
43   * </p>
44   * <p>
45   * This is the interface:
46   * </p>
47   * <a class="citation" href="javacode://example.registry.RegistryListenerTest.RegistryListener"/>
48   * <p>
49   * Typically you don't want to implement all of these methods. Some are only useful if you write
50   * a service or a generic control point. Most of the time you want to be notified when a particular
51   * device with a particular service appears on your network. So it is much easier to extend
52   * the <code>DefaultRegistryListener</code>, which has empty implementations for all methods of
53   * the interface, and only override the methods you need.
54   * </p>
55   * <a class="citation" href="javadoc://this#quickstartListener" style="read-title: false"/>
56   * <a class="citation" href="javadoc://this#regularListener" style="read-title: false"/>
57   */
58  public class RegistryListenerTest {
59  
60      // Just for documentation inclusion!
61      public interface RegistryListener {
62  
63          public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device);
64  
65          public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex);
66  
67          public void remoteDeviceAdded(Registry registry, RemoteDevice device);
68  
69          public void remoteDeviceUpdated(Registry registry, RemoteDevice device);
70  
71          public void remoteDeviceRemoved(Registry registry, RemoteDevice device);
72  
73          public void localDeviceAdded(Registry registry, LocalDevice device);
74  
75          public void localDeviceRemoved(Registry registry, LocalDevice device);
76  
77      }
78  
79      /**
80       * <p>
81       * The <code>remoteDeviceDiscoveryStarted()</code> and <code>remoteDeviceDiscoveryFailed()</code>
82       * methods are completely optional but useful on slow machines (such as Android handsets). Cling
83       * will retrieve and initialize all device metadata for each UPnP device before it will announce
84       * it on the <code>Registry</code>. UPnP metadata is split into several XML descriptors, so retrieval
85       * via HTTP of these descriptors, parsing, and validating all metadata for a complex UPnP device
86       * and service model can take several seconds. These two methods allow you to access the device
87       * as soon as possible, after the first descriptor has been retrieved and parsed. At this time
88       * the services metadata is however not available:
89       * </p>
90       * <a class="citation" href="javacode://example.registry.RegistryListenerTest.QuickstartRegistryListener" style="exclude: EXC1, EXC2;"/>
91       * <p>
92       * This is how you register and activate a listener:
93       * </p>
94       * <a class="citation" href="javacode://this" style="include: INC1"/>
95       */
96      @Test
97      public void quickstartListener() throws Exception {
98  
99          final RemoteDevice discoveredDevice = new RemoteDevice(SampleData.createRemoteDeviceIdentity());
100         final RemoteDevice hydratedDevice = SampleData.createRemoteDevice();
101 
102         MockUpnpService upnpService = new MockUpnpService() {
103             @Override
104             protected MockRouter createRouter() {
105                 return new MockRouter(getConfiguration(), getProtocolFactory()) {
106                     @Override
107                     public StreamResponseMessage[] getStreamResponseMessages() {
108                         try {
109                             String deviceDescriptorXML =
110                                 getConfiguration().getDeviceDescriptorBinderUDA10().generate(
111                                     hydratedDevice,
112                                     new RemoteClientInfo(),
113                                     getConfiguration().getNamespace()
114                                 );
115                             String serviceOneXML =
116                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[0]);
117                             String serviceTwoXML =
118                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[1]);
119                             String serviceThreeXML =
120                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[2]);
121                             return new StreamResponseMessage[]{
122                                 new StreamResponseMessage(deviceDescriptorXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
123                                 new StreamResponseMessage(serviceOneXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
124                                 new StreamResponseMessage(serviceTwoXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
125                                 new StreamResponseMessage(serviceThreeXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8)
126                             };
127                         } catch (Exception ex) {
128                             throw new RuntimeException(ex);
129                         }
130                     }
131                 };
132             }
133         };
134 
135         QuickstartRegistryListener listener = new QuickstartRegistryListener(); // DOC: INC1
136         upnpService.getRegistry().addListener(listener);                        // DOC: INC1
137 
138         RetrieveRemoteDescriptors retrieveDescriptors = new RetrieveRemoteDescriptors(upnpService, discoveredDevice);
139         retrieveDescriptors.run();
140 
141         assertEquals(listener.valid, true);
142     }
143 
144     @Test
145     public void failureQuickstartListener() throws Exception {
146 
147         final RemoteDevice discoveredDevice = new RemoteDevice(SampleData.createRemoteDeviceIdentity());
148         final RemoteDevice hydratedDevice = SampleData.createRemoteDevice();
149 
150         MockUpnpService upnpService = new MockUpnpService() {
151             @Override
152             protected MockRouter createRouter() {
153                 return new MockRouter(getConfiguration(), getProtocolFactory()) {
154                     @Override
155                     public StreamResponseMessage[] getStreamResponseMessages() {
156                         String deviceDescriptorXML;
157                         DeviceDescriptorBinder binder = getConfiguration().getDeviceDescriptorBinderUDA10();
158                         try {
159                             deviceDescriptorXML =
160                                 binder.generate(
161                                     hydratedDevice,
162                                     new RemoteClientInfo(),
163                                     getConfiguration().getNamespace()
164                                 );
165                         } catch (Exception ex) {
166                             throw new RuntimeException(ex);
167                         }
168                         return new StreamResponseMessage[]{
169                             new StreamResponseMessage(deviceDescriptorXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
170                             new StreamResponseMessage(
171                                 "<?xml>THIS SHOULD BE SERVER DESCRIPTOR XML, BUT WE WANT IT TO FAIL WITH SAXParseException.",
172                                 ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8
173                             ),
174                         };
175                     }
176                 };
177             }
178         };
179 
180         FailureQuickstartRegistryListener listener = new FailureQuickstartRegistryListener();
181         upnpService.getRegistry().addListener(listener);
182 
183         RetrieveRemoteDescriptors retrieveDescriptors = new RetrieveRemoteDescriptors(upnpService, discoveredDevice);
184         retrieveDescriptors.run();
185 
186         assertEquals(listener.valid, true);
187     }
188 
189     public class QuickstartRegistryListener extends DefaultRegistryListener {
190         public boolean valid = false; // DOC: EXC1
191 
192         @Override
193         public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
194 
195             // You can already use the device here and you can see which services it will have
196             assertEquals(device.findServices().length, 3);
197 
198             // But you can't use the services
199             for (RemoteService service : device.findServices()) {
200                 assertEquals(service.getActions().length, 0);
201                 assertEquals(service.getStateVariables().length, 0);
202             }
203             valid = true; // DOC: EXC2
204         }
205 
206         @Override
207         public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
208             // You might want to drop the device, its services couldn't be hydrated
209         }
210     }
211 
212     public class FailureQuickstartRegistryListener extends DefaultRegistryListener {
213         public boolean valid = false;
214 
215         @Override
216         public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception ex) {
217             if(ex.getCause() instanceof SAXParseException)
218                 valid = true;
219         }
220     }
221 
222     /**
223      * <p>
224      * Most of the time, on any device that is faster than a cellphone, your listeners will look
225      * like this:
226      * </p>
227      * <a class="citation" href="javacode://example.registry.RegistryListenerTest.MyListener" style="exclude: EXC1, EXC2, EXC3;"/>
228      * <p>
229      * The device metadata of the parameter to <code>remoteDeviceAdded()</code> is fully hydrated, all
230      * of its services, actions, and state variables are available. You can continue with this metadata,
231      * writing action invocations and event monitoring callbacks. You also might want to react accordingly
232      * when the device disappears from the network.
233      * </p>
234      */
235     @Test
236     public void regularListener() throws Exception {
237 
238         final RemoteDevice discoveredDevice = new RemoteDevice(SampleData.createRemoteDeviceIdentity());
239         final RemoteDevice hydratedDevice = SampleData.createRemoteDevice();
240 
241         MockUpnpService upnpService = new MockUpnpService() {
242             @Override
243             protected MockRouter createRouter() {
244                 return new MockRouter(getConfiguration(), getProtocolFactory()) {
245                     @Override
246                     public StreamResponseMessage[] getStreamResponseMessages() {
247                         try {
248                             String deviceDescriptorXML =
249                                 getConfiguration().getDeviceDescriptorBinderUDA10().generate(
250                                     hydratedDevice,
251                                     new RemoteClientInfo(),
252                                     getConfiguration().getNamespace()
253                                 );
254                             String serviceOneXML =
255                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[0]);
256                             String serviceTwoXML =
257                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[1]);
258                             String serviceThreeXML =
259                                 getConfiguration().getServiceDescriptorBinderUDA10().generate(hydratedDevice.findServices()[2]);
260                             return new StreamResponseMessage[]{
261                                 new StreamResponseMessage(deviceDescriptorXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
262                                 new StreamResponseMessage(serviceOneXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
263                                 new StreamResponseMessage(serviceTwoXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8),
264                                 new StreamResponseMessage(serviceThreeXML, ContentTypeHeader.DEFAULT_CONTENT_TYPE_UTF8)
265                             };
266                         } catch (Exception ex) {
267                             throw new RuntimeException(ex);
268                         }
269                     }
270                 };
271             }
272         };
273 
274         MyListener listener = new MyListener();
275         upnpService.getRegistry().addListener(listener);
276 
277         RetrieveRemoteDescriptors retrieveDescriptors = new RetrieveRemoteDescriptors(upnpService, discoveredDevice);
278         retrieveDescriptors.run();
279 
280         upnpService.getRegistry().removeAllRemoteDevices();
281 
282         assertEquals(listener.added, true);
283         assertEquals(listener.removed, true);
284     }
285 
286     public class MyListener extends DefaultRegistryListener {
287         public boolean added = false; // DOC: EXC1
288         public boolean removed = false; // DOC: EXC1
289 
290         @Override
291         public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
292             Service myService = device.findService(new UDAServiceId("MY-SERVICE-123"));
293             if (myService != null) {
294                 // Do something with the discovered service
295                 added = true; // DOC: EXC2
296             }
297         }
298 
299         @Override
300         public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
301             // Stop using the service if this is the same device, it's gone now
302             removed = true; // DOC: EXC3
303         }
304     }
305 
306 }