1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.fourthline.cling.protocol;
17
18 import java.net.URL;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.CopyOnWriteArraySet;
24 import java.util.logging.Level;
25 import java.util.logging.Logger;
26
27 import org.fourthline.cling.UpnpService;
28 import org.fourthline.cling.binding.xml.DescriptorBindingException;
29 import org.fourthline.cling.binding.xml.DeviceDescriptorBinder;
30 import org.fourthline.cling.binding.xml.ServiceDescriptorBinder;
31 import org.fourthline.cling.model.ValidationError;
32 import org.fourthline.cling.model.ValidationException;
33 import org.fourthline.cling.model.message.StreamRequestMessage;
34 import org.fourthline.cling.model.message.StreamResponseMessage;
35 import org.fourthline.cling.model.message.UpnpHeaders;
36 import org.fourthline.cling.model.message.UpnpRequest;
37 import org.fourthline.cling.model.meta.Icon;
38 import org.fourthline.cling.model.meta.RemoteDevice;
39 import org.fourthline.cling.model.meta.RemoteService;
40 import org.fourthline.cling.model.types.ServiceType;
41 import org.fourthline.cling.model.types.UDN;
42 import org.fourthline.cling.registry.RegistrationException;
43 import org.fourthline.cling.transport.RouterException;
44 import org.seamless.util.Exceptions;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public class RetrieveRemoteDescriptors implements Runnable {
67
68 final private static Logger log = Logger.getLogger(RetrieveRemoteDescriptors.class.getName());
69
70 private final UpnpService upnpService;
71 private RemoteDevice rd;
72
73 private static final Set<URL> activeRetrievals = new CopyOnWriteArraySet();
74 protected List<UDN> errorsAlreadyLogged = new ArrayList<>();
75
76 public RetrieveRemoteDescriptors(UpnpService upnpService, RemoteDevice rd) {
77 this.upnpService = upnpService;
78 this.rd = rd;
79 }
80
81 public UpnpService getUpnpService() {
82 return upnpService;
83 }
84
85 public void run() {
86
87 URL deviceURL = rd.getIdentity().getDescriptorURL();
88
89
90
91
92
93 if (activeRetrievals.contains(deviceURL)) {
94 log.finer("Exiting early, active retrieval for URL already in progress: " + deviceURL);
95 return;
96 }
97
98
99 if (getUpnpService().getRegistry().getRemoteDevice(rd.getIdentity().getUdn(), true) != null) {
100 log.finer("Exiting early, already discovered: " + deviceURL);
101 return;
102 }
103
104 try {
105 activeRetrievals.add(deviceURL);
106 describe();
107 } catch (RouterException ex) {
108 log.log(Level.WARNING,
109 "Descriptor retrieval failed: " + deviceURL,
110 ex
111 );
112 } finally {
113 activeRetrievals.remove(deviceURL);
114 }
115 }
116
117 protected void describe() throws RouterException {
118
119
120
121
122
123
124 if(getUpnpService().getRouter() == null) {
125 log.warning("Router not yet initialized");
126 return ;
127 }
128
129 StreamRequestMessage deviceDescRetrievalMsg;
130 StreamResponseMessage deviceDescMsg;
131
132 try {
133
134 deviceDescRetrievalMsg =
135 new StreamRequestMessage(UpnpRequest.Method.GET, rd.getIdentity().getDescriptorURL());
136
137
138 UpnpHeaders headers =
139 getUpnpService().getConfiguration().getDescriptorRetrievalHeaders(rd.getIdentity());
140 if (headers != null)
141 deviceDescRetrievalMsg.getHeaders().putAll(headers);
142
143 log.fine("Sending device descriptor retrieval message: " + deviceDescRetrievalMsg);
144 deviceDescMsg = getUpnpService().getRouter().send(deviceDescRetrievalMsg);
145
146 } catch(IllegalArgumentException ex) {
147
148
149 log.warning(
150 "Device descriptor retrieval failed: "
151 + rd.getIdentity().getDescriptorURL()
152 + ", possibly invalid URL: " + ex);
153 return ;
154 }
155
156 if (deviceDescMsg == null) {
157 log.warning(
158 "Device descriptor retrieval failed, no response: " + rd.getIdentity().getDescriptorURL()
159 );
160 return;
161 }
162
163 if (deviceDescMsg.getOperation().isFailed()) {
164 log.warning(
165 "Device descriptor retrieval failed: "
166 + rd.getIdentity().getDescriptorURL() +
167 ", "
168 + deviceDescMsg.getOperation().getResponseDetails()
169 );
170 return;
171 }
172
173 if (!deviceDescMsg.isContentTypeTextUDA()) {
174 log.fine(
175 "Received device descriptor without or with invalid Content-Type: "
176 + rd.getIdentity().getDescriptorURL());
177
178 }
179
180 String descriptorContent = deviceDescMsg.getBodyString();
181 if (descriptorContent == null || descriptorContent.length() == 0) {
182 log.warning("Received empty device descriptor:" + rd.getIdentity().getDescriptorURL());
183 return;
184 }
185
186 log.fine("Received root device descriptor: " + deviceDescMsg);
187 describe(descriptorContent);
188 }
189
190 protected void describe(String descriptorXML) throws RouterException {
191
192 boolean notifiedStart = false;
193 RemoteDevice describedDevice = null;
194 try {
195
196 DeviceDescriptorBinder deviceDescriptorBinder =
197 getUpnpService().getConfiguration().getDeviceDescriptorBinderUDA10();
198
199 describedDevice = deviceDescriptorBinder.describe(
200 rd,
201 descriptorXML
202 );
203
204 log.fine("Remote device described (without services) notifying listeners: " + describedDevice);
205 notifiedStart = getUpnpService().getRegistry().notifyDiscoveryStart(describedDevice);
206
207 log.fine("Hydrating described device's services: " + describedDevice);
208 RemoteDevice hydratedDevice = describeServices(describedDevice);
209 if (hydratedDevice == null) {
210 if(!errorsAlreadyLogged.contains(rd.getIdentity().getUdn())) {
211 errorsAlreadyLogged.add(rd.getIdentity().getUdn());
212 log.warning("Device service description failed: " + rd);
213 }
214 if (notifiedStart)
215 getUpnpService().getRegistry().notifyDiscoveryFailure(
216 describedDevice,
217 new DescriptorBindingException("Device service description failed: " + rd)
218 );
219 return;
220 } else {
221 log.fine("Adding fully hydrated remote device to registry: " + hydratedDevice);
222
223
224
225
226 getUpnpService().getRegistry().addDevice(hydratedDevice);
227 }
228
229 } catch (ValidationException ex) {
230
231 if(!errorsAlreadyLogged.contains(rd.getIdentity().getUdn())) {
232 errorsAlreadyLogged.add(rd.getIdentity().getUdn());
233 log.warning("Could not validate device model: " + rd);
234 for (ValidationError validationError : ex.getErrors()) {
235 log.warning(validationError.toString());
236 }
237 if (describedDevice != null && notifiedStart)
238 getUpnpService().getRegistry().notifyDiscoveryFailure(describedDevice, ex);
239 }
240
241 } catch (DescriptorBindingException ex) {
242 log.warning("Could not hydrate device or its services from descriptor: " + rd);
243 log.warning("Cause was: " + Exceptions.unwrap(ex));
244 if (describedDevice != null && notifiedStart)
245 getUpnpService().getRegistry().notifyDiscoveryFailure(describedDevice, ex);
246
247 } catch (RegistrationException ex) {
248 log.warning("Adding hydrated device to registry failed: " + rd);
249 log.warning("Cause was: " + ex.toString());
250 if (describedDevice != null && notifiedStart)
251 getUpnpService().getRegistry().notifyDiscoveryFailure(describedDevice, ex);
252 }
253 }
254
255 protected RemoteDevice describeServices(RemoteDevice currentDevice)
256 throws RouterException, DescriptorBindingException, ValidationException {
257
258 List<RemoteService> describedServices = new ArrayList<>();
259 if (currentDevice.hasServices()) {
260 List<RemoteService> filteredServices = filterExclusiveServices(currentDevice.getServices());
261 for (RemoteService service : filteredServices) {
262 RemoteService svc = describeService(service);
263
264 if (svc != null)
265 describedServices.add(svc);
266 else
267 log.warning("Skipping invalid service '" + service + "' of: " + currentDevice);
268 }
269 }
270
271 List<RemoteDevice> describedEmbeddedDevices = new ArrayList<>();
272 if (currentDevice.hasEmbeddedDevices()) {
273 for (RemoteDevice embeddedDevice : currentDevice.getEmbeddedDevices()) {
274
275 if (embeddedDevice == null)
276 continue;
277 RemoteDevice describedEmbeddedDevice = describeServices(embeddedDevice);
278
279 if (describedEmbeddedDevice != null)
280 describedEmbeddedDevices.add(describedEmbeddedDevice);
281 }
282 }
283
284 Icon[] iconDupes = new Icon[currentDevice.getIcons().length];
285 for (int i = 0; i < currentDevice.getIcons().length; i++) {
286 Icon icon = currentDevice.getIcons()[i];
287 iconDupes[i] = icon.deepCopy();
288 }
289
290
291 return currentDevice.newInstance(
292 currentDevice.getIdentity().getUdn(),
293 currentDevice.getVersion(),
294 currentDevice.getType(),
295 currentDevice.getDetails(),
296 iconDupes,
297 currentDevice.toServiceArray(describedServices),
298 describedEmbeddedDevices
299 );
300 }
301
302 protected RemoteService describeService(RemoteService service)
303 throws RouterException, DescriptorBindingException, ValidationException {
304
305 URL descriptorURL;
306 try {
307 descriptorURL = service.getDevice().normalizeURI(service.getDescriptorURI());
308 } catch(IllegalArgumentException e) {
309 log.warning("Could not normalize service descriptor URL: " + service.getDescriptorURI());
310 return null;
311 }
312
313 StreamRequestMessage serviceDescRetrievalMsg = new StreamRequestMessage(UpnpRequest.Method.GET, descriptorURL);
314
315
316 UpnpHeaders headers =
317 getUpnpService().getConfiguration().getDescriptorRetrievalHeaders(service.getDevice().getIdentity());
318 if (headers != null)
319 serviceDescRetrievalMsg.getHeaders().putAll(headers);
320
321 log.fine("Sending service descriptor retrieval message: " + serviceDescRetrievalMsg);
322 StreamResponseMessage serviceDescMsg = getUpnpService().getRouter().send(serviceDescRetrievalMsg);
323
324 if (serviceDescMsg == null) {
325 log.warning("Could not retrieve service descriptor, no response: " + service);
326 return null;
327 }
328
329 if (serviceDescMsg.getOperation().isFailed()) {
330 log.warning("Service descriptor retrieval failed: "
331 + descriptorURL
332 + ", "
333 + serviceDescMsg.getOperation().getResponseDetails());
334 return null;
335 }
336
337 if (!serviceDescMsg.isContentTypeTextUDA()) {
338 log.fine("Received service descriptor without or with invalid Content-Type: " + descriptorURL);
339
340 }
341
342 String descriptorContent = serviceDescMsg.getBodyString();
343 if (descriptorContent == null || descriptorContent.length() == 0) {
344 log.warning("Received empty service descriptor:" + descriptorURL);
345 return null;
346 }
347
348 log.fine("Received service descriptor, hydrating service model: " + serviceDescMsg);
349 ServiceDescriptorBinder serviceDescriptorBinder =
350 getUpnpService().getConfiguration().getServiceDescriptorBinderUDA10();
351
352 return serviceDescriptorBinder.describe(service, descriptorContent);
353 }
354
355 protected List<RemoteService> filterExclusiveServices(RemoteService[] services) {
356 ServiceType[] exclusiveTypes = getUpnpService().getConfiguration().getExclusiveServiceTypes();
357
358 if (exclusiveTypes == null || exclusiveTypes.length == 0)
359 return Arrays.asList(services);
360
361 List<RemoteService> exclusiveServices = new ArrayList<>();
362 for (RemoteService discoveredService : services) {
363 for (ServiceType exclusiveType : exclusiveTypes) {
364 if (discoveredService.getServiceType().implementsVersion(exclusiveType)) {
365 log.fine("Including exclusive service: " + discoveredService);
366 exclusiveServices.add(discoveredService);
367 } else {
368 log.fine("Excluding unwanted service: " + exclusiveType);
369 }
370 }
371 }
372 return exclusiveServices;
373 }
374
375 }