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