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.model;
17  
18  import java.net.URI;
19  import java.net.URISyntaxException;
20  import java.util.ArrayList;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Set;
24  import java.util.logging.Logger;
25  
26  import org.fourthline.cling.model.meta.Device;
27  import org.fourthline.cling.model.meta.Icon;
28  import org.fourthline.cling.model.meta.Service;
29  import org.fourthline.cling.model.resource.Resource;
30  import org.seamless.util.URIUtil;
31  
32  /**
33   * Enforces path conventions for all locally offered resources (descriptors, icons, etc.)
34   * <p>
35   * Every descriptor, icon, event callback, or action message is send to a URL. This namespace
36   * defines how the path of this URL will look like and it will build the path for a given
37   * resource.
38   * </p>
39   * <p>
40   * By default, the namespace is organized as follows:
41   * </p>
42   * <pre>{@code
43   * http://host:port/dev/<udn>/desc.xml
44   * http://host:port/dev/<udn>/svc/<svcIdNamespace>/<svcId>/desc.xml
45   * http://host:port/dev/<udn>/svc/<svcIdNamespace>/<svcId>/action
46   * http://host:port/dev/<udn>/svc/<svcIdNamespace>/<svcId>/event
47   * http://host:port/dev/<ThisIsEitherRootUDN>/svc/<svcIdNamespace>/<svcId>/event/cb.xml
48   * http://host:port/dev/<OrEvenAnEmbeddedDevicesUDN>/svc/<svcIdNamespace>/<svcId>/action
49   * ...
50   * }</pre>
51   * <p>
52   * The namespace is also used to discover and create all {@link org.fourthline.cling.model.resource.Resource}s
53   * given a {@link org.fourthline.cling.model.meta.Device}'s metadata. This procedure is typically
54   * invoked once, when the device is added to the {@link org.fourthline.cling.registry.Registry}.
55   * </p>
56   *
57   * @author Christian Bauer
58   */
59  public class Namespace {
60  
61      final private static Logger log = Logger.getLogger(Namespace.class.getName());
62  
63      public static final String DEVICE = "/dev";
64      public static final String SERVICE = "/svc";
65      public static final String CONTROL = "/action";
66      public static final String EVENTS = "/event";
67      public static final String DESCRIPTOR_FILE = "/desc";
68      public static final String CALLBACK_FILE = "/cb";
69  
70      final protected URI basePath;
71      final protected String decodedPath;
72  
73      public Namespace() {
74          this("");
75      }
76  
77      public Namespace(String basePath) {
78          this(URI.create(basePath));
79      }
80  
81      public Namespace(URI basePath) {
82          this.basePath = basePath;
83          this.decodedPath = basePath.getPath();
84      }
85  
86      public URI getBasePath() {
87          return basePath;
88      }
89  
90      public URI getPath(Device device) {
91          return appendPathToBaseURI(getDevicePath(device));
92      }
93  
94      public URI getPath(Service service) {
95          return appendPathToBaseURI(getServicePath(service));
96      }
97  
98      public URI getDescriptorPath(Device device) {
99          return appendPathToBaseURI(getDevicePath(device.getRoot()) + DESCRIPTOR_FILE);
100     }
101 
102     /**
103      * Performance optimization, avoids URI manipulation.
104      */
105     public String getDescriptorPathString(Device device) {
106         return decodedPath + getDevicePath(device.getRoot()) + DESCRIPTOR_FILE;
107     }
108 
109     public URI getDescriptorPath(Service service) {
110         return appendPathToBaseURI(getServicePath(service) + DESCRIPTOR_FILE);
111     }
112 
113     public URI getControlPath(Service service) {
114         return appendPathToBaseURI(getServicePath(service) + CONTROL);
115     }
116 
117     public URI getIconPath(Icon icon) {
118         return appendPathToBaseURI(getDevicePath(icon.getDevice()) + "/" + icon.getUri().toString());
119     }
120 
121     public URI getEventSubscriptionPath(Service service) {
122         return appendPathToBaseURI(getServicePath(service) + EVENTS);
123     }
124 
125     public URI getEventCallbackPath(Service service) {
126         return appendPathToBaseURI(getServicePath(service) + EVENTS + CALLBACK_FILE);
127     }
128 
129     /**
130      * Performance optimization, avoids URI manipulation.
131      */
132     public String getEventCallbackPathString(Service service) {
133         return decodedPath + getServicePath(service) + EVENTS + CALLBACK_FILE;
134     }
135 
136     public URI prefixIfRelative(Device device, URI uri) {
137         if (!uri.isAbsolute() && !uri.getPath().startsWith("/")) {
138             return appendPathToBaseURI(getDevicePath(device) + "/" + uri);
139         }
140         return uri;
141     }
142 
143     public boolean isControlPath(URI uri) {
144         return uri.toString().endsWith(Namespace.CONTROL);
145     }
146 
147     public boolean isEventSubscriptionPath(URI uri) {
148         return uri.toString().endsWith(Namespace.EVENTS);
149     }
150 
151     public boolean isEventCallbackPath(URI uri) {
152         return uri.toString().endsWith(Namespace.CALLBACK_FILE);
153     }
154 
155     public Resource[] getResources(Device device) throws ValidationException {
156         if (!device.isRoot()) return null;
157 
158         Set<Resource> resources = new HashSet<>();
159         List<ValidationError> errors = new ArrayList<>();
160 
161         log.fine("Discovering local resources of device graph");
162         Resource[] discoveredResources = device.discoverResources(this);
163         for (Resource resource : discoveredResources) {
164             log.finer("Discovered: " + resource);
165             if (!resources.add(resource)) {
166                 log.finer("Local resource already exists, queueing validation error");
167                 errors.add(new ValidationError(
168                     getClass(),
169                     "resources",
170                     "Local URI namespace conflict between resources of device: " + resource
171                 ));
172             }
173         }
174         if (errors.size() > 0) {
175             throw new ValidationException("Validation of device graph failed, call getErrors() on exception", errors);
176         }
177         return resources.toArray(new Resource[resources.size()]);
178     }
179 
180     protected URI appendPathToBaseURI(String path) {
181         try {
182             // not calling getBasePath() on purpose since we're not sure if all DalvikVMs will inline it correctly
183             return
184                 new URI(
185                     basePath.getScheme(),
186                     null,
187                     basePath.getHost(),
188                     basePath.getPort(),
189                     decodedPath + path,
190                     null,
191                     null
192                 );
193         } catch (URISyntaxException e) {
194             return URI.create(basePath + path);
195         }
196     }
197 
198     protected String getDevicePath(Device device) {
199         if (device.getIdentity().getUdn() == null) {
200             throw new IllegalStateException("Can't generate local URI prefix without UDN");
201         }
202         StringBuilder s = new StringBuilder();
203         s.append(DEVICE).append("/");
204 
205         s.append(URIUtil.encodePathSegment(device.getIdentity().getUdn().getIdentifierString()));
206         return s.toString();
207     }
208 
209     protected String getServicePath(Service service) {
210         if (service.getServiceId() == null) {
211             throw new IllegalStateException("Can't generate local URI prefix without service ID");
212         }
213         StringBuilder s = new StringBuilder();
214         s.append(SERVICE);
215         s.append("/");
216         s.append(service.getServiceId().getNamespace());
217         s.append("/");
218         s.append(service.getServiceId().getId());
219         return getDevicePath(service.getDevice()) + s.toString();
220     }
221 }