1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
76
77 public final class EventIndexUtils {
78
79 private static final Logger logger = LoggerFactory.getLogger(EventIndexUtils.class);
80
81
82 public static final int DEFAULT_ATTEMPTS = 10;
83
84 public static final long DEFAULT_SLEEP = 100L;
85
86
87
88
89 private EventIndexUtils() {
90 }
91
92
93
94
95
96
97
98
99
100
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
111
112
113
114
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
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
280
281
282
283
284
285 private static HashMap<String, Object> generatePublicationDoc(Publication publication) {
286 HashMap<String, Object> pMap = new HashMap<String, Object>();
287
288
289 pMap.put(PublicationIndexSchema.CHANNEL, publication.getChannel());
290 addObjectStringToMap(pMap, PublicationIndexSchema.MIMETYPE, publication.getMimeType());
291
292
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
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
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
346
347
348
349
350
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
366
367
368
369
370
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
377 for (Action action : Permissions.Action.values()) {
378 permissions.put(action.toString(), new ArrayList<>());
379 }
380
381
382 AccessControlList acl = AccessControlParser.parseAclSilent(aclString);
383 List<AccessControlEntry> entries = acl.getEntries();
384
385
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
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
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
430
431
432
433
434
435
436
437
438
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
454
455
456
457
458
459
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
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
506 event.setTechnicalStartTime(event.getRecordingStartDate());
507 event.setTechnicalEndTime(event.getRecordingEndDate());
508 } else {
509
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
522
523
524
525
526
527
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
537
538
539
540
541
542
543
544
545
546
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
555
556
557
558
559
560
561
562
563
564
565
566
567
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
595
596
597
598
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
624
625
626
627
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 }