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     SearchMetadataCollection metadata = new SearchMetadataCollection(
120             event.getIdentifier().concat(event.getOrganization()), Event.DOCUMENT_TYPE);
121     metadata.addField(EventIndexSchema.UID, event.getIdentifier(), false);
122     metadata.addField(EventIndexSchema.ORGANIZATION, event.getOrganization(), false);
123     metadata.addField(EventIndexSchema.OBJECT, event.toXML(), false);
124     if (StringUtils.isNotBlank(event.getTitle())) {
125       metadata.addField(EventIndexSchema.TITLE, event.getTitle(), true);
126     }
127     if (StringUtils.isNotBlank(event.getDescription())) {
128       metadata.addField(EventIndexSchema.DESCRIPTION, event.getDescription(), true);
129     }
130     if (StringUtils.isNotBlank(event.getLocation())) {
131       metadata.addField(EventIndexSchema.LOCATION, event.getLocation(), true);
132     }
133     if (StringUtils.isNotBlank(event.getSeriesId())) {
134       metadata.addField(EventIndexSchema.SERIES_ID, event.getSeriesId(), false);
135     }
136     if (StringUtils.isNotBlank(event.getSeriesName())) {
137       metadata.addField(EventIndexSchema.SERIES_NAME, event.getSeriesName(), true);
138     }
139     if (StringUtils.isNotBlank(event.getLanguage())) {
140       metadata.addField(EventIndexSchema.LANGUAGE, event.getLanguage(), false);
141     }
142     if (StringUtils.isNotBlank(event.getSubject())) {
143       metadata.addField(EventIndexSchema.SUBJECT, event.getSubject(), true);
144     }
145     if (StringUtils.isNotBlank(event.getSource())) {
146       metadata.addField(EventIndexSchema.SOURCE, event.getSource(), false);
147     }
148     if (StringUtils.isNotBlank(event.getCreated())) {
149       metadata.addField(EventIndexSchema.CREATED, event.getCreated(), false);
150     }
151     if (StringUtils.isNotBlank(event.getCreator())) {
152       metadata.addField(EventIndexSchema.CREATOR, event.getCreator(), true);
153     }
154     if (StringUtils.isNotBlank(event.getPublisher())) {
155       metadata.addField(EventIndexSchema.PUBLISHER, event.getPublisher(), true);
156     }
157     if (StringUtils.isNotBlank(event.getLicense())) {
158       metadata.addField(EventIndexSchema.LICENSE, event.getLicense(), false);
159     }
160     if (StringUtils.isNotBlank(event.getRights())) {
161       metadata.addField(EventIndexSchema.RIGHTS, event.getRights(), true);
162     }
163     if (StringUtils.isNotBlank(event.getManagedAcl())) {
164       metadata.addField(EventIndexSchema.MANAGED_ACL, event.getManagedAcl(), false);
165     }
166     if (StringUtils.isNotBlank(event.getWorkflowState())) {
167       metadata.addField(EventIndexSchema.WORKFLOW_STATE, event.getWorkflowState(), false);
168     }
169     if (event.getWorkflowId() != null) {
170       metadata.addField(EventIndexSchema.WORKFLOW_ID, event.getWorkflowId(), false);
171     }
172     if (StringUtils.isNotBlank(event.getWorkflowDefinitionId())) {
173       metadata.addField(EventIndexSchema.WORKFLOW_DEFINITION_ID, event.getWorkflowDefinitionId(), false);
174     }
175     if (StringUtils.isNotBlank(event.getRecordingStartDate())) {
176       metadata.addField(EventIndexSchema.START_DATE, event.getRecordingStartDate(), false);
177     }
178     if (StringUtils.isNotBlank(event.getRecordingEndDate())) {
179       metadata.addField(EventIndexSchema.END_DATE, event.getRecordingEndDate(), false);
180     }
181     if (event.getDuration() != null) {
182       metadata.addField(EventIndexSchema.DURATION, event.getDuration(), false);
183     }
184     if (event.getArchiveVersion() != null) {
185       metadata.addField(EventIndexSchema.ARCHIVE_VERSION, event.getArchiveVersion(), false);
186     }
187     if (event.getRecordingStatus() != null) {
188       metadata.addField(EventIndexSchema.RECORDING_STATUS, event.getRecordingStatus(), false);
189     }
190 
191     metadata.addField(EventIndexSchema.EVENT_STATUS, event.getEventStatus(), false);
192 
193     metadata.addField(EventIndexSchema.HAS_COMMENTS, event.hasComments(), false);
194     metadata.addField(EventIndexSchema.HAS_OPEN_COMMENTS, event.hasOpenComments(), false);
195 
196     if (event.comments() != null) {
197       List<Comment> comments = event.comments();
198       HashMap<String, Object>[] commentsArray = new HashMap[comments.size()];
199       for (int i = 0; i < comments.size(); i++) {
200         Comment comment = comments.get(i);
201         HashMap<String, Object> myMap = new HashMap<String, Object>() {{
202             put(CommentIndexSchema.ID, comment.getId());
203             put(CommentIndexSchema.REASON, comment.getReason());
204             put(CommentIndexSchema.TEXT, comment.getText());
205             put(CommentIndexSchema.RESOLVED_STATUS, comment.isResolvedStatus());
206           }};
207         commentsArray[i] = myMap;
208 //        generatePublicationDoc(comments.get(i).getType());
209       }
210 
211       metadata.addField(EventIndexSchema.COMMENTS, commentsArray, false);
212     }
213 
214     metadata.addField(EventIndexSchema.NEEDS_CUTTING, event.needsCutting(), false);
215 
216     if (event.getPublications() != null) {
217       List<Publication> publications = event.getPublications();
218       HashMap<String, Object>[] publicationsArray = new HashMap[publications.size()];
219       for (int i = 0; i < publications.size(); i++) {
220         publicationsArray[i] = generatePublicationDoc(publications.get(i));
221       }
222 
223       if (publications.size() == 1 && !publications.get(0).getChannel().equals("internal") || publications.size() > 1) {
224         metadata.addField(EventIndexSchema.IS_PUBLISHED, true, false);
225       } else {
226         metadata.addField(EventIndexSchema.IS_PUBLISHED, false, false);
227       }
228 
229       metadata.addField(EventIndexSchema.PUBLICATION, publicationsArray, false);
230 
231     }
232 
233     if (event.getPresenters() != null) {
234       List<String> presenters = event.getPresenters();
235       metadata.addField(EventIndexSchema.PRESENTER, presenters.toArray(new String[presenters.size()]), true);
236     }
237 
238     if (event.getContributors() != null) {
239       List<String> contributors = event.getContributors();
240       metadata.addField(EventIndexSchema.CONTRIBUTOR, contributors.toArray(new String[contributors.size()]), true);
241     }
242 
243     if (!event.getExtendedMetadata().isEmpty()) {
244       addExtendedMetadata(metadata, event.getExtendedMetadata());
245     }
246 
247     if (StringUtils.isNotBlank(event.getAccessPolicy())) {
248       metadata.addField(EventIndexSchema.ACCESS_POLICY, event.getAccessPolicy(), false);
249       addAuthorization(metadata, event.getAccessPolicy(), event.getIdentifier(), listProviderService);
250     }
251 
252     if (StringUtils.isNotBlank(event.getAgentId())) {
253       metadata.addField(EventIndexSchema.AGENT_ID, event.getAgentId(), false);
254     }
255 
256     if (StringUtils.isNotBlank(event.getTechnicalStartTime())) {
257       metadata.addField(EventIndexSchema.TECHNICAL_START, event.getTechnicalStartTime(), false);
258     }
259 
260     if (StringUtils.isNotBlank(event.getTechnicalEndTime())) {
261       metadata.addField(EventIndexSchema.TECHNICAL_END, event.getTechnicalEndTime(), false);
262     }
263 
264     if (event.getTechnicalPresenters() != null) {
265       metadata.addField(EventIndexSchema.TECHNICAL_PRESENTERS,
266               event.getTechnicalPresenters().toArray(new String[event.getTechnicalPresenters().size()]), false);
267     }
268 
269     return metadata;
270   }
271 
272   private static void addObjectStringToMap(HashMap<String, Object> map, String key, Object value) {
273     if (value == null) {
274       map.put(key, "");
275     } else {
276       map.put(key, value.toString());
277     }
278   }
279 
280   /**
281    * Generate the document structure for the publication element
282    *
283    * @param publication
284    *          the source publication element
285    * @return a map representing the ES document structure of the publication element
286    */
287   private static HashMap<String, Object> generatePublicationDoc(Publication publication) {
288     HashMap<String, Object> pMap = new HashMap<String, Object>();
289 
290     // Add first level elements
291     pMap.put(PublicationIndexSchema.CHANNEL, publication.getChannel());
292     addObjectStringToMap(pMap, PublicationIndexSchema.MIMETYPE, publication.getMimeType());
293 
294     // Attachments
295     Attachment[] attachments = publication.getAttachments();
296     HashMap<String, Object>[] attachmentsArray = new HashMap[attachments.length];
297     for (int i = 0; i < attachmentsArray.length; i++) {
298       Attachment attachment = attachments[i];
299       HashMap<String, Object> element = new HashMap<String, Object>();
300       element.put(PublicationIndexSchema.ELEMENT_ID, attachment.getIdentifier());
301       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, attachment.getMimeType());
302       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, attachment.getElementType());
303       element.put(PublicationIndexSchema.ELEMENT_TAG, attachment.getTags());
304       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, attachment.getURI());
305       element.put(PublicationIndexSchema.ELEMENT_SIZE, attachment.getSize());
306       attachmentsArray[i] = element;
307     }
308     pMap.put(PublicationIndexSchema.ATTACHMENT, attachmentsArray);
309 
310     // Catalogs
311     Catalog[] catalogs = publication.getCatalogs();
312     HashMap<String, Object>[] catalogsArray = new HashMap[catalogs.length];
313     for (int i = 0; i < catalogsArray.length; i++) {
314       Catalog catalog = catalogs[i];
315       HashMap<String, Object> element = new HashMap<String, Object>();
316       element.put(PublicationIndexSchema.ELEMENT_ID, catalog.getIdentifier());
317       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, catalog.getMimeType());
318       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, catalog.getElementType());
319       element.put(PublicationIndexSchema.ELEMENT_TAG, catalog.getTags());
320       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, catalog.getURI());
321       element.put(PublicationIndexSchema.ELEMENT_SIZE, catalog.getSize());
322       catalogsArray[i] = element;
323     }
324     pMap.put(PublicationIndexSchema.CATALOG, catalogsArray);
325 
326     // Tracks
327     Track[] tracks = publication.getTracks();
328     HashMap<String, Object>[] tracksArray = new HashMap[tracks.length];
329     for (int i = 0; i < tracksArray.length; i++) {
330       Track track = tracks[i];
331       HashMap<String, Object> element = new HashMap<String, Object>();
332       element.put(PublicationIndexSchema.ELEMENT_ID, track.getIdentifier());
333       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_MIMETYPE, track.getMimeType());
334       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_TYPE, track.getElementType());
335       element.put(PublicationIndexSchema.ELEMENT_TAG, track.getTags());
336       addObjectStringToMap(element, PublicationIndexSchema.ELEMENT_URL, track.getURI());
337       element.put(PublicationIndexSchema.ELEMENT_SIZE, track.getSize());
338       element.put(PublicationIndexSchema.TRACK_DURATION, track.getDuration());
339       tracksArray[i] = element;
340     }
341     pMap.put(PublicationIndexSchema.TRACK, tracksArray);
342 
343     return pMap;
344   }
345 
346   /**
347    * Adds extended metadata fields to the input document
348    *
349    * @param doc
350    *          the input document
351    * @param extendedMetadata
352    *          the extended metadata map
353    */
354   private static void addExtendedMetadata(SearchMetadataCollection doc, Map<String, Map<String,
355           List<String>>> extendedMetadata) {
356     for (String type: extendedMetadata.keySet()) {
357       Map<String, List<String>> extendedMetadataByType = extendedMetadata.get(type);
358       for (String name: extendedMetadataByType.keySet()) {
359         List<String> values = extendedMetadataByType.get(name);
360         String fieldName = SeriesIndexSchema.EXTENDED_METADATA_PREFIX.concat(type + "_" + name);
361         doc.addField(fieldName, values, true);
362       }
363     }
364   }
365 
366   /**
367    * Adds authorization fields to the input document.
368    *
369    * @param doc
370    *          the input document
371    * @param aclString
372    *          the access control list string
373    */
374   private static void addAuthorization(SearchMetadataCollection doc, String aclString,
375       String eventId, ListProvidersService listProvidersService) {
376     Map<String, List<String>> permissions = new HashMap<>();
377 
378     // Define containers for common permissions
379     for (Action action : Permissions.Action.values()) {
380       permissions.put(action.toString(), new ArrayList<>());
381     }
382 
383     // Add roles from acl
384     AccessControlList acl = AccessControlParser.parseAclSilent(aclString);
385     List<AccessControlEntry> entries = acl.getEntries();
386 
387     // Add special action roles for episode id roles
388     Set<AccessControlEntry> customEntries = new HashSet<>();
389     customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, "READ"), "read", true));
390     customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, "WRITE"), "write", true));
391 
392     ResourceListQuery query = new ResourceListQueryImpl();
393     if (listProvidersService.hasProvider("ACL.ACTIONS")) {
394       Map<String, String> actions = new HashMap<>();
395       try {
396         actions = listProvidersService.getList("ACL.ACTIONS", query, true);
397       } catch (ListProviderException e) {
398         logger.error("Listproviders not loaded. " + e);
399       }
400       for (String action : actions.keySet()) {
401         customEntries.add(new AccessControlEntry(getEpisodeRoleId(eventId, action), action, true));
402       }
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 }