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  
16  package org.fourthline.cling.registry;
17  
18  import org.fourthline.cling.UpnpService;
19  import org.fourthline.cling.UpnpServiceConfiguration;
20  import org.fourthline.cling.model.DiscoveryOptions;
21  import org.fourthline.cling.model.ExpirationDetails;
22  import org.fourthline.cling.model.ServiceReference;
23  import org.fourthline.cling.model.gena.LocalGENASubscription;
24  import org.fourthline.cling.model.gena.RemoteGENASubscription;
25  import org.fourthline.cling.model.meta.Device;
26  import org.fourthline.cling.model.meta.LocalDevice;
27  import org.fourthline.cling.model.meta.RemoteDevice;
28  import org.fourthline.cling.model.meta.RemoteDeviceIdentity;
29  import org.fourthline.cling.model.meta.Service;
30  import org.fourthline.cling.model.resource.Resource;
31  import org.fourthline.cling.model.types.DeviceType;
32  import org.fourthline.cling.model.types.ServiceType;
33  import org.fourthline.cling.model.types.UDN;
34  import org.fourthline.cling.protocol.ProtocolFactory;
35  
36  import javax.enterprise.context.ApplicationScoped;
37  import javax.inject.Inject;
38  import java.net.URI;
39  import java.util.ArrayList;
40  import java.util.Collection;
41  import java.util.Collections;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Set;
46  import java.util.logging.Level;
47  import java.util.logging.Logger;
48  
49  /**
50   * Default implementation of {@link Registry}.
51   *
52   * @author Christian Bauer
53   */
54  @ApplicationScoped
55  public class RegistryImpl implements Registry {
56  
57      private static Logger log = Logger.getLogger(Registry.class.getName());
58  
59      protected UpnpService upnpService;
60      protected RegistryMaintainer registryMaintainer;
61      protected final Set<RemoteGENASubscription> pendingSubscriptionsLock = new HashSet<>();
62  
63      public RegistryImpl() {
64      }
65  
66      /**
67       * Starts background maintenance immediately.
68       */
69      @Inject
70      public RegistryImpl(UpnpService upnpService) {
71          log.fine("Creating Registry: " + getClass().getName());
72  
73          this.upnpService = upnpService;
74  
75          log.fine("Starting registry background maintenance...");
76          registryMaintainer = createRegistryMaintainer();
77          if (registryMaintainer != null) {
78              getConfiguration().getRegistryMaintainerExecutor().execute(registryMaintainer);
79          }
80      }
81  
82      public UpnpService getUpnpService() {
83          return upnpService;
84      }
85  
86      public UpnpServiceConfiguration getConfiguration() {
87          return getUpnpService().getConfiguration();
88      }
89  
90      public ProtocolFactory getProtocolFactory() {
91          return getUpnpService().getProtocolFactory();
92      }
93  
94      protected RegistryMaintainer createRegistryMaintainer() {
95          return new RegistryMaintainer(
96                  this,
97                  getConfiguration().getRegistryMaintenanceIntervalMillis()
98          );
99      }
100 
101     // #################################################################################################
102 
103     protected final Set<RegistryListener> registryListeners = new HashSet<>();
104     protected final Set<RegistryItem<URI, Resource>> resourceItems = new HashSet<>();
105     protected final List<Runnable> pendingExecutions = new ArrayList<>();
106 
107     protected final RemoteItems remoteItems = new RemoteItems(this);
108     protected final LocalItems localItems = new LocalItems(this);
109 
110     // #################################################################################################
111 
112     synchronized public void addListener(RegistryListener listener) {
113         registryListeners.add(listener);
114     }
115 
116     synchronized public void removeListener(RegistryListener listener) {
117         registryListeners.remove(listener);
118     }
119 
120     synchronized public Collection<RegistryListener> getListeners() {
121         return Collections.unmodifiableCollection(registryListeners);
122     }
123 
124     synchronized public boolean notifyDiscoveryStart(final RemoteDevice device) {
125         // Exit if we have it already, this is atomic inside this method, finally
126         if (getUpnpService().getRegistry().getRemoteDevice(device.getIdentity().getUdn(), true) != null) {
127             log.finer("Not notifying listeners, already registered: " + device);
128             return false;
129         }
130         for (final RegistryListener listener : getListeners()) {
131             getConfiguration().getRegistryListenerExecutor().execute(
132                     new Runnable() {
133                         public void run() {
134                             listener.remoteDeviceDiscoveryStarted(RegistryImpl.this, device);
135                         }
136                     }
137             );
138         }
139         return true;
140     }
141 
142     synchronized public void notifyDiscoveryFailure(final RemoteDevice device, final Exception ex) {
143         for (final RegistryListener listener : getListeners()) {
144             getConfiguration().getRegistryListenerExecutor().execute(
145                     new Runnable() {
146                         public void run() {
147                             listener.remoteDeviceDiscoveryFailed(RegistryImpl.this, device, ex);
148                         }
149                     }
150             );
151         }
152     }
153 
154     // #################################################################################################
155 
156     synchronized public void addDevice(LocalDevice localDevice) {
157         localItems.add(localDevice);
158     }
159 
160     synchronized public void addDevice(LocalDevice localDevice, DiscoveryOptions options) {
161         localItems.add(localDevice, options);
162     }
163 
164     synchronized public void setDiscoveryOptions(UDN udn, DiscoveryOptions options) {
165         localItems.setDiscoveryOptions(udn, options);
166     }
167 
168     synchronized public DiscoveryOptions getDiscoveryOptions(UDN udn) {
169         return localItems.getDiscoveryOptions(udn);
170     }
171 
172     synchronized public void addDevice(RemoteDevice remoteDevice) {
173         remoteItems.add(remoteDevice);
174     }
175 
176     synchronized public boolean update(RemoteDeviceIdentity rdIdentity) {
177         return remoteItems.update(rdIdentity);
178     }
179 
180     synchronized public boolean removeDevice(LocalDevice localDevice) {
181         return localItems.remove(localDevice);
182     }
183 
184     synchronized public boolean removeDevice(RemoteDevice remoteDevice) {
185         return remoteItems.remove(remoteDevice);
186     }
187 
188     synchronized public void removeAllLocalDevices() {
189         localItems.removeAll();
190     }
191 
192     synchronized public void removeAllRemoteDevices() {
193         remoteItems.removeAll();
194     }
195 
196     synchronized public boolean removeDevice(UDN udn) {
197         Device device = getDevice(udn, true);
198         if (device != null && device instanceof LocalDevice)
199             return removeDevice((LocalDevice) device);
200         if (device != null && device instanceof RemoteDevice)
201             return removeDevice((RemoteDevice) device);
202         return false;
203     }
204 
205     synchronized public Device getDevice(UDN udn, boolean rootOnly) {
206         Device device;
207         if ((device = localItems.get(udn, rootOnly)) != null) return device;
208         if ((device = remoteItems.get(udn, rootOnly)) != null) return device;
209         return null;
210     }
211 
212     synchronized public LocalDevice getLocalDevice(UDN udn, boolean rootOnly) {
213         return localItems.get(udn, rootOnly);
214     }
215 
216     synchronized public RemoteDevice getRemoteDevice(UDN udn, boolean rootOnly) {
217         return remoteItems.get(udn, rootOnly);
218     }
219 
220     synchronized public Collection<LocalDevice> getLocalDevices() {
221         return Collections.unmodifiableCollection(localItems.get());
222     }
223 
224     synchronized public Collection<RemoteDevice> getRemoteDevices() {
225         return Collections.unmodifiableCollection(remoteItems.get());
226     }
227 
228     synchronized public Collection<Device> getDevices() {
229         Set all = new HashSet<>();
230         all.addAll(localItems.get());
231         all.addAll(remoteItems.get());
232         return Collections.unmodifiableCollection(all);
233     }
234 
235     synchronized public Collection<Device> getDevices(DeviceType deviceType) {
236         Collection<Device> devices = new HashSet<>();
237 
238         devices.addAll(localItems.get(deviceType));
239         devices.addAll(remoteItems.get(deviceType));
240 
241         return Collections.unmodifiableCollection(devices);
242     }
243 
244     synchronized public Collection<Device> getDevices(ServiceType serviceType) {
245         Collection<Device> devices = new HashSet<>();
246 
247         devices.addAll(localItems.get(serviceType));
248         devices.addAll(remoteItems.get(serviceType));
249 
250         return Collections.unmodifiableCollection(devices);
251     }
252 
253     synchronized public Service getService(ServiceReference serviceReference) {
254         Device device;
255         if ((device = getDevice(serviceReference.getUdn(), false)) != null) {
256             return device.findService(serviceReference.getServiceId());
257         }
258         return null;
259     }
260 
261     // #################################################################################################
262 
263     synchronized public Resource getResource(URI pathQuery) throws IllegalArgumentException {
264         if (pathQuery.isAbsolute()) {
265             throw new IllegalArgumentException("Resource URI can not be absolute, only path and query:" + pathQuery);
266         }
267 
268         // Note: Uses field access on resourceItems for performance reasons
269 
270 		for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
271         	Resource resource = resourceItem.getItem();
272         	if (resource.matches(pathQuery)) {
273                 return resource;
274             }
275         }
276 
277         // TODO: UPNP VIOLATION: Fuppes on my ReadyNAS thinks it's a cool idea to add a slash at the end of the callback URI...
278         // It also cuts off any query parameters in the callback URL - nice!
279         if (pathQuery.getPath().endsWith("/")) {
280             URI pathQueryWithoutSlash = URI.create(pathQuery.toString().substring(0, pathQuery.toString().length() - 1));
281 
282  			for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
283             	Resource resource = resourceItem.getItem();
284             	if (resource.matches(pathQueryWithoutSlash)) {
285                     return resource;
286                 }
287             }
288         }
289 
290         return null;
291     }
292 
293     synchronized public <T extends Resource> T getResource(Class<T> resourceType, URI pathQuery) throws IllegalArgumentException {
294         Resource resource = getResource(pathQuery);
295         if (resource != null && resourceType.isAssignableFrom(resource.getClass())) {
296             return (T) resource;
297         }
298         return null;
299     }
300 
301     synchronized public Collection<Resource> getResources() {
302         Collection<Resource> s = new HashSet<>();
303         for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
304             s.add(resourceItem.getItem());
305         }
306         return s;
307     }
308 
309     synchronized public <T extends Resource> Collection<T> getResources(Class<T> resourceType) {
310         Collection<T> s = new HashSet<>();
311         for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
312             if (resourceType.isAssignableFrom(resourceItem.getItem().getClass()))
313                 s.add((T) resourceItem.getItem());
314         }
315         return s;
316     }
317 
318     synchronized public void addResource(Resource resource) {
319         addResource(resource, ExpirationDetails.UNLIMITED_AGE);
320     }
321 
322     synchronized public void addResource(Resource resource, int maxAgeSeconds) {
323         RegistryItem resourceItem = new RegistryItem(resource.getPathQuery(), resource, maxAgeSeconds);
324         resourceItems.remove(resourceItem);
325         resourceItems.add(resourceItem);
326     }
327 
328     synchronized public boolean removeResource(Resource resource) {
329         return resourceItems.remove(new RegistryItem(resource.getPathQuery()));
330     }
331 
332     // #################################################################################################
333 
334     synchronized public void addLocalSubscription(LocalGENASubscription subscription) {
335         localItems.addSubscription(subscription);
336     }
337 
338     synchronized public LocalGENASubscription getLocalSubscription(String subscriptionId) {
339         return localItems.getSubscription(subscriptionId);
340     }
341 
342     synchronized public boolean updateLocalSubscription(LocalGENASubscription subscription) {
343         return localItems.updateSubscription(subscription);
344     }
345 
346     synchronized public boolean removeLocalSubscription(LocalGENASubscription subscription) {
347         return localItems.removeSubscription(subscription);
348     }
349 
350     synchronized public void addRemoteSubscription(RemoteGENASubscription subscription) {
351         remoteItems.addSubscription(subscription);
352     }
353 
354     synchronized public RemoteGENASubscription getRemoteSubscription(String subscriptionId) {
355         return remoteItems.getSubscription(subscriptionId);
356     }
357 
358     synchronized public void updateRemoteSubscription(RemoteGENASubscription subscription) {
359         remoteItems.updateSubscription(subscription);
360     }
361 
362     synchronized public void removeRemoteSubscription(RemoteGENASubscription subscription) {
363         remoteItems.removeSubscription(subscription);
364     }
365 
366     /* ############################################################################################################ */
367 
368    	synchronized public void advertiseLocalDevices() {
369    		localItems.advertiseLocalDevices();
370    	}
371 
372     /* ############################################################################################################ */
373 
374     // When you call this, make sure you have the Router lock before this lock is obtained!
375     synchronized public void shutdown() {
376         log.fine("Shutting down registry...");
377 
378         if (registryMaintainer != null)
379             registryMaintainer.stop();
380         
381         // Final cleanup run to flush out pending executions which might
382         // not have been caught by the maintainer before it stopped
383         log.finest("Executing final pending operations on shutdown: " + pendingExecutions.size());
384         runPendingExecutions(false);
385 
386         for (RegistryListener listener : registryListeners) {
387             listener.beforeShutdown(this);
388         }
389 
390         RegistryItem<URI, Resource>[] resources = resourceItems.toArray(new RegistryItem[resourceItems.size()]);
391         for (RegistryItem<URI, Resource> resourceItem : resources) {
392             resourceItem.getItem().shutdown();
393         }
394 
395         remoteItems.shutdown();
396         localItems.shutdown();
397 
398         for (RegistryListener listener : registryListeners) {
399             listener.afterShutdown();
400         }
401     }
402 
403     synchronized public void pause() {
404         if (registryMaintainer != null) {
405             log.fine("Pausing registry maintenance");
406             runPendingExecutions(true);
407             registryMaintainer.stop();
408             registryMaintainer = null;
409         }
410     }
411 
412     synchronized public void resume() {
413         if (registryMaintainer == null) {
414             log.fine("Resuming registry maintenance");
415             remoteItems.resume();
416             registryMaintainer = createRegistryMaintainer();
417             if (registryMaintainer != null) {
418                 getConfiguration().getRegistryMaintainerExecutor().execute(registryMaintainer);
419             }
420         }
421     }
422 
423     synchronized public boolean isPaused() {
424         return registryMaintainer == null;
425     }
426 
427     /* ############################################################################################################ */
428 
429     synchronized void maintain() {
430 
431         if (log.isLoggable(Level.FINEST))
432             log.finest("Maintaining registry...");
433 
434         // Remove expired resources
435         Iterator<RegistryItem<URI, Resource>> it = resourceItems.iterator();
436         while (it.hasNext()) {
437             RegistryItem<URI, Resource> item = it.next();
438             if (item.getExpirationDetails().hasExpired()) {
439                 if (log.isLoggable(Level.FINER))
440                     log.finer("Removing expired resource: " + item);
441                 it.remove();
442             }
443         }
444 
445         // Let each resource do its own maintenance
446         for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
447             resourceItem.getItem().maintain(
448                     pendingExecutions,
449                     resourceItem.getExpirationDetails()
450             );
451         }
452 
453         // These add all their operations to the pendingExecutions queue
454         remoteItems.maintain();
455         localItems.maintain();
456 
457         // We now run the queue asynchronously so the maintenance thread can continue its loop undisturbed
458         runPendingExecutions(true);
459     }
460 
461     synchronized void executeAsyncProtocol(Runnable runnable) {
462         pendingExecutions.add(runnable);
463     }
464 
465     synchronized void runPendingExecutions(boolean async) {
466         if (log.isLoggable(Level.FINEST))
467             log.finest("Executing pending operations: " + pendingExecutions.size());
468         for (Runnable pendingExecution : pendingExecutions) {
469             if (async)
470                 getConfiguration().getAsyncProtocolExecutor().execute(pendingExecution);
471             else
472                 pendingExecution.run();
473         }
474         if (pendingExecutions.size() > 0) {
475             pendingExecutions.clear();
476         }
477     }
478 
479     /* ############################################################################################################ */
480 
481     public void printDebugLog() {
482         if (log.isLoggable(Level.FINE)) {
483             log.fine("====================================    REMOTE   ================================================");
484 
485             for (RemoteDevice remoteDevice : remoteItems.get()) {
486                 log.fine(remoteDevice.toString());
487             }
488 
489             log.fine("====================================    LOCAL    ================================================");
490 
491             for (LocalDevice localDevice : localItems.get()) {
492                 log.fine(localDevice.toString());
493             }
494 
495             log.fine("====================================  RESOURCES  ================================================");
496 
497             for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
498                 log.fine(resourceItem.toString());
499             }
500 
501             log.fine("=================================================================================================");
502 
503         }
504 
505     }
506 
507  	@Override
508 	public void registerPendingRemoteSubscription(RemoteGENASubscription subscription) {
509 		synchronized (pendingSubscriptionsLock) {
510             pendingSubscriptionsLock.add(subscription);
511         }
512 	}
513 	
514 	@Override
515 	public void unregisterPendingRemoteSubscription(RemoteGENASubscription subscription) {
516         synchronized (pendingSubscriptionsLock) {
517             if(pendingSubscriptionsLock.remove(subscription)) {
518                 pendingSubscriptionsLock.notifyAll();
519             }
520         }
521 	}
522 
523     @Override
524     public RemoteGENASubscription getWaitRemoteSubscription(String subscriptionId) {
525         synchronized (pendingSubscriptionsLock) {
526             RemoteGENASubscription subscription = getRemoteSubscription(subscriptionId);
527             while (subscription == null && !pendingSubscriptionsLock.isEmpty()) {
528                 try {
529                     log.finest("Subscription not found, waiting for pending subscription procedure to terminate.");
530                     pendingSubscriptionsLock.wait();
531                 } catch (InterruptedException e) {
532                 }
533                 subscription = getRemoteSubscription(subscriptionId);
534             }
535             return subscription;
536         }
537     }
538 
539 }