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.binding.xml;
17  
18  import org.fourthline.cling.binding.staging.MutableDevice;
19  import org.fourthline.cling.binding.staging.MutableIcon;
20  import org.fourthline.cling.binding.staging.MutableService;
21  import org.fourthline.cling.binding.staging.MutableUDAVersion;
22  import org.fourthline.cling.model.ValidationException;
23  import org.fourthline.cling.model.XMLUtil;
24  import org.fourthline.cling.model.meta.Device;
25  import org.fourthline.cling.model.types.DLNACaps;
26  import org.fourthline.cling.model.types.DLNADoc;
27  import org.fourthline.cling.model.types.InvalidValueException;
28  import org.fourthline.cling.model.types.ServiceId;
29  import org.fourthline.cling.model.types.ServiceType;
30  import org.fourthline.cling.model.types.UDN;
31  import org.seamless.util.MimeType;
32  import org.seamless.xml.SAXParser;
33  import org.xml.sax.Attributes;
34  import org.xml.sax.InputSource;
35  import org.xml.sax.SAXException;
36  
37  import java.io.StringReader;
38  import java.net.URL;
39  import java.util.ArrayList;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.logging.Logger;
43  
44  import static org.fourthline.cling.binding.xml.Descriptor.Device.ELEMENT;
45  
46  /**
47   * A JAXP SAX parser implementation, which is actually slower than the DOM implementation (on desktop and on Android)!
48   *
49   * @author Christian Bauer
50   */
51  public class UDA10DeviceDescriptorBinderSAXImpl extends UDA10DeviceDescriptorBinderImpl {
52  
53      private static Logger log = Logger.getLogger(DeviceDescriptorBinder.class.getName());
54  
55      @Override
56      public <D extends Device> D describe(D undescribedDevice, String descriptorXml) throws DescriptorBindingException, ValidationException {
57  
58          if (descriptorXml == null || descriptorXml.length() == 0) {
59              throw new DescriptorBindingException("Null or empty descriptor");
60          }
61  
62          try {
63              log.fine("Populating device from XML descriptor: " + undescribedDevice);
64  
65              // Read the XML into a mutable descriptor graph
66  
67              SAXParser parser = new SAXParser();
68  
69              MutableDevice descriptor = new MutableDevice();
70              new RootHandler(descriptor, parser);
71  
72              parser.parse(
73                      new InputSource(
74                              // TODO: UPNP VIOLATION: Virgin Media Superhub sends trailing spaces/newlines after last XML element, need to trim()
75                              new StringReader(descriptorXml.trim())
76                      )
77              );
78  
79              // Build the immutable descriptor graph
80              return (D) descriptor.build(undescribedDevice);
81  
82          } catch (ValidationException ex) {
83              throw ex;
84          } catch (Exception ex) {
85              throw new DescriptorBindingException("Could not parse device descriptor: " + ex.toString(), ex);
86          }
87      }
88  
89      protected static class RootHandler extends DeviceDescriptorHandler<MutableDevice> {
90  
91          public RootHandler(MutableDevice instance, SAXParser parser) {
92              super(instance, parser);
93          }
94  
95          @Override
96          public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
97  
98              if (element.equals(SpecVersionHandler.EL)) {
99                  MutableUDAVersion udaVersion = new MutableUDAVersion();
100                 getInstance().udaVersion = udaVersion;
101                 new SpecVersionHandler(udaVersion, this);
102             }
103 
104             if (element.equals(DeviceHandler.EL)) {
105                 new DeviceHandler(getInstance(), this);
106             }
107 
108         }
109 
110         @Override
111         public void endElement(ELEMENT element) throws SAXException {
112             switch (element) {
113                 case URLBase:
114                     try {
115                         String urlString = getCharacters();
116                         if (urlString != null && urlString.length() > 0) {
117                             // We hope it's  RFC 2396 and RFC 2732 compliant
118                             getInstance().baseURL = new URL(urlString);
119                         }
120                     } catch (Exception ex) {
121                         throw new SAXException("Invalid URLBase: " + ex.toString());
122                     }
123                     break;
124             }
125         }
126     }
127 
128     protected static class SpecVersionHandler extends DeviceDescriptorHandler<MutableUDAVersion> {
129 
130         public static final ELEMENT EL = ELEMENT.specVersion;
131 
132         public SpecVersionHandler(MutableUDAVersion instance, DeviceDescriptorHandler parent) {
133             super(instance, parent);
134         }
135 
136         @Override
137         public void endElement(ELEMENT element) throws SAXException {
138             switch (element) {
139                 case major:
140                     String majorVersion = getCharacters().trim();
141                     if (!majorVersion.equals("1")) {
142                         log.warning("Unsupported UDA major version, ignoring: " + majorVersion);
143                         majorVersion = "1";
144                     }
145                     getInstance().major = Integer.valueOf(majorVersion);
146                     break;
147                 case minor:
148                     String minorVersion = getCharacters().trim();
149                     if (!minorVersion.equals("0")) {
150                         log.warning("Unsupported UDA minor version, ignoring: " + minorVersion);
151                         minorVersion = "0";
152                     }
153                     getInstance().minor = Integer.valueOf(minorVersion);
154                     break;
155             }
156         }
157 
158         @Override
159         public boolean isLastElement(ELEMENT element) {
160             return element.equals(EL);
161         }
162     }
163 
164     protected static class DeviceHandler extends DeviceDescriptorHandler<MutableDevice> {
165 
166         public static final ELEMENT EL = ELEMENT.device;
167 
168         public DeviceHandler(MutableDevice instance, DeviceDescriptorHandler parent) {
169             super(instance, parent);
170         }
171 
172         @Override
173         public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
174 
175             if (element.equals(IconListHandler.EL)) {
176                 List<MutableIcon> icons = new ArrayList<>();
177                 getInstance().icons = icons;
178                 new IconListHandler(icons, this);
179             }
180 
181             if (element.equals(ServiceListHandler.EL)) {
182                 List<MutableService> services = new ArrayList<>();
183                 getInstance().services = services;
184                 new ServiceListHandler(services, this);
185             }
186 
187             if (element.equals(DeviceListHandler.EL)) {
188                 List<MutableDevice> devices = new ArrayList<>();
189                 getInstance().embeddedDevices = devices;
190                 new DeviceListHandler(devices, this);
191             }
192         }
193 
194         @Override
195         public void endElement(ELEMENT element) throws SAXException {
196             switch (element) {
197                 case deviceType:
198                     getInstance().deviceType = getCharacters();
199                     break;
200                 case friendlyName:
201                     getInstance().friendlyName = getCharacters();
202                     break;
203                 case manufacturer:
204                     getInstance().manufacturer = getCharacters();
205                     break;
206                 case manufacturerURL:
207                     getInstance().manufacturerURI = parseURI(getCharacters());
208                     break;
209                 case modelDescription:
210                     getInstance().modelDescription = getCharacters();
211                     break;
212                 case modelName:
213                     getInstance().modelName = getCharacters();
214                     break;
215                 case modelNumber:
216                     getInstance().modelNumber = getCharacters();
217                     break;
218                 case modelURL:
219                     getInstance().modelURI = parseURI(getCharacters());
220                     break;
221                 case presentationURL:
222                     getInstance().presentationURI = parseURI(getCharacters());
223                     break;
224                 case UPC:
225                     getInstance().upc = getCharacters();
226                     break;
227                 case serialNumber:
228                     getInstance().serialNumber = getCharacters();
229                     break;
230                 case UDN:
231                     getInstance().udn = UDN.valueOf(getCharacters());
232                     break;
233                 case X_DLNADOC:
234                     String txt = getCharacters();
235                     try {
236                         getInstance().dlnaDocs.add(DLNADoc.valueOf(txt));
237                     } catch (InvalidValueException ex) {
238                         log.info("Invalid X_DLNADOC value, ignoring value: " + txt);
239                     }
240                     break;
241                 case X_DLNACAP:
242                     getInstance().dlnaCaps = DLNACaps.valueOf(getCharacters());
243                     break;
244             }
245         }
246 
247         @Override
248         public boolean isLastElement(ELEMENT element) {
249             return element.equals(EL);
250         }
251     }
252 
253     protected static class IconListHandler extends DeviceDescriptorHandler<List<MutableIcon>> {
254 
255         public static final ELEMENT EL = ELEMENT.iconList;
256 
257         public IconListHandler(List<MutableIcon> instance, DeviceDescriptorHandler parent) {
258             super(instance, parent);
259         }
260 
261         @Override
262         public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
263             if (element.equals(IconHandler.EL)) {
264                 MutableIcon icon = new MutableIcon();
265                 getInstance().add(icon);
266                 new IconHandler(icon, this);
267             }
268         }
269 
270         @Override
271         public boolean isLastElement(ELEMENT element) {
272             return element.equals(EL);
273         }
274     }
275 
276     protected static class IconHandler extends DeviceDescriptorHandler<MutableIcon> {
277 
278         public static final ELEMENT EL = ELEMENT.icon;
279 
280         public IconHandler(MutableIcon instance, DeviceDescriptorHandler parent) {
281             super(instance, parent);
282         }
283 
284         @Override
285         public void endElement(ELEMENT element) throws SAXException {
286             switch (element) {
287                 case width:
288                     getInstance().width = Integer.valueOf(getCharacters());
289                     break;
290                 case height:
291                     getInstance().height = Integer.valueOf(getCharacters());
292                     break;
293                 case depth:
294                 	try {
295                 		getInstance().depth = Integer.valueOf(getCharacters());
296                 	} catch(NumberFormatException ex) {
297                 		log.warning("Invalid icon depth '" + getCharacters() + "', using 16 as default: " + ex);
298                 		getInstance().depth = 16;
299                 	}
300                     break;
301                 case url:
302                     getInstance().uri = parseURI(getCharacters());
303                     break;
304                 case mimetype:
305                     try {
306                         getInstance().mimeType = getCharacters();
307                         MimeType.valueOf(getInstance().mimeType);
308                     } catch(IllegalArgumentException ex) {
309                         log.warning("Ignoring invalid icon mime type: " + getInstance().mimeType);
310                         getInstance().mimeType = "";
311                     }
312                     break;
313             }
314         }
315 
316         @Override
317         public boolean isLastElement(ELEMENT element) {
318             return element.equals(EL);
319         }
320     }
321 
322     protected static class ServiceListHandler extends DeviceDescriptorHandler<List<MutableService>> {
323 
324         public static final ELEMENT EL = ELEMENT.serviceList;
325 
326         public ServiceListHandler(List<MutableService> instance, DeviceDescriptorHandler parent) {
327             super(instance, parent);
328         }
329 
330         @Override
331         public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
332             if (element.equals(ServiceHandler.EL)) {
333                 MutableService service = new MutableService();
334                 getInstance().add(service);
335                 new ServiceHandler(service, this);
336             }
337         }
338 
339         @Override
340         public boolean isLastElement(ELEMENT element) {
341             boolean last = element.equals(EL);
342             if (last) {
343                 Iterator<MutableService> it = getInstance().iterator();
344                 while (it.hasNext()) {
345                     MutableService service = it.next();
346                     if (service.serviceType == null || service.serviceId == null)
347                         it.remove();
348                 }
349             }
350             return last;
351         }
352     }
353 
354     protected static class ServiceHandler extends DeviceDescriptorHandler<MutableService> {
355 
356         public static final ELEMENT EL = ELEMENT.service;
357 
358         public ServiceHandler(MutableService instance, DeviceDescriptorHandler parent) {
359             super(instance, parent);
360         }
361 
362         @Override
363         public void endElement(ELEMENT element) throws SAXException {
364             try {
365                 switch (element) {
366                     case serviceType:
367                         getInstance().serviceType = ServiceType.valueOf(getCharacters());
368                         break;
369                     case serviceId:
370                         getInstance().serviceId = ServiceId.valueOf(getCharacters());
371                         break;
372                     case SCPDURL:
373                         getInstance().descriptorURI = parseURI(getCharacters());
374                         break;
375                     case controlURL:
376                         getInstance().controlURI = parseURI(getCharacters());
377                         break;
378                     case eventSubURL:
379                         getInstance().eventSubscriptionURI = parseURI(getCharacters());
380                         break;
381                 }
382             } catch (InvalidValueException ex) {
383                 log.warning(
384                     "UPnP specification violation, skipping invalid service declaration. " + ex.getMessage()
385                 );
386             }
387         }
388 
389         @Override
390         public boolean isLastElement(ELEMENT element) {
391             return element.equals(EL);
392         }
393     }
394 
395     protected static class DeviceListHandler extends DeviceDescriptorHandler<List<MutableDevice>> {
396 
397         public static final ELEMENT EL = ELEMENT.deviceList;
398 
399         public DeviceListHandler(List<MutableDevice> instance, DeviceDescriptorHandler parent) {
400             super(instance, parent);
401         }
402 
403         @Override
404         public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
405             if (element.equals(DeviceHandler.EL)) {
406                 MutableDevice device = new MutableDevice();
407                 getInstance().add(device);
408                 new DeviceHandler(device, this);
409             }
410         }
411 
412         @Override
413         public boolean isLastElement(ELEMENT element) {
414             return element.equals(EL);
415         }
416     }
417 
418     protected static class DeviceDescriptorHandler<I> extends SAXParser.Handler<I> {
419 
420         public DeviceDescriptorHandler(I instance) {
421             super(instance);
422         }
423 
424         public DeviceDescriptorHandler(I instance, SAXParser parser) {
425             super(instance, parser);
426         }
427 
428         public DeviceDescriptorHandler(I instance, DeviceDescriptorHandler parent) {
429             super(instance, parent);
430         }
431 
432         public DeviceDescriptorHandler(I instance, SAXParser parser, DeviceDescriptorHandler parent) {
433             super(instance, parser, parent);
434         }
435 
436         @Override
437         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
438             super.startElement(uri, localName, qName, attributes);
439             ELEMENT el = ELEMENT.valueOrNullOf(localName);
440             if (el == null) return;
441             startElement(el, attributes);
442         }
443 
444         @Override
445         public void endElement(String uri, String localName, String qName) throws SAXException {
446             super.endElement(uri, localName, qName);
447             ELEMENT el = ELEMENT.valueOrNullOf(localName);
448             if (el == null) return;
449             endElement(el);
450         }
451 
452         @Override
453         protected boolean isLastElement(String uri, String localName, String qName) {
454             ELEMENT el = ELEMENT.valueOrNullOf(localName);
455             return el != null && isLastElement(el);
456         }
457 
458         public void startElement(ELEMENT element, Attributes attributes) throws SAXException {
459 
460         }
461 
462         public void endElement(ELEMENT element) throws SAXException {
463 
464         }
465 
466         public boolean isLastElement(ELEMENT element) {
467             return false;
468         }
469     }
470 }