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  package org.fourthline.cling.support.contentdirectory;
16  
17  import org.fourthline.cling.binding.annotations.UpnpAction;
18  import org.fourthline.cling.binding.annotations.UpnpInputArgument;
19  import org.fourthline.cling.binding.annotations.UpnpOutputArgument;
20  import org.fourthline.cling.binding.annotations.UpnpService;
21  import org.fourthline.cling.binding.annotations.UpnpServiceId;
22  import org.fourthline.cling.binding.annotations.UpnpServiceType;
23  import org.fourthline.cling.binding.annotations.UpnpStateVariable;
24  import org.fourthline.cling.binding.annotations.UpnpStateVariables;
25  import org.fourthline.cling.model.types.ErrorCode;
26  import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
27  import org.fourthline.cling.model.types.csv.CSV;
28  import org.fourthline.cling.model.types.csv.CSVString;
29  import org.fourthline.cling.support.model.BrowseFlag;
30  import org.fourthline.cling.support.model.BrowseResult;
31  import org.fourthline.cling.support.model.DIDLContent;
32  import org.fourthline.cling.support.model.SortCriterion;
33  
34  import java.beans.PropertyChangeSupport;
35  import java.util.ArrayList;
36  import java.util.List;
37  
38  /**
39   * Simple ContentDirectory service skeleton.
40   * <p>
41   * Only state variables and actions required by <em>ContentDirectory:1</em>
42   * (not the optional ones) are implemented.
43   * </p>
44   *
45   * @author Alessio Gaeta
46   * @author Christian Bauer
47   */
48  
49  @UpnpService(
50          serviceId = @UpnpServiceId("ContentDirectory"),
51          serviceType = @UpnpServiceType(value = "ContentDirectory", version = 1)
52  )
53  
54  @UpnpStateVariables({
55                              @UpnpStateVariable(
56                                      name = "A_ARG_TYPE_ObjectID",
57                                      sendEvents = false,
58                                      datatype = "string"),
59                              @UpnpStateVariable(
60                                      name = "A_ARG_TYPE_Result",
61                                      sendEvents = false,
62                                      datatype = "string"),
63                              @UpnpStateVariable(
64                                      name = "A_ARG_TYPE_BrowseFlag",
65                                      sendEvents = false,
66                                      datatype = "string",
67                                      allowedValuesEnum = BrowseFlag.class),
68                              @UpnpStateVariable(
69                                      name = "A_ARG_TYPE_Filter",
70                                      sendEvents = false,
71                                      datatype = "string"),
72                              @UpnpStateVariable(
73                                      name = "A_ARG_TYPE_SortCriteria",
74                                      sendEvents = false,
75                                      datatype = "string"),
76                              @UpnpStateVariable(
77                                      name = "A_ARG_TYPE_Index",
78                                      sendEvents = false,
79                                      datatype = "ui4"),
80                              @UpnpStateVariable(
81                                      name = "A_ARG_TYPE_Count",
82                                      sendEvents = false,
83                                      datatype = "ui4"),
84                              @UpnpStateVariable(
85                                      name = "A_ARG_TYPE_UpdateID",
86                                      sendEvents = false,
87                                      datatype = "ui4"),
88                              @UpnpStateVariable(
89                                      name = "A_ARG_TYPE_URI",
90                                      sendEvents = false,
91                                      datatype = "uri"),
92                              @UpnpStateVariable(
93                                      name = "A_ARG_TYPE_SearchCriteria",
94                                      sendEvents = false,
95                                      datatype = "string")
96                      })
97  public abstract class AbstractContentDirectoryService {
98  
99      public static final String CAPS_WILDCARD = "*";
100 
101     @UpnpStateVariable(sendEvents = false)
102     final private CSV<String> searchCapabilities;
103 
104     @UpnpStateVariable(sendEvents = false)
105     final private CSV<String> sortCapabilities;
106 
107     @UpnpStateVariable(
108             sendEvents = true,
109             defaultValue = "0",
110             eventMaximumRateMilliseconds = 200
111     )
112     private UnsignedIntegerFourBytes systemUpdateID = new UnsignedIntegerFourBytes(0);
113 
114     final protected PropertyChangeSupport propertyChangeSupport;
115 
116     protected AbstractContentDirectoryService() {
117         this(new ArrayList<String>(), new ArrayList<String>(), null);
118     }
119 
120     protected AbstractContentDirectoryService(List<String> searchCapabilities, List<String> sortCapabilities) {
121         this(searchCapabilities, sortCapabilities, null);
122     }
123 
124     protected AbstractContentDirectoryService(List<String> searchCapabilities, List<String> sortCapabilities,
125                                               PropertyChangeSupport propertyChangeSupport) {
126         this.propertyChangeSupport = propertyChangeSupport != null ? propertyChangeSupport : new PropertyChangeSupport(this);
127         this.searchCapabilities = new CSVString();
128         this.searchCapabilities.addAll(searchCapabilities);
129         this.sortCapabilities = new CSVString();
130         this.sortCapabilities.addAll(sortCapabilities);
131     }
132 
133     @UpnpAction(out = @UpnpOutputArgument(name = "SearchCaps"))
134     public CSV<String> getSearchCapabilities() {
135         return searchCapabilities;
136     }
137 
138     @UpnpAction(out = @UpnpOutputArgument(name = "SortCaps"))
139     public CSV<String> getSortCapabilities() {
140         return sortCapabilities;
141     }
142 
143     @UpnpAction(out = @UpnpOutputArgument(name = "Id"))
144     synchronized public UnsignedIntegerFourBytes getSystemUpdateID() {
145         return systemUpdateID;
146     }
147 
148     public PropertyChangeSupport getPropertyChangeSupport() {
149         return propertyChangeSupport;
150     }
151 
152     /**
153      * Call this method after making changes to your content directory.
154      * <p>
155      * This will notify clients that their view of the content directory is potentially
156      * outdated and has to be refreshed.
157      * </p>
158      */
159     synchronized protected void changeSystemUpdateID() {
160         Long oldUpdateID = getSystemUpdateID().getValue();
161         systemUpdateID.increment(true);
162         getPropertyChangeSupport().firePropertyChange(
163                 "SystemUpdateID",
164                 oldUpdateID,
165                 getSystemUpdateID().getValue()
166         );
167     }
168 
169     @UpnpAction(out = {
170             @UpnpOutputArgument(name = "Result",
171                                 stateVariable = "A_ARG_TYPE_Result",
172                                 getterName = "getResult"),
173             @UpnpOutputArgument(name = "NumberReturned",
174                                 stateVariable = "A_ARG_TYPE_Count",
175                                 getterName = "getCount"),
176             @UpnpOutputArgument(name = "TotalMatches",
177                                 stateVariable = "A_ARG_TYPE_Count",
178                                 getterName = "getTotalMatches"),
179             @UpnpOutputArgument(name = "UpdateID",
180                                 stateVariable = "A_ARG_TYPE_UpdateID",
181                                 getterName = "getContainerUpdateID")
182     })
183     public BrowseResult browse(
184             @UpnpInputArgument(name = "ObjectID", aliases = "ContainerID") String objectId,
185             @UpnpInputArgument(name = "BrowseFlag") String browseFlag,
186             @UpnpInputArgument(name = "Filter") String filter,
187             @UpnpInputArgument(name = "StartingIndex", stateVariable = "A_ARG_TYPE_Index") UnsignedIntegerFourBytes firstResult,
188             @UpnpInputArgument(name = "RequestedCount", stateVariable = "A_ARG_TYPE_Count") UnsignedIntegerFourBytes maxResults,
189             @UpnpInputArgument(name = "SortCriteria") String orderBy)
190             throws ContentDirectoryException {
191 
192         SortCriterion[] orderByCriteria;
193         try {
194             orderByCriteria = SortCriterion.valueOf(orderBy);
195         } catch (Exception ex) {
196             throw new ContentDirectoryException(ContentDirectoryErrorCode.UNSUPPORTED_SORT_CRITERIA, ex.toString());
197         }
198 
199         try {
200             return browse(
201                     objectId,
202                     BrowseFlag.valueOrNullOf(browseFlag),
203                     filter,
204                     firstResult.getValue(), maxResults.getValue(),
205                     orderByCriteria
206             );
207         } catch (ContentDirectoryException ex) {
208             throw ex;
209         } catch (Exception ex) {
210             throw new ContentDirectoryException(ErrorCode.ACTION_FAILED, ex.toString());
211         }
212     }
213 
214     /**
215      * Implement this method to implement browsing of your content.
216      * <p>
217      * This is a required action defined by <em>ContentDirectory:1</em>.
218      * </p>
219      * <p>
220      * You should wrap any exception into a {@link ContentDirectoryException}, so a propery
221      * error message can be returned to control points.
222      * </p>
223      */
224     public abstract BrowseResult browse(String objectID, BrowseFlag browseFlag,
225                                         String filter,
226                                         long firstResult, long maxResults,
227                                         SortCriterion[] orderby) throws ContentDirectoryException;
228 
229 
230     @UpnpAction(out = {
231             @UpnpOutputArgument(name = "Result",
232                                 stateVariable = "A_ARG_TYPE_Result",
233                                 getterName = "getResult"),
234             @UpnpOutputArgument(name = "NumberReturned",
235                                 stateVariable = "A_ARG_TYPE_Count",
236                                 getterName = "getCount"),
237             @UpnpOutputArgument(name = "TotalMatches",
238                                 stateVariable = "A_ARG_TYPE_Count",
239                                 getterName = "getTotalMatches"),
240             @UpnpOutputArgument(name = "UpdateID",
241                                 stateVariable = "A_ARG_TYPE_UpdateID",
242                                 getterName = "getContainerUpdateID")
243     })
244     public BrowseResult search(
245             @UpnpInputArgument(name = "ContainerID", stateVariable = "A_ARG_TYPE_ObjectID") String containerId,
246             @UpnpInputArgument(name = "SearchCriteria") String searchCriteria,
247             @UpnpInputArgument(name = "Filter") String filter,
248             @UpnpInputArgument(name = "StartingIndex", stateVariable = "A_ARG_TYPE_Index") UnsignedIntegerFourBytes firstResult,
249             @UpnpInputArgument(name = "RequestedCount", stateVariable = "A_ARG_TYPE_Count") UnsignedIntegerFourBytes maxResults,
250             @UpnpInputArgument(name = "SortCriteria") String orderBy)
251             throws ContentDirectoryException {
252 
253         SortCriterion[] orderByCriteria;
254         try {
255             orderByCriteria = SortCriterion.valueOf(orderBy);
256         } catch (Exception ex) {
257             throw new ContentDirectoryException(ContentDirectoryErrorCode.UNSUPPORTED_SORT_CRITERIA, ex.toString());
258         }
259 
260         try {
261             return search(
262                     containerId,
263                     searchCriteria,
264                     filter,
265                     firstResult.getValue(), maxResults.getValue(),
266                     orderByCriteria
267             );
268         } catch (ContentDirectoryException ex) {
269             throw ex;
270         } catch (Exception ex) {
271             throw new ContentDirectoryException(ErrorCode.ACTION_FAILED, ex.toString());
272         }
273     }
274 
275     /**
276      * Override this method to implement searching of your content.
277      * <p>
278      * The default implementation returns an empty result.
279      * </p>
280      */
281     public BrowseResult search(String containerId, String searchCriteria, String filter,
282                                long firstResult, long maxResults, SortCriterion[] orderBy) throws ContentDirectoryException {
283 
284         try {
285             return new BrowseResult(new DIDLParser().generate(new DIDLContent()), 0, 0);
286         } catch (Exception ex) {
287             throw new ContentDirectoryException(ErrorCode.ACTION_FAILED, ex.toString());
288         }
289     }
290 }