View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.mediapackage.selector;
23  
24  import org.opencastproject.mediapackage.MediaPackage;
25  import org.opencastproject.mediapackage.MediaPackageElement;
26  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
27  import org.opencastproject.mediapackage.MediaPackageElementSelector;
28  import org.opencastproject.mediapackage.Publication;
29  
30  import java.lang.reflect.ParameterizedType;
31  import java.lang.reflect.Type;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashSet;
36  import java.util.LinkedHashSet;
37  import java.util.List;
38  import java.util.Optional;
39  import java.util.Set;
40  
41  /**
42   * This selector will return any <code>MediaPackageElement</code>s from a <code>MediaPackage</code> that matches the tag
43   * and flavors.
44   */
45  public abstract class AbstractMediaPackageElementSelector<T extends MediaPackageElement> implements
46          MediaPackageElementSelector<T> {
47  
48    /** The tags */
49    protected Set<String> tags = new HashSet<String>();
50  
51    /** The tags to exclude */
52    protected Set<String> excludeTags = new HashSet<String>();
53  
54    /** The flavors */
55    protected List<MediaPackageElementFlavor> flavors = new ArrayList<MediaPackageElementFlavor>();
56  
57    /**
58     * The prefix indicating that a tag should be excluded from a search for elements using
59     * {@link MediaPackage#getElementsByTags(Collection)}
60     */
61    public static final String NEGATE_TAG_PREFIX = "-";
62  
63    /**
64     * This base implementation will return those media package elements that match the type specified as the type
65     * parameter to the class and that flavor (if specified) AND at least one of the tags (if specified) match.
66     */
67    @Override
68    public Collection<T> select(MediaPackage mediaPackage, boolean withTagsAndFlavors) {
69      return select(Arrays.asList(mediaPackage.getElements()), withTagsAndFlavors);
70    }
71  
72    /**
73     * Similar to above, but will get media package elements from the given publication instead.
74     */
75    @Override
76    public Collection<T> select(MediaPackage mediaPackage, String publicationChannel, boolean withTagsAndFlavors) {
77      Optional<Publication> publication = Arrays.stream(mediaPackage.getPublications())
78          .filter(channel -> channel.getChannel().equals(publicationChannel)).findFirst();
79  
80      // Skip if the media package does not contain a matching publication
81      if (publication.isEmpty()) {
82        return new LinkedHashSet<T>();
83      }
84  
85      List<MediaPackageElement> list = new ArrayList(Arrays.asList(publication.get().getTracks()));
86      list.addAll(new ArrayList(Arrays.asList(publication.get().getAttachments())));
87      list.addAll(new ArrayList(Arrays.asList(publication.get().getCatalogs())));
88  
89      return select(list, withTagsAndFlavors);
90    }
91  
92    @Override
93    @SuppressWarnings("unchecked")
94    public Collection<T> select(List<MediaPackageElement> elements, boolean withTagsAndFlavors) {
95      Set<T> result = new LinkedHashSet<T>();
96  
97      // If no flavors and tags are set, return empty list
98      if (flavors.isEmpty() && tags.isEmpty()) {
99        return result;
100     }
101 
102     Class type = getParametrizedType(result);
103     elementLoop: for (MediaPackageElement e : elements) {
104 
105       // Does the type match?
106       if (type.isAssignableFrom(e.getClass())) {
107 
108         for (String tag : e.getTags()) {
109           if (excludeTags.contains(tag)) {
110             continue elementLoop;
111           }
112         }
113 
114         // Any of the flavors?
115         boolean matchesFlavor = false;
116         for (MediaPackageElementFlavor flavor : flavors) {
117           if (flavor.matches(e.getFlavor())) {
118             matchesFlavor = true;
119             break;
120           }
121         }
122 
123         if (flavors.isEmpty()) {
124           matchesFlavor = true;
125         }
126 
127         // If the elements selection is done by tags AND flavors
128         if (withTagsAndFlavors && matchesFlavor && e.containsTag(tags)) {
129           result.add((T) e);
130         }
131         // Otherwise if only one of these parameters is necessary to select an element
132         if (!withTagsAndFlavors && ((!flavors.isEmpty() && matchesFlavor)
133             || (!tags.isEmpty() && e.containsTag(tags)))) {
134           result.add((T) e);
135         }
136       }
137     }
138 
139     return result;
140   }
141 
142   /**
143    * This constructor tries to determine the entity type from the type argument used by a concrete implementation of
144    * <code>GenericHibernateDao</code>.
145    * <p>
146    * Note: This code will only work for immediate specialization, and especially not for subclasses.
147    */
148   @SuppressWarnings("unchecked")
149   private Class getParametrizedType(Object object) {
150     Class current = getClass();
151     Type superclass;
152     Class<? extends T> entityClass = null;
153     while ((superclass = current.getGenericSuperclass()) != null) {
154       if (superclass instanceof ParameterizedType) {
155         entityClass = (Class<T>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
156         break;
157       } else if (superclass instanceof Class) {
158         current = (Class) superclass;
159       } else {
160         break;
161       }
162     }
163     if (entityClass == null) {
164       throw new IllegalStateException("Cannot determine entity type because " + getClass().getName()
165               + " does not specify any type parameter.");
166     }
167     return entityClass;
168   }
169 
170   /**
171    * Sets the flavors.
172    * <p>
173    * Note that the order is relevant to the selection of the track returned by this selector.
174    *
175    * @param flavors
176    *          the list of flavors
177    * @throws IllegalArgumentException
178    *           if the flavors list is <code>null</code>
179    */
180   public void setFlavors(List<MediaPackageElementFlavor> flavors) {
181     if (flavors == null) {
182       throw new IllegalArgumentException("List of flavors must not be null");
183     }
184     this.flavors = flavors;
185   }
186 
187   /**
188    * Adds the given flavor to the list of flavors.
189    * <p>
190    * Note that the order is relevant to the selection of the track returned by this selector.
191    *
192    * @param flavor
193    */
194   public void addFlavor(MediaPackageElementFlavor flavor) {
195     if (flavor == null) {
196       throw new IllegalArgumentException("Flavor must not be null");
197     }
198     if (!flavors.contains(flavor)) {
199       flavors.add(flavor);
200     }
201   }
202 
203   /**
204    * Adds the given flavor to the list of flavors.
205    * <p>
206    * Note that the order is relevant to the selection of the track returned by this selector.
207    *
208    * @param flavor
209    */
210   public void addFlavor(String flavor) {
211     if (flavor == null) {
212       throw new IllegalArgumentException("Flavor must not be null");
213     }
214     MediaPackageElementFlavor f = MediaPackageElementFlavor.parseFlavor(flavor);
215     if (!flavors.contains(f)) {
216       flavors.add(f);
217     }
218   }
219 
220   /**
221    * Adds the given flavor to the list of flavors.
222    * <p>
223    * Note that the order is relevant to the selection of the track returned by this selector.
224    *
225    * @param index
226    *          the position in the list
227    * @param flavor
228    *          the flavor to add
229    */
230   public void addFlavorAt(int index, MediaPackageElementFlavor flavor) {
231     if (flavor == null) {
232       throw new IllegalArgumentException("Flavor must not be null");
233     }
234     flavors.add(index, flavor);
235     for (int i = index + 1; i < flavors.size(); i++) {
236       if (flavors.get(i).equals(flavor)) {
237         flavors.remove(i);
238       }
239     }
240   }
241 
242   /**
243    * Adds the given flavor to the list of flavors.
244    * <p>
245    * Note that the order is relevant to the selection of the track returned by this selector.
246    *
247    * @param index
248    *          the position in the list
249    * @param flavor
250    *          the flavor to add
251    */
252   public void addFlavorAt(int index, String flavor) {
253     if (flavor == null) {
254       throw new IllegalArgumentException("Flavor must not be null");
255     }
256     MediaPackageElementFlavor f = MediaPackageElementFlavor.parseFlavor(flavor);
257     flavors.add(index, f);
258     for (int i = index + 1; i < flavors.size(); i++) {
259       if (flavors.get(i).equals(f)) {
260         flavors.remove(i);
261       }
262     }
263   }
264 
265   /**
266    * Removes all occurences of the given flavor from the list of flavors.
267    *
268    * @param flavor
269    *          the flavor to remove
270    */
271   public void removeFlavor(MediaPackageElementFlavor flavor) {
272     if (flavor == null) {
273       throw new IllegalArgumentException("Flavor must not be null");
274     }
275     flavors.remove(flavor);
276   }
277 
278   /**
279    * Removes all occurences of the given flavor from the list of flavors.
280    *
281    * @param flavor
282    *          the flavor to remove
283    */
284   public void removeFlavor(String flavor) {
285     if (flavor == null) {
286       throw new IllegalArgumentException("Flavor must not be null");
287     }
288     flavors.remove(MediaPackageElementFlavor.parseFlavor(flavor));
289   }
290 
291   /**
292    * Removes all occurences of the given flavor from the list of flavors.
293    *
294    * @param index
295    *          the position in the list
296    */
297   public void removeFlavorAt(int index) {
298     flavors.remove(index);
299   }
300 
301   /**
302    * Returns the list of flavors.
303    *
304    * @return the flavors
305    */
306   public MediaPackageElementFlavor[] getFlavors() {
307     return flavors.toArray(new MediaPackageElementFlavor[flavors.size()]);
308   }
309 
310   /**
311    * Adds <code>tag</code> to the list of tags that are used to select the media.
312    *
313    * @param tag
314    *          the tag to include
315    */
316   public void addTag(String tag) {
317     if (tag.startsWith(NEGATE_TAG_PREFIX)) {
318       excludeTags.add(tag.substring(NEGATE_TAG_PREFIX.length()));
319     } else {
320       tags.add(tag);
321     }
322   }
323 
324   /**
325    * Adds <code>tag</code> to the list of tags that are used to select the media.
326    *
327    * @param tag
328    *          the tag to include
329    */
330   public void removeTag(String tag) {
331     tags.remove(tag);
332   }
333 
334   /**
335    * Returns the tags.
336    *
337    * @return the tags
338    */
339   public String[] getTags() {
340     return tags.toArray(new String[tags.size()]);
341   }
342 
343   /**
344    * Removes all of the tags from this selector.
345    */
346   public void clearTags() {
347     tags.clear();
348   }
349 
350 }