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     Class type = getParametrizedType(result);
102     elementLoop: for (MediaPackageElement e : elements) {
103 
104       // Does the type match?
105       if (type.isAssignableFrom(e.getClass())) {
106 
107         for (String tag : e.getTags()) {
108           if (excludeTags.contains(tag))
109             continue elementLoop;
110         }
111 
112         // Any of the flavors?
113         boolean matchesFlavor = false;
114         for (MediaPackageElementFlavor flavor : flavors) {
115           if (flavor.matches(e.getFlavor())) {
116             matchesFlavor = true;
117             break;
118           }
119         }
120 
121         if (flavors.isEmpty())
122           matchesFlavor = true;
123 
124         // If the elements selection is done by tags AND flavors
125         if (withTagsAndFlavors && matchesFlavor && e.containsTag(tags))
126           result.add((T) e);
127         // Otherwise if only one of these parameters is necessary to select an element
128         if (!withTagsAndFlavors && ((!flavors.isEmpty() && matchesFlavor) || (!tags.isEmpty() && e.containsTag(tags))))
129           result.add((T) e);
130       }
131     }
132 
133     return result;
134   }
135 
136   /**
137    * This constructor tries to determine the entity type from the type argument used by a concrete implementation of
138    * <code>GenericHibernateDao</code>.
139    * <p>
140    * Note: This code will only work for immediate specialization, and especially not for subclasses.
141    */
142   @SuppressWarnings("unchecked")
143   private Class getParametrizedType(Object object) {
144     Class current = getClass();
145     Type superclass;
146     Class<? extends T> entityClass = null;
147     while ((superclass = current.getGenericSuperclass()) != null) {
148       if (superclass instanceof ParameterizedType) {
149         entityClass = (Class<T>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
150         break;
151       } else if (superclass instanceof Class) {
152         current = (Class) superclass;
153       } else {
154         break;
155       }
156     }
157     if (entityClass == null) {
158       throw new IllegalStateException("Cannot determine entity type because " + getClass().getName()
159               + " does not specify any type parameter.");
160     }
161     return entityClass;
162   }
163 
164   /**
165    * Sets the flavors.
166    * <p>
167    * Note that the order is relevant to the selection of the track returned by this selector.
168    *
169    * @param flavors
170    *          the list of flavors
171    * @throws IllegalArgumentException
172    *           if the flavors list is <code>null</code>
173    */
174   public void setFlavors(List<MediaPackageElementFlavor> flavors) {
175     if (flavors == null)
176       throw new IllegalArgumentException("List of flavors must not be null");
177     this.flavors = flavors;
178   }
179 
180   /**
181    * Adds the given flavor to the list of flavors.
182    * <p>
183    * Note that the order is relevant to the selection of the track returned by this selector.
184    *
185    * @param flavor
186    */
187   public void addFlavor(MediaPackageElementFlavor flavor) {
188     if (flavor == null)
189       throw new IllegalArgumentException("Flavor must not be null");
190     if (!flavors.contains(flavor))
191       flavors.add(flavor);
192   }
193 
194   /**
195    * Adds the given flavor to the list of flavors.
196    * <p>
197    * Note that the order is relevant to the selection of the track returned by this selector.
198    *
199    * @param flavor
200    */
201   public void addFlavor(String flavor) {
202     if (flavor == null)
203       throw new IllegalArgumentException("Flavor must not be null");
204     MediaPackageElementFlavor f = MediaPackageElementFlavor.parseFlavor(flavor);
205     if (!flavors.contains(f))
206       flavors.add(f);
207   }
208 
209   /**
210    * Adds the given flavor to the list of flavors.
211    * <p>
212    * Note that the order is relevant to the selection of the track returned by this selector.
213    *
214    * @param index
215    *          the position in the list
216    * @param flavor
217    *          the flavor to add
218    */
219   public void addFlavorAt(int index, MediaPackageElementFlavor flavor) {
220     if (flavor == null)
221       throw new IllegalArgumentException("Flavor must not be null");
222     flavors.add(index, flavor);
223     for (int i = index + 1; i < flavors.size(); i++) {
224       if (flavors.get(i).equals(flavor))
225         flavors.remove(i);
226     }
227   }
228 
229   /**
230    * Adds the given flavor to the list of flavors.
231    * <p>
232    * Note that the order is relevant to the selection of the track returned by this selector.
233    *
234    * @param index
235    *          the position in the list
236    * @param flavor
237    *          the flavor to add
238    */
239   public void addFlavorAt(int index, String flavor) {
240     if (flavor == null)
241       throw new IllegalArgumentException("Flavor must not be null");
242     MediaPackageElementFlavor f = MediaPackageElementFlavor.parseFlavor(flavor);
243     flavors.add(index, f);
244     for (int i = index + 1; i < flavors.size(); i++) {
245       if (flavors.get(i).equals(f))
246         flavors.remove(i);
247     }
248   }
249 
250   /**
251    * Removes all occurences of the given flavor from the list of flavors.
252    *
253    * @param flavor
254    *          the flavor to remove
255    */
256   public void removeFlavor(MediaPackageElementFlavor flavor) {
257     if (flavor == null)
258       throw new IllegalArgumentException("Flavor must not be null");
259     flavors.remove(flavor);
260   }
261 
262   /**
263    * Removes all occurences of the given flavor from the list of flavors.
264    *
265    * @param flavor
266    *          the flavor to remove
267    */
268   public void removeFlavor(String flavor) {
269     if (flavor == null)
270       throw new IllegalArgumentException("Flavor must not be null");
271     flavors.remove(MediaPackageElementFlavor.parseFlavor(flavor));
272   }
273 
274   /**
275    * Removes all occurences of the given flavor from the list of flavors.
276    *
277    * @param index
278    *          the position in the list
279    */
280   public void removeFlavorAt(int index) {
281     flavors.remove(index);
282   }
283 
284   /**
285    * Returns the list of flavors.
286    *
287    * @return the flavors
288    */
289   public MediaPackageElementFlavor[] getFlavors() {
290     return flavors.toArray(new MediaPackageElementFlavor[flavors.size()]);
291   }
292 
293   /**
294    * Adds <code>tag</code> to the list of tags that are used to select the media.
295    *
296    * @param tag
297    *          the tag to include
298    */
299   public void addTag(String tag) {
300     if (tag.startsWith(NEGATE_TAG_PREFIX)) {
301       excludeTags.add(tag.substring(NEGATE_TAG_PREFIX.length()));
302     } else {
303       tags.add(tag);
304     }
305   }
306 
307   /**
308    * Adds <code>tag</code> to the list of tags that are used to select the media.
309    *
310    * @param tag
311    *          the tag to include
312    */
313   public void removeTag(String tag) {
314     tags.remove(tag);
315   }
316 
317   /**
318    * Returns the tags.
319    *
320    * @return the tags
321    */
322   public String[] getTags() {
323     return tags.toArray(new String[tags.size()]);
324   }
325 
326   /**
327    * Removes all of the tags from this selector.
328    */
329   public void clearTags() {
330     tags.clear();
331   }
332 
333 }