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.DiscoveryOptions;
19  import org.fourthline.cling.model.resource.Resource;
20  import org.fourthline.cling.model.gena.CancelReason;
21  import org.fourthline.cling.model.gena.LocalGENASubscription;
22  import org.fourthline.cling.model.meta.LocalDevice;
23  import org.fourthline.cling.model.types.UDN;
24  import org.fourthline.cling.protocol.SendingAsync;
25  
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.Map;
32  import java.util.Random;
33  import java.util.Set;
34  import java.util.logging.Logger;
35  
36  /**
37   * Internal class, required by {@link RegistryImpl}.
38   *
39   * @author Christian Bauer
40   */
41  class LocalItems extends RegistryItems<LocalDevice, LocalGENASubscription> {
42  
43      private static Logger log = Logger.getLogger(Registry.class.getName());
44      
45      protected Map<UDN, DiscoveryOptions> discoveryOptions = new HashMap<>();
46      protected long lastAliveIntervalTimestamp = 0;
47  
48      LocalItems(RegistryImpl registry) {
49          super(registry);
50      }
51  
52      protected void setDiscoveryOptions(UDN udn, DiscoveryOptions options) {
53          if (options != null)
54              this.discoveryOptions.put(udn, options);
55          else
56              this.discoveryOptions.remove(udn);
57      }
58  
59      protected DiscoveryOptions getDiscoveryOptions(UDN udn) {
60          return this.discoveryOptions.get(udn);
61      }
62  
63      protected boolean isAdvertised(UDN udn) {
64          // Defaults to true
65          return getDiscoveryOptions(udn) == null || getDiscoveryOptions(udn).isAdvertised();
66      }
67  
68      protected boolean isByeByeBeforeFirstAlive(UDN udn) {
69          // Defaults to false
70          return getDiscoveryOptions(udn) != null && getDiscoveryOptions(udn).isByeByeBeforeFirstAlive();
71      }
72  
73      void add(LocalDevice localDevice) throws RegistrationException {
74          add(localDevice, null);
75      }
76  
77      void add(final LocalDevice localDevice, DiscoveryOptions options) throws RegistrationException {
78  
79          // Always set/override the options, even if we don't end up adding the device
80          setDiscoveryOptions(localDevice.getIdentity().getUdn(), options);
81  
82          if (registry.getDevice(localDevice.getIdentity().getUdn(), false) != null) {
83              log.fine("Ignoring addition, device already registered: " + localDevice);
84              return;
85          }
86  
87          log.fine("Adding local device to registry: " + localDevice);
88  
89          for (Resource deviceResource : getResources(localDevice)) {
90  
91              if (registry.getResource(deviceResource.getPathQuery()) != null) {
92                  throw new RegistrationException("URI namespace conflict with already registered resource: " + deviceResource);
93              }
94  
95              registry.addResource(deviceResource);
96              log.fine("Registered resource: " + deviceResource);
97  
98          }
99  
100         log.fine("Adding item to registry with expiration in seconds: " + localDevice.getIdentity().getMaxAgeSeconds());
101 
102         RegistryItem<UDN, LocalDevice> localItem = new RegistryItem<>(
103                 localDevice.getIdentity().getUdn(),
104                 localDevice,
105                 localDevice.getIdentity().getMaxAgeSeconds()
106         );
107 
108         getDeviceItems().add(localItem);
109         log.fine("Registered local device: " + localItem);
110 
111         if (isByeByeBeforeFirstAlive(localItem.getKey()))
112             advertiseByebye(localDevice, true);
113 
114         if (isAdvertised(localItem.getKey()))
115              advertiseAlive(localDevice);
116 
117         for (final RegistryListener listener : registry.getListeners()) {
118             registry.getConfiguration().getRegistryListenerExecutor().execute(
119                 new Runnable() {
120                     public void run() {
121                         listener.localDeviceAdded(registry, localDevice);
122                     }
123                 }
124             );
125         }
126 
127     }
128 
129     Collection<LocalDevice> get() {
130         Set<LocalDevice> c = new HashSet<>();
131         for (RegistryItem<UDN, LocalDevice> item : getDeviceItems()) {
132             c.add(item.getItem());
133         }
134         return Collections.unmodifiableCollection(c);
135     }
136 
137     boolean remove(final LocalDevice localDevice) throws RegistrationException {
138         return remove(localDevice, false);
139     }
140 
141     boolean remove(final LocalDevice localDevice, boolean shuttingDown) throws RegistrationException {
142 
143         LocalDevice registeredDevice = get(localDevice.getIdentity().getUdn(), true);
144         if (registeredDevice != null) {
145 
146             log.fine("Removing local device from registry: " + localDevice);
147 
148             setDiscoveryOptions(localDevice.getIdentity().getUdn(), null);
149             getDeviceItems().remove(new RegistryItem(localDevice.getIdentity().getUdn()));
150 
151             for (Resource deviceResource : getResources(localDevice)) {
152                 if (registry.removeResource(deviceResource)) {
153                     log.fine("Unregistered resource: " + deviceResource);
154                 }
155             }
156 
157             // Active subscriptions
158             Iterator<RegistryItem<String, LocalGENASubscription>> it = getSubscriptionItems().iterator();
159             while (it.hasNext()) {
160                 final RegistryItem<String, LocalGENASubscription> incomingSubscription = it.next();
161 
162                 UDN subscriptionForUDN =
163                         incomingSubscription.getItem().getService().getDevice().getIdentity().getUdn();
164 
165                 if (subscriptionForUDN.equals(registeredDevice.getIdentity().getUdn())) {
166                     log.fine("Removing incoming subscription: " + incomingSubscription.getKey());
167                     it.remove();
168                     if (!shuttingDown) {
169                         registry.getConfiguration().getRegistryListenerExecutor().execute(
170                                 new Runnable() {
171                                     public void run() {
172                                         incomingSubscription.getItem().end(CancelReason.DEVICE_WAS_REMOVED);
173                                     }
174                                 }
175                         );
176                     }
177                 }
178             }
179 
180             if (isAdvertised(localDevice.getIdentity().getUdn()))
181          		advertiseByebye(localDevice, !shuttingDown);
182 
183             if (!shuttingDown) {
184                 for (final RegistryListener listener : registry.getListeners()) {
185                     registry.getConfiguration().getRegistryListenerExecutor().execute(
186                             new Runnable() {
187                                 public void run() {
188                                     listener.localDeviceRemoved(registry, localDevice);
189                                 }
190                             }
191                     );
192                 }
193             }
194 
195             return true;
196         }
197 
198         return false;
199     }
200 
201     void removeAll() {
202         removeAll(false);
203     }
204 
205     void removeAll(boolean shuttingDown) {
206         LocalDevice[] allDevices = get().toArray(new LocalDevice[get().size()]);
207         for (LocalDevice device : allDevices) {
208             remove(device, shuttingDown);
209         }
210     }
211 
212     /* ############################################################################################################ */
213 
214     public void advertiseLocalDevices() {
215         for (RegistryItem<UDN, LocalDevice> localItem : deviceItems) {
216             if (isAdvertised(localItem.getKey()))
217                 advertiseAlive(localItem.getItem());
218         }
219     }
220 
221     /* ############################################################################################################ */
222     
223     void maintain() {
224 
225     	if(getDeviceItems().isEmpty()) return ;
226 
227         Set<RegistryItem<UDN, LocalDevice>> expiredLocalItems = new HashSet<>();
228 
229         // "Flooding" is enabled, check if we need to send advertisements for all devices
230         int aliveIntervalMillis = registry.getConfiguration().getAliveIntervalMillis();
231         if(aliveIntervalMillis > 0) {
232         	long now = System.currentTimeMillis();
233         	if(now - lastAliveIntervalTimestamp > aliveIntervalMillis) {
234         		lastAliveIntervalTimestamp = now;
235                 for (RegistryItem<UDN, LocalDevice> localItem : getDeviceItems()) {
236                     if (isAdvertised(localItem.getKey())) {
237                         log.finer("Flooding advertisement of local item: " + localItem);
238                         expiredLocalItems.add(localItem);
239                     }
240                 }
241         	}
242         } else {
243             // Reset, the configuration might dynamically switch the alive interval
244             lastAliveIntervalTimestamp = 0;
245 
246             // Alive interval is not enabled, regular expiration check of all devices
247             for (RegistryItem<UDN, LocalDevice> localItem : getDeviceItems()) {
248                 if (isAdvertised(localItem.getKey()) && localItem.getExpirationDetails().hasExpired(true)) {
249                     log.finer("Local item has expired: " + localItem);
250                     expiredLocalItems.add(localItem);
251                 }
252             }
253         }
254 
255         // Now execute the advertisements
256         for (RegistryItem<UDN, LocalDevice> expiredLocalItem : expiredLocalItems) {
257             log.fine("Refreshing local device advertisement: " + expiredLocalItem.getItem());
258             advertiseAlive(expiredLocalItem.getItem());
259             expiredLocalItem.getExpirationDetails().stampLastRefresh();
260         }
261 
262         // Expire incoming subscriptions
263         Set<RegistryItem<String, LocalGENASubscription>> expiredIncomingSubscriptions = new HashSet<>();
264         for (RegistryItem<String, LocalGENASubscription> item : getSubscriptionItems()) {
265             if (item.getExpirationDetails().hasExpired(false)) {
266                 expiredIncomingSubscriptions.add(item);
267             }
268         }
269         for (RegistryItem<String, LocalGENASubscription> subscription : expiredIncomingSubscriptions) {
270             log.fine("Removing expired: " + subscription);
271             removeSubscription(subscription.getItem());
272             subscription.getItem().end(CancelReason.EXPIRED);
273         }
274 
275     }
276 
277     void shutdown() {
278         log.fine("Clearing all registered subscriptions to local devices during shutdown");
279         getSubscriptionItems().clear();
280 
281         log.fine("Removing all local devices from registry during shutdown");
282         removeAll(true);
283     }
284 
285     /* ############################################################################################################ */
286 
287     protected Random randomGenerator = new Random();
288 
289     protected void advertiseAlive(final LocalDevice localDevice) {
290         registry.executeAsyncProtocol(new Runnable() {
291             public void run() {
292                 try {
293                     log.finer("Sleeping some milliseconds to avoid flooding the network with ALIVE msgs");
294                     Thread.sleep(randomGenerator.nextInt(100));
295                 } catch (InterruptedException ex) {
296                     log.severe("Background execution interrupted: " + ex.getMessage());
297                 }
298                 registry.getProtocolFactory().createSendingNotificationAlive(localDevice).run();
299             }
300         });
301     }
302 
303     protected void advertiseByebye(final LocalDevice localDevice, boolean asynchronous) {
304         final SendingAsync prot = registry.getProtocolFactory().createSendingNotificationByebye(localDevice);
305         if (asynchronous) {
306             registry.executeAsyncProtocol(prot);
307         } else {
308             prot.run();
309         }
310     }
311 
312 }