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.elasticsearch.index.objects.event;
23  
24  import static org.opencastproject.security.util.SecurityUtil.getEpisodeRoleId;
25  
26  import org.opencastproject.elasticsearch.api.SearchIndexException;
27  import org.opencastproject.elasticsearch.api.SearchMetadata;
28  import org.opencastproject.elasticsearch.api.SearchResult;
29  import org.opencastproject.elasticsearch.impl.SearchMetadataCollection;
30  import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
31  import org.opencastproject.elasticsearch.index.objects.series.Series;
32  import org.opencastproject.elasticsearch.index.objects.series.SeriesIndexSchema;
33  import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
34  import org.opencastproject.list.api.DefaultResourceListQuery;
35  import org.opencastproject.list.api.ResourceListQuery;
36  import org.opencastproject.mediapackage.Attachment;
37  import org.opencastproject.mediapackage.Catalog;
38  import org.opencastproject.mediapackage.EName;
39  import org.opencastproject.mediapackage.MediaPackage;
40  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
41  import org.opencastproject.mediapackage.Publication;
42  import org.opencastproject.mediapackage.Track;
43  import org.opencastproject.metadata.dublincore.DCMIPeriod;
44  import org.opencastproject.metadata.dublincore.DublinCore;
45  import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
46  import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
47  import org.opencastproject.security.api.AccessControlEntry;
48  import org.opencastproject.security.api.AccessControlList;
49  import org.opencastproject.security.api.AccessControlParser;
50  import org.opencastproject.security.api.Permissions;
51  import org.opencastproject.security.api.Permissions.Action;
52  import org.opencastproject.security.api.User;
53  import org.opencastproject.util.DateTimeSupport;
54  
55  import org.apache.commons.io.IOUtils;
56  import org.apache.commons.lang3.StringUtils;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  import java.io.IOException;
61  import java.nio.charset.Charset;
62  import java.util.ArrayList;
63  import java.util.Arrays;
64  import java.util.Date;
65  import java.util.HashMap;
66  import java.util.HashSet;
67  import java.util.List;
68  import java.util.Map;
69  import java.util.Set;
70  import java.util.TreeSet;
71  
72  import javax.xml.bind.Unmarshaller;
73  
74  /**
75   * Utility implementation to deal with the conversion of recording events and its corresponding index data structures.
76   */
77  public final class EventIndexUtils {
78  
79    private static final Logger logger = LoggerFactory.getLogger(EventIndexUtils.class);
80  
81    // The number of attempts to get the series title in case it hasn't been added to the index.
82    public static final int DEFAULT_ATTEMPTS = 10;
83    // The amount of time in ms to wait before trying to get the series title again.
84    public static final long DEFAULT_SLEEP = 100L;
85  
86    /**
87     * This is a utility class and should therefore not be instantiated.
88     */
89    private EventIndexUtils() {
90    }
91  
92    /**
93     * Creates a search result item based on the data returned from the search index.
94     *
95     * @param metadata
96     *          the search metadata
97     * @param unmarshaller the unmarshaller to use
98     * @return the search result item
99     * @throws IOException
100    *           if unmarshalling fails
101    */
102   public static Event toRecordingEvent(SearchMetadataCollection metadata, Unmarshaller unmarshaller)
103           throws IOException {
104     Map<String, SearchMetadata<?>> metadataMap = metadata.toMap();
105     String eventJson = (String) metadataMap.get(EventIndexSchema.OBJECT).getValue();
106     return Event.valueOf(IOUtils.toInputStream(eventJson, Charset.defaultCharset()), unmarshaller);
107   }
108 
109   /**
110    * Creates search metadata from a recording event such that the event can be stored in the search index.
111    *
112    * @param event
113    *          the recording event
114    * @return the set of metadata
115    */
116   public static SearchMetadataCollection toSearchMetadata(Event event, Map<String, String> additionalActions) {
117     SearchMetadataCollection metadata = new SearchMetadataCollection(
118             event.getIdentifier().concat(event.getOrganization()), Event.DOCUMENT_TYPE);
119     metadata.addField(EventIndexSchema.UID, event.getIdentifier(), false);
120     metadata.addField(EventIndexSchema.ORGANIZATION, event.getOrganization(), false);
121     metadata.addField(EventIndexSchema.OBJECT, event.toXML(), false);
122     if (StringUtils.isNotBlank(event.getTitle())) {
123       metadata.addField(EventIndexSchema.TITLE, event.getTitle(), true);
124     }
125     if (StringUtils.isNotBlank(event.getDescription())) {
126       metadata.addField(EventIndexSchema.DESCRIPTION, event.getDescription(), true);
127     }
128     if (StringUtils.isNotBlank(event.getLocation())) {
129       metadata.addField(EventIndexSchema.LOCATION, event.getLocation(), true);
130     }
131     if (StringUtils.isNotBlank(event.getSeriesId())) {
132       metadata.addField(EventIndexSchema.SERIES_ID, event.getSeriesId(), false);
133     }
134     if (StringUtils.isNotBlank(event.getSeriesName())) {
135       metadata.addField(EventIndexSchema.SERIES_NAME, event.getSeriesName(), true);
136     }
137     if (StringUtils.isNotBlank(event.getLanguage())) {
138       metadata.addField(EventIndexSchema.LANGUAGE, event.getLanguage(), false);
139     }
140     if (StringUtils.isNotBlank(event.getSubject())) {
141       metadata.addField(EventIndexSchema.SUBJECT, event.getSubject(), true);
142     }
143     if (StringUtils.isNotBlank(event.getSource())) {
144       metadata.addField(EventIndexSchema.SOURCE, event.getSource(), false);
145     }
146     if (StringUtils.isNotBlank(event.getCreated())) {
147       metadata.addField(EventIndexSchema.CREATED, event.getCreated(), false);
148     }
149     if (StringUtils.isNotBlank(event.getCreator())) {
150       metadata.addField(EventIndexSchema.CREATOR, event.getCreator(), true);
151     }
152     if (StringUtils.isNotBlank(event.getPublisher())) {
153       metadata.addField(EventIndexSchema.PUBLISHER, event.getPublisher(), true);
154     }
155     if (StringUtils.isNotBlank(event.getLicense())) {
156       metadata.addField(EventIndexSchema.LICENSE, event.getLicense(), false);
157     }
158     if (StringUtils.isNotBlank(event.getRights())) {
159       metadata.addField(EventIndexSchema.RIGHTS, event.getRights(), true);
160     }
161     if (StringUtils.isNotBlank(event.getManagedAcl())) {
162       metadata.addField(EventIndexSchema.MANAGED_ACL, event.getManagedAcl(), false);
163     }
164     if (StringUtils.isNotBlank(event.getWorkflowState())) {
165       metadata.addField(EventIndexSchema.WORKFLOW_STATE, event.getWorkflowState(), false);
166     }
167     if (event.getWorkflowId() != null) {
168       metadata.addField(EventIndexSchema.WORKFLOW_ID, event.getWorkflowId(), false);
169     }
170     if (StringUtils.isNotBlank(event.getWorkflowDefinitionId())) {
171       metadata.addField(EventIndexSchema.WORKFLOW_DEFINITION_ID, event.getWorkflowDefinitionId(), false);
172     }
173     if (StringUtils.isNotBlank(event.getRecordingStartDate())) {
174       metadata.addField(EventIndexSchema.START_DATE, event.getRecordingStartDate(), false);
175     }
176     if (StringUtils.isNotBlank(event.getRecordingEndDate())) {
177       metadata.addField(EventIndexSchema.END_DATE, event.getRecordingEndDate(), false);
178     }
179     if (event.getDuration() != null) {
180       metadata.addField(EventIndexSchema.DURATION, event.getDuration(), false);
181     }
182     if (event.getArchiveVersion() != null) {
183       metadata.addField(EventIndexSchema.ARCHIVE_VERSION, event.getArchiveVersion(), false);
184     }
185     if (event.getRecordingStatus() != null) {
186       metadata.addField(EventIndexSchema.RECORDING_STATUS, event.getRecordingStatus(), false);
187     }
188 
189     metadata.addField(EventIndexSchema.EVENT_STATUS, event.getEventStatus(), false);
190 
191     metadata.addField(EventIndexSchema.HAS_COMMENTS, event.hasComments(), false);
192     metadata.addField(EventIndexSchema.HAS_OPEN_COMMENTS, event.hasOpenComments(), false);
193 
194     if (event.comments() != null) {
195       List<Comment> comments = event.comments();
196       HashMap<String, Object>[] commentsArray = new HashMap[comments.size()];
197       for (int i = 0; i < comments.size(); i++) {
198         Comment comment = comments.get(i);
199         HashMap<String, Object> myMap = new HashMap<String, Object>() {{
200             put(CommentIndexSchema.ID, comment.getId());
201             put(CommentIndexSchema.REASON, comment.getReason());
202             put(CommentIndexSchema.TEXT, comment.getText());
203             put(CommentIndexSchema.RESOLVED_STATUS, comment.isResolvedStatus());
204           }};
205         commentsArray[i] = myMap;
206 //        generatePublicationDoc(comments.get(i).getType());
207       }
208 
209       metadata.addField(EventIndexSchema.COMMENTS, commentsArray, false);
210     }
211 
212     metadata.addField(EventIndexSchema.NEEDS_CUTTING, event.needsCutting(), false);
213 
214     if (event.getPublications() != null) {
215       List<Publication> publications = event.getPublications();
216       HashMap<String, Object>[] publicationsArray = new HashMap[publications.size()];
217       for (int i = 0; i < publications.size(); i++) {
218         publicationsArray[i] = generatePublicationDoc(publications.get(i));
219       }
220 
221       if (publications.size() == 1 && !publications.get(0).getChannel().equals("internal") || publications.size() > 1) {
222         metadata.addField(EventIndexSchema.IS_PUBLISHED, true, false);
223       } else {
224         metadata.addField(EventIndexSchema.IS_PUBLISHED, false, false);
225       }
226 
227       metadata.addField(EventIndexSchema.PUBLICATION, publicationsArray, false);
228 
229     }
230 
231     if (event.getPresenters() != null) {
232       List<String> presenters = event.getPresenters();
233       metadata.addField(EventIndexSchema.PRESENTER, presenters.toArray(new String[presenters.size()]), true);
234     }
235 
236     if (event.getContributors() != null) {
237       List<String> contributors = event.getContributors();
238       metadata.addField(EventIndexSchema.CONTRIBUTOR, contributors.toArray(new String[contributors.size()]), true);
239     }
240 
241     if (!event.getExtendedMetadata().isEmpty()) {
242       addExtendedMetadata(metadata, event.getExtendedMetadata());
243     }
244 
245     if (StringUtils.isNotBlank(event.getAccessPolicy())) {
246       metadata.addField(EventIndexSchema.ACCESS_POLICY, event.getAccessPolicy(), false);
247       addAuthorization(metadata, event.getAccessPolicy(), event.getIdentifier(), additionalActions);
248     }
249 
250     if (StringUtils.isNotBlank(event.getAgentId())) {
251       metadata.addField(EventIndexSchema.AGENT_ID, event.getAgentId(), false);
252     }
253 
254     if (StringUtils.isNotBlank(event.getTechnicalStartTime())) {
255       metadata.addField(EventIndexSchema.TECHNICAL_START, event.getTechnicalStartTime(), false);
256     }
257 
258     if (StringUtils.isNotBlank(event.getTechnicalEndTime())) {
259       metadata.addField(EventIndexSchema.TECHNICAL_END, event.getTechnicalEndTime(), false);
260     }
261 
262     if (event.getTechnicalPresenters() != null) {
263       metadata.addField(EventIndexSchema.TECHNICAL_PRESENTERS,
264               event.getTechnicalPresenters().toArray(new String[event.getTechnicalPresenters().size()]), false);
265     }
266 
267     return metadata;
268   }
269 
270   private static void addObjectStringToMap(HashMap<String, Object> map, String key, Object value) {
271     if (value == null) {
272       map.put(key, "");
273     } else {
274       map.put(key, value.toString());
275     }
276   }
277 
278   /**
279    * Generate the document structure for the publication element
280    *
281    * @param publication
282    *          the source publication element
283    * @return a map representing the ES document structure of the publication element
284    */
285   private static HashMap<String, Object> generatePublicationDoc(Publication publication) {
286     HashMap<String, Object> pMap = new HashMap<String, Object>();
287 
288     // Add first level elements
289     pMap.put(PublicationIndexSchema.CHANNEL, publication.getChannel());
290     addObjectStringToMap(pMap, PublicationIndexSchema.MIMETYPE, publication.getMimeType());
291 
292     // Attachments
293     Attachment[] attachments = publication.getAttachments();
294     HashMap<String, Object>[] attachmentsArray = new HashMap[attachments.length];
295     for (int i = 0; i < attachmentsArray.length; i++) {
296       Attachment attachment = attachments[i];
297       HashMap<String, Object> element = new HashMap<String, Object>();
298       element.put(PublicationIndexSchema.ELEMENT_ID, attachment.getIdentifier());
299       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, attachment.getMimeType());
300       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, attachment.getElementType());
301       element.put(PublicationIndexSchema.ELEMENT_TAG, attachment.getTags());
302       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, attachment.getURI());
303       element.put(PublicationIndexSchema.ELEMENT_SIZE, attachment.getSize());
304       attachmentsArray[i] = element;
305     }
306     pMap.put(PublicationIndexSchema.ATTACHMENT, attachmentsArray);
307 
308     // Catalogs
309     Catalog[] catalogs = publication.getCatalogs();
310     HashMap<String, Object>[] catalogsArray = new HashMap[catalogs.length];
311     for (int i = 0; i < catalogsArray.length; i++) {
312       Catalog catalog = catalogs[i];
313       HashMap<String, Object> element = new HashMap<String, Object>();
314       element.put(PublicationIndexSchema.ELEMENT_ID, catalog.getIdentifier());
315       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, catalog.getMimeType());
316       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, catalog.getElementType());
317       element.put(PublicationIndexSchema.ELEMENT_TAG, catalog.getTags());
318       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, catalog.getURI());
319       element.put(PublicationIndexSchema.ELEMENT_SIZE, catalog.getSize());
320       catalogsArray[i] = element;
321     }
322     pMap.put(PublicationIndexSchema.CATALOG, catalogsArray);
323 
324     // Tracks
325     Track[] tracks = publication.getTracks();
326     HashMap<String, Object>[] tracksArray = new HashMap[tracks.length];
327     for (int i = 0; i < tracksArray.length; i++) {
328       Track track = tracks[i];
329       HashMap<String, Object> element = new HashMap<String, Object>();
330       element.put(PublicationIndexSchema.ELEMENT_ID, track.getIdentifier());
331       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, track.getMimeType());
332       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, track.getElementType());
333       element.put(PublicationIndexSchema.ELEMENT_TAG, track.getTags());
334       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, track.getURI());
335       element.put(PublicationIndexSchema.ELEMENT_SIZE, track.getSize());
336       element.put(PublicationIndexSchema.TRACK_DURATION, track.getDuration());
337       tracksArray[i] = element;
338     }
339     pMap.put(PublicationIndexSchema.TRACK, tracksArray);
340 
341     return pMap;
342   }
343 
344   /**
345    * Adds extended metadata fields to the input document
346    *
347    * @param doc
348    *          the input document
349    * @param extendedMetadata
350    *          the extended metadata map
351    */
352   private static void addExtendedMetadata(SearchMetadataCollection doc, Map<String, Map<String,
353           List<String>>> extendedMetadata) {
354     for (String type: extendedMetadata.keySet()) {
355       Map<String, List<String>> extendedMetadataByType = extendedMetadata.get(type);
356       for (String name: extendedMetadataByType.keySet()) {
357         List<String> values = extendedMetadataByType.get(name);
358         String fieldName = SeriesIndexSchema.EXTENDED_METADATA_PREFIX.concat(type + "_" + name);
359         doc.addField(fieldName, values, true);
360       }
361     }
362   }
363 
364   /**
365    * Adds authorization fields to the input document.
366    *
367    * @param doc
368    *          the input document
369    * @param aclString
370    *          the access control list string
371    */
372   private static void addAuthorization(SearchMetadataCollection doc, String aclString,
373       String eventId, Map<String, String> additionalActions) {
374     Map<String, List<String>> permissions = new HashMap<>();
375 
376     // Define containers for common permissions
377     for (Action action : Permissions.Action.values()) {
378       permissions.put(action.toString(), new ArrayList<>());
379     }
380 
381     // Add roles from acl
382     AccessControlList acl = AccessControlParser.parseAclSilent(aclString);
383     List<AccessControlEntry> entries = acl.getEntries();
384 
385     // Add special action roles for episode id roles
386     Set<AccessControlEntry> customEntries = new HashSet<>();
387     customEntries.add(
388         new AccessControlEntry(getEpisodeRoleId(eventId, Action.READ.toString()),
389           Action.READ.getValue(),
390           true
391         )
392     );
393     customEntries.add(
394         new AccessControlEntry(getEpisodeRoleId(eventId, Action.WRITE.toString()),
395           Action.WRITE.getValue(),
396           true
397         )
398     );
399 
400     ResourceListQuery query = new DefaultResourceListQuery();
401     for (String action : additionalActions.keySet()) {
402       customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, action), action, true));
403     }
404 
405     entries.addAll(customEntries);
406 
407     // Convert roles to permission blocks
408     for (AccessControlEntry entry : entries) {
409       if (!entry.isAllow()) {
410         logger.info("Event index does not support denial via ACL, ignoring {}", entry);
411         continue;
412       }
413       List<String> actionPermissions = permissions.get(entry.getAction());
414       if (actionPermissions == null) {
415         actionPermissions = new ArrayList<>();
416         permissions.put(entry.getAction(), actionPermissions);
417       }
418       actionPermissions.add(entry.getRole());
419     }
420 
421     // Write the permissions to the input document
422     for (Map.Entry<String, List<String>> entry : permissions.entrySet()) {
423       String fieldName = EventIndexSchema.ACL_PERMISSION_PREFIX.concat(entry.getKey());
424       doc.addField(fieldName, entry.getValue(), false);
425     }
426   }
427 
428   /**
429    * Update extended metadata for event from dublin core catalog.
430    *
431    * @param event
432    *         The event
433    * @param dc
434    *         The dublin core catalog with extended metadata
435    * @param flavor
436    *         The flavor of the extended metadata
437    * @return
438    *         The updated event
439    */
440   public static Event updateEventExtendedMetadata(Event event, DublinCoreCatalog dc, MediaPackageElementFlavor flavor) {
441     Map<String, List<String>> map = new HashMap();
442     Set<EName> eNames = dc.getProperties();
443     for (EName eName: eNames) {
444       String name = eName.getLocalName();
445       List<String> values = dc.get(eName, DublinCore.LANGUAGE_ANY);
446       map.put(name, values);
447     }
448     event.setExtendedMetadata(flavor.toString(), map);
449     return event;
450   }
451 
452   /**
453    * Update the given {@link Event} with the given {@link DublinCore}.
454    *
455    * @param event
456    *          the event to update
457    * @param dc
458    *          the catalog with the metadata for the update
459    * @return the updated event
460    */
461   public static Event updateEvent(Event event, DublinCore dc) {
462     event.setTitle(dc.getFirst(DublinCore.PROPERTY_TITLE));
463     event.setDescription(dc.getFirst(DublinCore.PROPERTY_DESCRIPTION));
464     event.setSubject(dc.getFirst(DublinCore.PROPERTY_SUBJECT));
465     event.setLocation(dc.getFirst(DublinCore.PROPERTY_SPATIAL));
466     event.setLanguage(dc.getFirst(DublinCore.PROPERTY_LANGUAGE));
467     event.setSource(dc.getFirst(DublinCore.PROPERTY_SOURCE));
468     event.setSeriesId(dc.getFirst(DublinCore.PROPERTY_IS_PART_OF));
469     event.setLicense(dc.getFirst(DublinCore.PROPERTY_LICENSE));
470     event.setRights(dc.getFirst(DublinCore.PROPERTY_RIGHTS_HOLDER));
471     event.setPublisher(dc.getFirst(DublinCore.PROPERTY_PUBLISHER));
472     Date created;
473     String encodedDate = dc.getFirst(DublinCore.PROPERTY_CREATED);
474     if (StringUtils.isBlank(encodedDate)) {
475       created = new Date();
476     } else {
477       created = EncodingSchemeUtils.decodeDate(encodedDate);
478     }
479     event.setCreated(DateTimeSupport.toUTC(created.getTime()));
480     String strPeriod = dc.getFirst(DublinCore.PROPERTY_TEMPORAL);
481     try {
482       if (StringUtils.isNotBlank(strPeriod)) {
483         DCMIPeriod period = EncodingSchemeUtils.decodeMandatoryPeriod(strPeriod);
484         event.setRecordingStartDate(DateTimeSupport.toUTC(period.getStart().getTime()));
485         event.setRecordingEndDate(DateTimeSupport.toUTC(period.getEnd().getTime()));
486         event.setDuration(period.getEnd().getTime() - period.getStart().getTime());
487       } else {
488         event.setRecordingStartDate(DateTimeSupport.toUTC(created.getTime()));
489       }
490     } catch (Exception e) {
491       logger.warn("Invalid start and end date/time for event {}: {}", event.getIdentifier(), strPeriod);
492       event.setRecordingStartDate(DateTimeSupport.toUTC(created.getTime()));
493     }
494 
495     updateTechnicalDate(event);
496 
497     // TODO: Add support for language
498     event.setContributors(dc.get(DublinCore.PROPERTY_CONTRIBUTOR, DublinCore.LANGUAGE_ANY));
499     event.setPresenters(dc.get(DublinCore.PROPERTY_CREATOR, DublinCore.LANGUAGE_ANY));
500     return event;
501   }
502 
503   public static Event updateTechnicalDate(Event event) {
504     if (event.isScheduledEvent() && event.hasRecordingStarted()) {
505       // Override technical dates from recording if already started
506       event.setTechnicalStartTime(event.getRecordingStartDate());
507       event.setTechnicalEndTime(event.getRecordingEndDate());
508     } else {
509       // If this is an upload where the start time is not set, set the start time to same as dublin core
510       if (StringUtils.isBlank(event.getTechnicalStartTime())) {
511         event.setTechnicalStartTime(event.getRecordingStartDate());
512       }
513       if (StringUtils.isBlank(event.getTechnicalEndTime())) {
514         event.setTechnicalEndTime(event.getRecordingEndDate());
515       }
516     }
517     return event;
518   }
519 
520   /**
521    * Update the given {@link Event} with the given {@link MediaPackage}.
522    *
523    * @param event
524    *          the event to update
525    * @param mp
526    *          the mediapackage containing the metadata for the update
527    * @return the updated event
528    */
529   public static Event updateEvent(Event event, MediaPackage mp) {
530     event.setPublications(Arrays.asList(mp.getPublications()));
531     event.setSeriesName(mp.getSeriesTitle());
532     return event;
533   }
534 
535   /**
536    * A function to update the series title within an event. Uses the default number of attempts to get the series title
537    * and the default amount of time to sleep between attempts.
538    *
539    * @param event
540    *          The event to update the series name in
541    * @param organization
542    *          The organization for this event and series
543    * @param user
544    *          The user
545    * @param searchIndex
546    *          The index to search for the series
547    */
548   public static void updateSeriesName(Event event, String organization, User user, ElasticsearchIndex searchIndex)
549           throws SearchIndexException {
550     updateSeriesName(event, organization, user, searchIndex, DEFAULT_ATTEMPTS, DEFAULT_SLEEP);
551   }
552 
553   /**
554    * A function to update the series title within an event.
555    *
556    * @param event
557    *          The event to update the series name in
558    * @param organization
559    *          The organization for this event and series
560    * @param user
561    *          The user
562    * @param searchIndex
563    *          The index to search for the series
564    * @param tries
565    *          The number of attempts to try to get the series title
566    * @param sleep
567    *          The amount of time in ms to sleep between attempts to get the series title.
568    */
569   public static void updateSeriesName(Event event, String organization, User user, ElasticsearchIndex searchIndex,
570           int tries, long sleep) throws SearchIndexException {
571     if (event.getSeriesId() != null) {
572       for (int i = 1; i <= tries; i++) {
573         SearchResult<Series> result = searchIndex.getByQuery(
574                 new SeriesSearchQuery(organization, user).withoutActions().withIdentifier(event.getSeriesId()));
575         if (result.getHitCount() > 0) {
576           event.setSeriesName(result.getItems()[0].getSource().getTitle());
577           break;
578         } else {
579           Integer triesLeft = tries - i;
580           logger.debug("Not able to find the series {} in the search index for the event {}. Will try {} more times.",
581                   event.getSeriesId(), event.getIdentifier(), triesLeft);
582           try {
583             Thread.sleep(sleep);
584           } catch (InterruptedException e) {
585             logger.warn("Interrupted while sleeping before checking for the series being added to the index", e);
586           }
587         }
588 
589       }
590     }
591   }
592 
593  /**
594    * Gets all of the MediaPackageElement's flavors.
595    *
596    * @param publications
597    *          The list of publication elements to get the flavors from.
598    * @return An array of {@link String} representation of the MediaPackageElementFlavors
599    */
600   private static String[] getPublicationFlavors(List<Publication> publications) {
601     Set<String> allPublicationFlavors = new TreeSet<String>();
602     for (Publication p : publications) {
603       for (Attachment attachment : p.getAttachments()) {
604         if (attachment.getFlavor() != null) {
605           allPublicationFlavors.add(attachment.getFlavor().toString());
606         }
607       }
608       for (Catalog catalog : p.getCatalogs()) {
609         if (catalog.getFlavor() != null) {
610           allPublicationFlavors.add(catalog.getFlavor().toString());
611         }
612       }
613       for (Track track : p.getTracks()) {
614         if (track.getFlavor() != null) {
615           allPublicationFlavors.add(track.getFlavor().toString());
616         }
617       }
618     }
619     return allPublicationFlavors.toArray(new String[allPublicationFlavors.size()]);
620   }
621 
622   /**
623    * Returns <code>true</code> if the previewSubtype matches any of the publicationFlavors.
624    *
625    * @param publications
626    * @param previewSubtype
627    * @return
628    */
629   public static Boolean subflavorMatches(List<Publication> publications, String previewSubtype) {
630     String[] publicationFlavors = getPublicationFlavors(publications);
631     if (publicationFlavors != null && previewSubtype != null) {
632       final String subtype = "/" + previewSubtype;
633       for (String flavor : publicationFlavors) {
634         if (flavor.endsWith(subtype)) {
635           return true;
636         }
637       }
638     }
639     return false;
640   }
641 
642 }