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;
17  
18  import org.fourthline.cling.UpnpService;
19  import org.fourthline.cling.model.Namespace;
20  import org.fourthline.cling.model.NetworkAddress;
21  import org.fourthline.cling.model.action.ActionInvocation;
22  import org.fourthline.cling.model.gena.LocalGENASubscription;
23  import org.fourthline.cling.model.gena.RemoteGENASubscription;
24  import org.fourthline.cling.model.message.IncomingDatagramMessage;
25  import org.fourthline.cling.model.message.StreamRequestMessage;
26  import org.fourthline.cling.model.message.UpnpRequest;
27  import org.fourthline.cling.model.message.UpnpResponse;
28  import org.fourthline.cling.model.message.header.UpnpHeader;
29  import org.fourthline.cling.model.meta.LocalDevice;
30  import org.fourthline.cling.model.types.InvalidValueException;
31  import org.fourthline.cling.model.types.NamedServiceType;
32  import org.fourthline.cling.model.types.NotificationSubtype;
33  import org.fourthline.cling.model.types.ServiceType;
34  import org.fourthline.cling.protocol.async.ReceivingNotification;
35  import org.fourthline.cling.protocol.async.ReceivingSearch;
36  import org.fourthline.cling.protocol.async.ReceivingSearchResponse;
37  import org.fourthline.cling.protocol.async.SendingNotificationAlive;
38  import org.fourthline.cling.protocol.async.SendingNotificationByebye;
39  import org.fourthline.cling.protocol.async.SendingSearch;
40  import org.fourthline.cling.protocol.sync.ReceivingAction;
41  import org.fourthline.cling.protocol.sync.ReceivingEvent;
42  import org.fourthline.cling.protocol.sync.ReceivingRetrieval;
43  import org.fourthline.cling.protocol.sync.ReceivingSubscribe;
44  import org.fourthline.cling.protocol.sync.ReceivingUnsubscribe;
45  import org.fourthline.cling.protocol.sync.SendingAction;
46  import org.fourthline.cling.protocol.sync.SendingEvent;
47  import org.fourthline.cling.protocol.sync.SendingRenewal;
48  import org.fourthline.cling.protocol.sync.SendingSubscribe;
49  import org.fourthline.cling.protocol.sync.SendingUnsubscribe;
50  import org.fourthline.cling.transport.RouterException;
51  
52  import javax.enterprise.context.ApplicationScoped;
53  import javax.inject.Inject;
54  import java.net.URI;
55  import java.net.URL;
56  import java.util.List;
57  import java.util.logging.Level;
58  import java.util.logging.Logger;
59  
60  /**
61   * Default implementation, directly instantiates the appropriate protocols.
62   *
63   * @author Christian Bauer
64   */
65  @ApplicationScoped
66  public class ProtocolFactoryImpl implements ProtocolFactory {
67  
68      final private static Logger log = Logger.getLogger(ProtocolFactory.class.getName());
69  
70      protected final UpnpService upnpService;
71  
72      protected ProtocolFactoryImpl() {
73          upnpService = null;
74      }
75  
76      @Inject
77      public ProtocolFactoryImpl(UpnpService upnpService) {
78          log.fine("Creating ProtocolFactory: " + getClass().getName());
79          this.upnpService = upnpService;
80      }
81  
82      public UpnpService getUpnpService() {
83          return upnpService;
84      }
85  
86      public ReceivingAsync createReceivingAsync(IncomingDatagramMessage message) throws ProtocolCreationException {
87          if (log.isLoggable(Level.FINE)) {
88              log.fine("Creating protocol for incoming asynchronous: " + message);
89          }
90  
91          if (message.getOperation() instanceof UpnpRequest) {
92              IncomingDatagramMessage<UpnpRequest> incomingRequest = message;
93  
94              switch (incomingRequest.getOperation().getMethod()) {
95                  case NOTIFY:
96                      return isByeBye(incomingRequest) || isSupportedServiceAdvertisement(incomingRequest)
97                          ? createReceivingNotification(incomingRequest) : null;
98                  case MSEARCH:
99                      return createReceivingSearch(incomingRequest);
100             }
101 
102         } else if (message.getOperation() instanceof UpnpResponse) {
103             IncomingDatagramMessage<UpnpResponse> incomingResponse = message;
104 
105             return isSupportedServiceAdvertisement(incomingResponse)
106                 ? createReceivingSearchResponse(incomingResponse) : null;
107         }
108 
109         throw new ProtocolCreationException("Protocol for incoming datagram message not found: " + message);
110     }
111 
112     protected ReceivingAsync createReceivingNotification(IncomingDatagramMessage<UpnpRequest> incomingRequest) {
113         return new ReceivingNotification(getUpnpService(), incomingRequest);
114     }
115 
116     protected ReceivingAsync createReceivingSearch(IncomingDatagramMessage<UpnpRequest> incomingRequest) {
117         return new ReceivingSearch(getUpnpService(), incomingRequest);
118     }
119 
120     protected ReceivingAsync createReceivingSearchResponse(IncomingDatagramMessage<UpnpResponse> incomingResponse) {
121         return new ReceivingSearchResponse(getUpnpService(), incomingResponse);
122     }
123 
124     // DO NOT USE THE PARSED/TYPED MSG HEADERS! THIS WOULD DEFEAT THE PURPOSE OF THIS OPTIMIZATION!
125 
126     protected boolean isByeBye(IncomingDatagramMessage message) {
127         String ntsHeader = message.getHeaders().getFirstHeader(UpnpHeader.Type.NTS.getHttpName());
128         return ntsHeader != null && ntsHeader.equals(NotificationSubtype.BYEBYE.getHeaderString());
129     }
130 
131     protected boolean isSupportedServiceAdvertisement(IncomingDatagramMessage message) {
132         ServiceType[] exclusiveServiceTypes = getUpnpService().getConfiguration().getExclusiveServiceTypes();
133         if (exclusiveServiceTypes == null) return false; // Discovery is disabled
134         if (exclusiveServiceTypes.length == 0) return true; // Any advertisement is fine
135 
136         String usnHeader = message.getHeaders().getFirstHeader(UpnpHeader.Type.USN.getHttpName());
137         if (usnHeader == null) return false; // Not a service advertisement, drop it
138 
139         try {
140             NamedServiceType nst = NamedServiceType.valueOf(usnHeader);
141             for (ServiceType exclusiveServiceType : exclusiveServiceTypes) {
142                 if (nst.getServiceType().implementsVersion(exclusiveServiceType))
143                     return true;
144             }
145         } catch (InvalidValueException ex) {
146             log.finest("Not a named service type header value: " + usnHeader);
147         }
148         log.fine("Service advertisement not supported, dropping it: " + usnHeader);
149         return false;
150     }
151 
152     public ReceivingSync createReceivingSync(StreamRequestMessage message) throws ProtocolCreationException {
153         log.fine("Creating protocol for incoming synchronous: " + message);
154 
155         if (message.getOperation().getMethod().equals(UpnpRequest.Method.GET)) {
156 
157             return createReceivingRetrieval(message);
158 
159         } else if (getUpnpService().getConfiguration().getNamespace().isControlPath(message.getUri())) {
160 
161             if (message.getOperation().getMethod().equals(UpnpRequest.Method.POST))
162                 return createReceivingAction(message);
163 
164         } else if (getUpnpService().getConfiguration().getNamespace().isEventSubscriptionPath(message.getUri())) {
165 
166             if (message.getOperation().getMethod().equals(UpnpRequest.Method.SUBSCRIBE)) {
167                 return createReceivingSubscribe(message);
168             } else if (message.getOperation().getMethod().equals(UpnpRequest.Method.UNSUBSCRIBE)) {
169                 return createReceivingUnsubscribe(message);
170             }
171 
172         } else if (getUpnpService().getConfiguration().getNamespace().isEventCallbackPath(message.getUri())) {
173 
174             if (message.getOperation().getMethod().equals(UpnpRequest.Method.NOTIFY))
175                 return createReceivingEvent(message);
176 
177         } else {
178 
179             // TODO: UPNP VIOLATION: Onkyo devices send event messages with trailing garbage characters
180             // /dev/9bb022aa-e922-aab9-682b-aa09e9b9e059/svc/upnp-org/RenderingControl/event/cb192%2e168%2e10%2e38
181             // TODO: UPNP VIOLATION: Yamaha does the same
182             // /dev/9ab0c000-f668-11de-9976-00a0de870fd4/svc/upnp-org/RenderingControl/event/cb><http://10.189.150.197:42082/dev/9ab0c000-f668-11de-9976-00a0de870fd4/svc/upnp-org/RenderingControl/event/cb
183             if (message.getUri().getPath().contains(Namespace.EVENTS + Namespace.CALLBACK_FILE)) {
184                 log.warning("Fixing trailing garbage in event message path: " + message.getUri().getPath());
185                 String invalid = message.getUri().toString();
186                 message.setUri(
187                     URI.create(invalid.substring(
188                         0, invalid.indexOf(Namespace.CALLBACK_FILE) + Namespace.CALLBACK_FILE.length()
189                     ))
190                 );
191                 if (getUpnpService().getConfiguration().getNamespace().isEventCallbackPath(message.getUri())
192                     && message.getOperation().getMethod().equals(UpnpRequest.Method.NOTIFY))
193                     return createReceivingEvent(message);
194             }
195 
196         }
197 
198         throw new ProtocolCreationException("Protocol for message type not found: " + message);
199     }
200 
201     public SendingNotificationAlive createSendingNotificationAlive(LocalDevice localDevice) {
202         return new SendingNotificationAlive(getUpnpService(), localDevice);
203     }
204 
205     public SendingNotificationByebye createSendingNotificationByebye(LocalDevice localDevice) {
206         return new SendingNotificationByebye(getUpnpService(), localDevice);
207     }
208 
209     public SendingSearch createSendingSearch(UpnpHeader searchTarget, int mxSeconds) {
210         return new SendingSearch(getUpnpService(), searchTarget, mxSeconds);
211     }
212 
213     public SendingAction createSendingAction(ActionInvocation actionInvocation, URL controlURL) {
214         return new SendingAction(getUpnpService(), actionInvocation, controlURL);
215     }
216 
217     public SendingSubscribe createSendingSubscribe(RemoteGENASubscription subscription) throws ProtocolCreationException {
218         try {
219             List<NetworkAddress> activeStreamServers =
220                 getUpnpService().getRouter().getActiveStreamServers(
221                     subscription.getService().getDevice().getIdentity().getDiscoveredOnLocalAddress()
222                 );
223             return new SendingSubscribe(getUpnpService(), subscription, activeStreamServers);
224         } catch (RouterException ex) {
225             throw new ProtocolCreationException(
226                 "Failed to obtain local stream servers (for event callback URL creation) from router",
227                 ex
228             );
229         }
230     }
231 
232     public SendingRenewal createSendingRenewal(RemoteGENASubscription subscription) {
233         return new SendingRenewal(getUpnpService(), subscription);
234     }
235 
236     public SendingUnsubscribe createSendingUnsubscribe(RemoteGENASubscription subscription) {
237         return new SendingUnsubscribe(getUpnpService(), subscription);
238     }
239 
240     public SendingEvent createSendingEvent(LocalGENASubscription subscription) {
241         return new SendingEvent(getUpnpService(), subscription);
242     }
243 
244     protected ReceivingRetrieval createReceivingRetrieval(StreamRequestMessage message) {
245         return new ReceivingRetrieval(getUpnpService(), message);
246     }
247 
248     protected ReceivingAction createReceivingAction(StreamRequestMessage message) {
249         return new ReceivingAction(getUpnpService(), message);
250     }
251 
252     protected ReceivingSubscribe createReceivingSubscribe(StreamRequestMessage message) {
253         return new ReceivingSubscribe(getUpnpService(), message);
254     }
255 
256     protected ReceivingUnsubscribe createReceivingUnsubscribe(StreamRequestMessage message) {
257         return new ReceivingUnsubscribe(getUpnpService(), message);
258     }
259 
260     protected ReceivingEvent createReceivingEvent(StreamRequestMessage message) {
261         return new ReceivingEvent(getUpnpService(), message);
262     }
263 }