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.protocol.async;
17  
18  import org.fourthline.cling.UpnpService;
19  import org.fourthline.cling.model.ValidationError;
20  import org.fourthline.cling.model.ValidationException;
21  import org.fourthline.cling.model.message.IncomingDatagramMessage;
22  import org.fourthline.cling.model.message.UpnpRequest;
23  import org.fourthline.cling.model.message.discovery.IncomingNotificationRequest;
24  import org.fourthline.cling.model.meta.RemoteDevice;
25  import org.fourthline.cling.model.meta.RemoteDeviceIdentity;
26  import org.fourthline.cling.model.types.UDN;
27  import org.fourthline.cling.protocol.ReceivingAsync;
28  import org.fourthline.cling.protocol.RetrieveRemoteDescriptors;
29  import org.fourthline.cling.transport.RouterException;
30  
31  import java.util.logging.Logger;
32  
33  /**
34   * Handles reception of notification messages.
35   * <p>
36   * First, the UDN is created from the received message.
37   * </p>
38   * <p>
39   * If an <em>ALIVE</em> message has been received, a new background process will be started
40   * running {@link org.fourthline.cling.protocol.RetrieveRemoteDescriptors}.
41   * </p>
42   * <p>
43   * If a <em>BYEBYE</em> message has been received, the device will be removed from the registry
44   * directly.
45   * </p>
46   * <p>
47   * The following was added to the UDA 1.1 spec (in 1.3), clarifying the handling of messages:
48   * </p>
49   * <p>
50   * "If a control point has received at least one 'byebye' message of a root device, embedded device, or
51   * service, then the control point can assume that all are no longer available."
52   * </p>
53   * <p>
54   * Of course, they contradict this a little later:
55   * </p>
56   * <p>
57   * "Only when all original advertisements of a root device, embedded device, and services have
58   * expired can a control point assume that they are no longer available."
59   * </p>
60   * <p>
61   * This could mean that even if we get 'byeby'e for the root device, we still have to assume that its services
62   * are available. That clearly makes no sense at all and I think it's just badly worded and relates to the
63   * previous sentence wich says "if you don't get byebye's, rely on the expiration timeout". It does not
64   * imply that a service or embedded device lives beyond its root device. It actually reinforces that we are
65   * free to ignore anything that happens as long as the root device is not gone with 'byebye' or has expired.
66   * In other words: There is no reason at all why SSDP sends dozens of messages for all embedded devices and
67   * services. The composite is the root device and the composite defines the lifecycle of all.
68   * </p>
69   *
70   * @author Christian Bauer
71   */
72  public class ReceivingNotification extends ReceivingAsync<IncomingNotificationRequest> {
73  
74      final private static Logger log = Logger.getLogger(ReceivingNotification.class.getName());
75  
76      public ReceivingNotification(UpnpService upnpService, IncomingDatagramMessage<UpnpRequest> inputMessage) {
77          super(upnpService, new IncomingNotificationRequest(inputMessage));
78      }
79  
80      protected void execute() throws RouterException {
81  
82          UDN udn = getInputMessage().getUDN();
83          if (udn == null) {
84              log.fine("Ignoring notification message without UDN: " + getInputMessage());
85              return;
86          }
87  
88          RemoteDeviceIdentity rdIdentity = new RemoteDeviceIdentity(getInputMessage());
89          log.fine("Received device notification: " + rdIdentity);
90  
91          RemoteDevice rd;
92          try {
93              rd = new RemoteDevice(rdIdentity);
94          } catch (ValidationException ex) {
95              log.warning("Validation errors of device during discovery: " + rdIdentity);
96              for (ValidationError validationError : ex.getErrors()) {
97                  log.warning(validationError.toString());
98              }
99              return;
100         }
101 
102         if (getInputMessage().isAliveMessage()) {
103 
104             log.fine("Received device ALIVE advertisement, descriptor location is: " + rdIdentity.getDescriptorURL());
105 
106             if (rdIdentity.getDescriptorURL() == null) {
107                 log.finer("Ignoring message without location URL header: " + getInputMessage());
108                 return;
109             }
110 
111             if (rdIdentity.getMaxAgeSeconds() == null) {
112                 log.finer("Ignoring message without max-age header: " + getInputMessage());
113                 return;
114             }
115 
116             if (getUpnpService().getRegistry().update(rdIdentity)) {
117                 log.finer("Remote device was already known: " + udn);
118                 return;
119             }
120 
121             // Unfortunately, we always have to retrieve the descriptor because at this point we
122             // have no idea if it's a root or embedded device
123             getUpnpService().getConfiguration().getAsyncProtocolExecutor().execute(
124                     new RetrieveRemoteDescriptors(getUpnpService(), rd)
125             );
126 
127         } else if (getInputMessage().isByeByeMessage()) {
128 
129             log.fine("Received device BYEBYE advertisement");
130             boolean removed = getUpnpService().getRegistry().removeDevice(rd);
131             if (removed) {
132                 log.fine("Removed remote device from registry: " + rd);
133             }
134 
135         } else {
136             log.finer("Ignoring unknown notification message: " + getInputMessage());
137         }
138 
139     }
140 
141 
142 }