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.model.resource.Resource;
19  import org.fourthline.cling.model.gena.CancelReason;
20  import org.fourthline.cling.model.gena.RemoteGENASubscription;
21  import org.fourthline.cling.model.meta.LocalDevice;
22  import org.fourthline.cling.model.meta.RemoteDevice;
23  import org.fourthline.cling.model.meta.RemoteDeviceIdentity;
24  import org.fourthline.cling.model.types.UDN;
25  
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.logging.Level;
34  import java.util.logging.Logger;
35  
36  /**
37   * Internal class, required by {@link RegistryImpl}.
38   *
39   * @author Christian Bauer
40   */
41  class RemoteItems extends RegistryItems<RemoteDevice, RemoteGENASubscription> {
42  
43      private static Logger log = Logger.getLogger(Registry.class.getName());
44  
45      RemoteItems(RegistryImpl registry) {
46          super(registry);
47      }
48  
49      /**
50       * Adds the given remote device to the registry, or udpates its expiration timestamp.
51       * <p>
52       * This method first checks if there is a remote device with the same UDN already registered. If so, it
53       * updates the expiration timestamp of the remote device without notifying any registry listeners. If the
54       * device is truly new, all its resources are tested for conflicts with existing resources in the registry's
55       * namespace, then it is added to the registry and listeners are notified that a new fully described remote
56       * device is now available.
57       * </p>
58       *
59       * @param device The remote device to be added
60       */
61      void add(final RemoteDevice device) {
62  
63          if (update(device.getIdentity())) {
64              log.fine("Ignoring addition, device already registered: " + device);
65              return;
66          }
67  
68          Resource[] resources = getResources(device);
69  
70          for (Resource deviceResource : resources) {
71              log.fine("Validating remote device resource; " + deviceResource);
72              if (registry.getResource(deviceResource.getPathQuery()) != null) {
73                  throw new RegistrationException("URI namespace conflict with already registered resource: " + deviceResource);
74              }
75          }
76  
77          for (Resource validatedResource : resources) {
78              registry.addResource(validatedResource);
79              log.fine("Added remote device resource: " + validatedResource);
80          }
81  
82          // Override the device's maximum age if configured (systems without multicast support)
83          RegistryItem item = new RegistryItem(
84                  device.getIdentity().getUdn(),
85                  device,
86                  registry.getConfiguration().getRemoteDeviceMaxAgeSeconds() != null
87                          ? registry.getConfiguration().getRemoteDeviceMaxAgeSeconds()
88                          : device.getIdentity().getMaxAgeSeconds()
89          );
90          log.fine("Adding hydrated remote device to registry with "
91                           + item.getExpirationDetails().getMaxAgeSeconds() + " seconds expiration: " + device);
92          getDeviceItems().add(item);
93  
94          if (log.isLoggable(Level.FINEST)) {
95              StringBuilder sb = new StringBuilder();
96              sb.append("\n");
97              sb.append("-------------------------- START Registry Namespace -----------------------------------\n");
98              for (Resource resource : registry.getResources()) {
99                  sb.append(resource).append("\n");
100             }
101             sb.append("-------------------------- END Registry Namespace -----------------------------------");
102             log.finest(sb.toString());
103         }
104 
105         // Only notify the listeners when the device is fully usable
106         log.fine("Completely hydrated remote device graph available, calling listeners: " + device);
107         for (final RegistryListener listener : registry.getListeners()) {
108             registry.getConfiguration().getRegistryListenerExecutor().execute(
109                     new Runnable() {
110                         public void run() {
111                             listener.remoteDeviceAdded(registry, device);
112                         }
113                     }
114             );
115         }
116 
117     }
118 
119     boolean update(RemoteDeviceIdentity rdIdentity) {
120 
121         for (LocalDevice localDevice : registry.getLocalDevices()) {
122             if (localDevice.findDevice(rdIdentity.getUdn()) != null) {
123                 log.fine("Ignoring update, a local device graph contains UDN");
124                 return true;
125             }
126         }
127 
128         RemoteDevice registeredRemoteDevice = get(rdIdentity.getUdn(), false);
129         if (registeredRemoteDevice != null) {
130 
131             if (!registeredRemoteDevice.isRoot()) {
132                 log.fine("Updating root device of embedded: " + registeredRemoteDevice);
133                 registeredRemoteDevice = registeredRemoteDevice.getRoot();
134             }
135 
136             // Override the device's maximum age if configured (systems without multicast support)
137             final RegistryItem<UDN, RemoteDevice> item = new RegistryItem<>(
138                     registeredRemoteDevice.getIdentity().getUdn(),
139                     registeredRemoteDevice,
140                     registry.getConfiguration().getRemoteDeviceMaxAgeSeconds() != null
141                             ? registry.getConfiguration().getRemoteDeviceMaxAgeSeconds()
142                             : rdIdentity.getMaxAgeSeconds()
143             );
144 
145             log.fine("Updating expiration of: " + registeredRemoteDevice);
146             getDeviceItems().remove(item);
147             getDeviceItems().add(item);
148 
149             log.fine("Remote device updated, calling listeners: " + registeredRemoteDevice);
150             for (final RegistryListener listener : registry.getListeners()) {
151                 registry.getConfiguration().getRegistryListenerExecutor().execute(
152                         new Runnable() {
153                             public void run() {
154                                 listener.remoteDeviceUpdated(registry, item.getItem());
155                             }
156                         }
157                 );
158             }
159 
160             return true;
161 
162         }
163         return false;
164     }
165 
166     /**
167      * Removes the given device from the registry and notifies registry listeners.
168      *
169      * @param remoteDevice The device to remove from the registry.
170      * @return <tt>true</tt> if the given device was found and removed from the registry, false if it wasn't registered.
171      */
172     boolean remove(final RemoteDevice remoteDevice) {
173         return remove(remoteDevice, false);
174     }
175 
176     boolean remove(final RemoteDevice remoteDevice, boolean shuttingDown) throws RegistrationException {
177         final RemoteDevice registeredDevice = get(remoteDevice.getIdentity().getUdn(), true);
178         if (registeredDevice != null) {
179 
180             log.fine("Removing remote device from registry: " + remoteDevice);
181 
182             // Resources
183             for (Resource deviceResource : getResources(registeredDevice)) {
184                 if (registry.removeResource(deviceResource)) {
185                     log.fine("Unregistered resource: " + deviceResource);
186                 }
187             }
188 
189             // Active subscriptions
190             Iterator<RegistryItem<String, RemoteGENASubscription>> it = getSubscriptionItems().iterator();
191             while (it.hasNext()) {
192                 final RegistryItem<String, RemoteGENASubscription> outgoingSubscription = it.next();
193 
194                 UDN subscriptionForUDN =
195                         outgoingSubscription.getItem().getService().getDevice().getIdentity().getUdn();
196 
197                 if (subscriptionForUDN.equals(registeredDevice.getIdentity().getUdn())) {
198                     log.fine("Removing outgoing subscription: " + outgoingSubscription.getKey());
199                     it.remove();
200                     if (!shuttingDown) {
201                         registry.getConfiguration().getRegistryListenerExecutor().execute(
202                                 new Runnable() {
203                                     public void run() {
204                                         outgoingSubscription.getItem().end(CancelReason.DEVICE_WAS_REMOVED, null);
205                                     }
206                                 }
207                         );
208                     }
209                 }
210             }
211 
212             // Only notify listeners if we are NOT in the process of shutting down the registry
213             if (!shuttingDown) {
214                 for (final RegistryListener listener : registry.getListeners()) {
215                     registry.getConfiguration().getRegistryListenerExecutor().execute(
216                             new Runnable() {
217                                 public void run() {
218                                     listener.remoteDeviceRemoved(registry, registeredDevice);
219                                 }
220                             }
221                     );
222                 }
223             }
224 
225             // Finally, remove the device from the registry
226             getDeviceItems().remove(new RegistryItem(registeredDevice.getIdentity().getUdn()));
227 
228             return true;
229         }
230 
231         return false;
232     }
233 
234     void removeAll() {
235         removeAll(false);
236     }
237 
238     void removeAll(boolean shuttingDown) {
239         RemoteDevice[] allDevices = get().toArray(new RemoteDevice[get().size()]);
240         for (RemoteDevice device : allDevices) {
241             remove(device, shuttingDown);
242         }
243     }
244 
245     /* ############################################################################################################ */
246 
247     void start() {
248         // Noop
249     }
250 
251     void maintain() {
252 
253         if (getDeviceItems().isEmpty()) return;
254 
255         // Remove expired remote devices
256         Map<UDN, RemoteDevice> expiredRemoteDevices = new HashMap<>();
257         for (RegistryItem<UDN, RemoteDevice> remoteItem : getDeviceItems()) {
258             if (log.isLoggable(Level.FINEST))
259                 log.finest("Device '" + remoteItem.getItem() + "' expires in seconds: "
260                                    + remoteItem.getExpirationDetails().getSecondsUntilExpiration());
261             if (remoteItem.getExpirationDetails().hasExpired(false)) {
262                 expiredRemoteDevices.put(remoteItem.getKey(), remoteItem.getItem());
263             }
264         }
265         for (RemoteDevice remoteDevice : expiredRemoteDevices.values()) {
266             if (log.isLoggable(Level.FINE))
267                 log.fine("Removing expired: " + remoteDevice);
268             remove(remoteDevice);
269         }
270 
271         // Renew outgoing subscriptions
272         Set<RemoteGENASubscription> expiredOutgoingSubscriptions = new HashSet<>();
273         for (RegistryItem<String, RemoteGENASubscription> item : getSubscriptionItems()) {
274             if (item.getExpirationDetails().hasExpired(true)) {
275                 expiredOutgoingSubscriptions.add(item.getItem());
276             }
277         }
278         for (RemoteGENASubscription subscription : expiredOutgoingSubscriptions) {
279             if (log.isLoggable(Level.FINEST))
280                 log.fine("Renewing outgoing subscription: " + subscription);
281             renewOutgoingSubscription(subscription);
282         }
283     }
284 
285     public void resume() {
286         log.fine("Updating remote device expiration timestamps on resume");
287         List<RemoteDeviceIdentity> toUpdate = new ArrayList<>();
288         for (RegistryItem<UDN, RemoteDevice> remoteItem : getDeviceItems()) {
289             toUpdate.add(remoteItem.getItem().getIdentity());
290         }
291         for (RemoteDeviceIdentity identity : toUpdate) {
292             update(identity);
293         }
294     }
295 
296     void shutdown() {
297         log.fine("Cancelling all outgoing subscriptions to remote devices during shutdown");
298         List<RemoteGENASubscription> remoteSubscriptions = new ArrayList<>();
299         for (RegistryItem<String, RemoteGENASubscription> item : getSubscriptionItems()) {
300             remoteSubscriptions.add(item.getItem());
301         }
302         for (RemoteGENASubscription remoteSubscription : remoteSubscriptions) {
303             // This will remove the active subscription from the registry!
304             registry.getProtocolFactory()
305                     .createSendingUnsubscribe(remoteSubscription)
306                     .run();
307         }
308 
309         log.fine("Removing all remote devices from registry during shutdown");
310         removeAll(true);
311     }
312 
313     /* ############################################################################################################ */
314 
315     protected void renewOutgoingSubscription(final RemoteGENASubscription subscription) {
316         registry.executeAsyncProtocol(
317                 registry.getProtocolFactory().createSendingRenewal(subscription)
318         );
319     }
320 }