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.statistics.export.impl;
23  
24  import static org.opencastproject.util.data.functions.Misc.chuck;
25  
26  import org.opencastproject.assetmanager.api.AssetManager;
27  import org.opencastproject.elasticsearch.api.SearchIndexException;
28  import org.opencastproject.elasticsearch.api.SearchQuery;
29  import org.opencastproject.elasticsearch.api.SearchResult;
30  import org.opencastproject.elasticsearch.api.SearchResultItem;
31  import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
32  import org.opencastproject.elasticsearch.index.objects.event.Event;
33  import org.opencastproject.elasticsearch.index.objects.event.EventSearchQuery;
34  import org.opencastproject.elasticsearch.index.objects.series.Series;
35  import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
36  import org.opencastproject.index.service.api.IndexService;
37  import org.opencastproject.mediapackage.MediaPackage;
38  import org.opencastproject.metadata.dublincore.DublinCoreMetadataCollection;
39  import org.opencastproject.metadata.dublincore.MetadataField;
40  import org.opencastproject.security.api.Organization;
41  import org.opencastproject.security.api.SecurityService;
42  import org.opencastproject.security.api.UnauthorizedException;
43  import org.opencastproject.statistics.api.DataResolution;
44  import org.opencastproject.statistics.api.StatisticsProvider;
45  import org.opencastproject.statistics.api.StatisticsService;
46  import org.opencastproject.statistics.api.TimeSeries;
47  import org.opencastproject.statistics.api.TimeSeriesProvider;
48  import org.opencastproject.statistics.export.api.DetailLevel;
49  import org.opencastproject.statistics.export.api.StatisticsExportService;
50  import org.opencastproject.util.ConfigurationException;
51  import org.opencastproject.util.NotFoundException;
52  
53  import org.apache.commons.csv.CSVFormat;
54  import org.apache.commons.csv.CSVPrinter;
55  import org.osgi.service.cm.ManagedService;
56  import org.osgi.service.component.ComponentContext;
57  import org.osgi.service.component.annotations.Activate;
58  import org.osgi.service.component.annotations.Component;
59  import org.osgi.service.component.annotations.Deactivate;
60  import org.osgi.service.component.annotations.Reference;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  import java.io.IOException;
65  import java.io.StringWriter;
66  import java.time.Instant;
67  import java.time.LocalDateTime;
68  import java.time.ZoneId;
69  import java.time.format.DateTimeFormatter;
70  import java.util.ArrayList;
71  import java.util.Arrays;
72  import java.util.Collection;
73  import java.util.Collections;
74  import java.util.Dictionary;
75  import java.util.HashMap;
76  import java.util.List;
77  import java.util.Map;
78  import java.util.Optional;
79  import java.util.stream.Collectors;
80  import java.util.stream.Stream;
81  
82  @Component(
83      immediate = true,
84      service = { ManagedService.class,StatisticsExportService.class },
85      property = {
86          "service.description=Statistics Export Service"
87      }
88  )
89  public class StatisticsExportServiceImpl implements StatisticsExportService, ManagedService {
90  
91    /** Logging utility */
92    private static final Logger logger = LoggerFactory.getLogger(StatisticsExportServiceImpl.class);
93    private static final String[] header = {"ID", "Name", "Date", "Value"};
94    private static final String CFG_KEY_SERIES_TO_EVENT_PROVIDER_MAPPINGS = "series.to.event.provider.mappings";
95    private static final String CFG_KEY_ORGANIZATION_TO_EVENT_PROVIDER_MAPPINGS
96        = "organization.to.event.provider.mappings";
97    private static final String CFG_KEY_ORGANIZATION_TO_SERIES_PROVIDER_MAPPINGS
98        = "organization.to.series.provider.mappings";
99  
100 
101   private Map<String, String> seriesToEventProviderMapping = new HashMap<>();
102   private Map<String, String> organizationToEventProviderMapping = new HashMap<>();
103   private Map<String, String> organizationToSeriesProviderMapping = new HashMap<>();
104 
105   private IndexService indexService;
106   private SecurityService securityService;
107   private StatisticsService statisticsService;
108   private AssetManager assetManager;
109 
110   @Override
111   public void updated(Dictionary<String, ?> dictionary) {
112     final String seriesToEventProviderMappings = (String) dictionary.get(CFG_KEY_SERIES_TO_EVENT_PROVIDER_MAPPINGS);
113     if (seriesToEventProviderMappings != null) {
114       this.seriesToEventProviderMapping = getMapping(seriesToEventProviderMappings);
115     }
116     final String organizationToEventProviderMappings
117         = (String) dictionary.get(CFG_KEY_ORGANIZATION_TO_EVENT_PROVIDER_MAPPINGS);
118     if (organizationToEventProviderMappings != null) {
119       this.organizationToEventProviderMapping = getMapping(organizationToEventProviderMappings);
120     }
121     final String organizationToSeriesProviderMappings
122         = (String) dictionary.get(CFG_KEY_ORGANIZATION_TO_SERIES_PROVIDER_MAPPINGS);
123     if (organizationToSeriesProviderMappings != null) {
124       this.organizationToSeriesProviderMapping = getMapping(organizationToSeriesProviderMappings);
125     }
126   }
127 
128   private Map<String, String> getMapping(String seriesProviderMappings) {
129     return Arrays.stream(seriesProviderMappings.split(","))
130         .peek(s -> {
131           if (!s.contains(":")) {
132             throw new ConfigurationException("Missing ':' in mapping between providers: " + s);
133           }
134         })
135         .collect(Collectors.toMap(
136             s -> s.split(":")[0], s -> s.split(":")[1]
137         ));
138   }
139 
140   @Activate
141   public void activate(ComponentContext cc) {
142     logger.info("Activating Statistics Service");
143   }
144 
145   @Deactivate
146   public void deactivate(ComponentContext cc) {
147     logger.info("Deactivating Statistics Service");
148   }
149 
150   @Reference
151   public void setIndexService(IndexService indexService) {
152     this.indexService = indexService;
153   }
154 
155   @Reference
156   public void setStatisticsService(StatisticsService statisticsService) {
157     this.statisticsService = statisticsService;
158   }
159 
160   @Reference
161   public void setSecurityService(SecurityService securityService) {
162     this.securityService = securityService;
163   }
164 
165   @Reference
166   public void setAssetManager(final AssetManager assetManager) {
167     this.assetManager = assetManager;
168   }
169 
170   private static String formatDate(final String dateStr, DataResolution dataResolution, ZoneId zoneId) {
171     final LocalDateTime ldt = LocalDateTime.ofInstant(Instant.parse(dateStr), zoneId);
172     DateTimeFormatter formatter = null;
173     switch (dataResolution) {
174       case HOURLY:
175         formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:00");
176         return formatter.format(ldt);
177       case DAILY:
178         formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
179         return formatter.format(ldt);
180       case WEEKLY:
181         formatter = DateTimeFormatter.ofPattern("uuuu-ww");
182         return formatter.format(ldt);
183       case MONTHLY:
184         formatter = DateTimeFormatter.ofPattern("uuuu-MM");
185         return formatter.format(ldt);
186       case YEARLY:
187         formatter = DateTimeFormatter.ofPattern("uuuu");
188         return formatter.format(ldt);
189       default:
190         throw new IllegalStateException("Unexpected value: " + dataResolution);
191     }
192   }
193 
194 
195   @Override
196   public String getCSV(
197       StatisticsProvider provider,
198       String resourceId,
199       Instant from,
200       Instant to,
201       DataResolution dataResolution,
202       ElasticsearchIndex index,
203       ZoneId zoneId
204   ) throws SearchIndexException, UnauthorizedException, NotFoundException {
205     if (!(provider instanceof TimeSeriesProvider)) {
206       throw new IllegalStateException("CSV export not supported for provider of type " + provider.getClass().getName());
207     }
208     final StringWriter stringWriter = new StringWriter();
209     try (CSVPrinter printer = CSVFormat.RFC4180.print(stringWriter)) {
210       switch (provider.getResourceType()) {
211         case EPISODE:
212           printEvent(provider, resourceId, from, to, dataResolution, index, zoneId, printer, false, 0, 0);
213           break;
214         case SERIES:
215           if (seriesToEventProviderMapping.containsKey(provider.getId())) {
216             // Advanced: instead of exporting the series data we export the data of all series events
217             printSeriesEvents(provider, resourceId, from, to, dataResolution, index, zoneId, printer, false,
218                     0, 0, Collections.emptyMap());
219           } else {
220             // Default: just export series data
221             printSeries(provider, resourceId, from, to, dataResolution, index, zoneId, printer, false, 0, 0);
222           }
223           break;
224         case ORGANIZATION:
225           if (organizationToEventProviderMapping.containsKey(provider.getId())) {
226             // Advanced: instead of exporting the organization data we export the data of all organization events
227             printOrganizationEvents(provider, resourceId, from, to, dataResolution, index, zoneId, printer, false,
228                     0, 0, Collections.emptyMap());
229           } else if (organizationToSeriesProviderMapping.containsKey(provider.getId())) {
230             // Advanced: instead of exporting the organization data we export the data of all organization series
231             printOrganizationSeries(provider, resourceId, from, to, dataResolution, index, zoneId, printer, false,
232                     0, 0, Collections.emptyMap());
233           } else {
234             printOrganization(provider, resourceId, from, to, dataResolution, zoneId, printer, 0, 0);
235           }
236           break;
237         default:
238           throw new IllegalStateException("Unknown resource type: " + provider.getResourceType().name());
239       }
240     } catch (IOException e) {
241       return chuck(e);
242     }
243     return stringWriter.toString();
244   }
245 
246   @Override
247   public String getCSV(StatisticsProvider provider, String resourceId, Instant from, Instant to, DataResolution
248           dataResolution, ElasticsearchIndex index, ZoneId zoneId, boolean fullMetadata, DetailLevel detailLevel,
249           int limit, int offset, Map<String, String> filters)
250           throws SearchIndexException, UnauthorizedException, NotFoundException {
251     if (!(provider instanceof TimeSeriesProvider)) {
252       throw new IllegalStateException("CSV export not supported for provider of type " + provider.getClass().getName());
253     }
254     final StringWriter stringWriter = new StringWriter();
255     try (CSVPrinter printer = CSVFormat.RFC4180.print(stringWriter)) {
256       switch (provider.getResourceType()) {
257         case EPISODE:
258           printEvent(provider, resourceId, from, to,
259               dataResolution, index, zoneId, printer, fullMetadata, limit, offset);
260           break;
261         case SERIES:
262           if (detailLevel == DetailLevel.EPISODE) {
263             // Advanced: instead of exporting the series data we export the data of all series events
264             printSeriesEvents(provider, resourceId, from, to, dataResolution, index, zoneId, printer, fullMetadata,
265                     limit, offset, filters);
266           } else {
267             // Default: just export series data
268             printSeries(provider, resourceId, from, to, dataResolution,
269                 index, zoneId, printer, fullMetadata, limit, offset);
270           }
271           break;
272         case ORGANIZATION:
273           if (detailLevel == DetailLevel.EPISODE) {
274             // Advanced: instead of exporting the organization data we export
275             // the data of all organization events
276             printOrganizationEvents(provider, resourceId, from, to,
277                 dataResolution, index, zoneId, printer, fullMetadata,
278                     limit, offset, filters);
279           } else if (detailLevel == DetailLevel.SERIES) {
280             // Advanced: instead of exporting the organization data we export
281             // the data of all organization series
282             printOrganizationSeries(provider, resourceId, from, to,
283                 dataResolution, index, zoneId, printer, fullMetadata,
284                     limit, offset, filters);
285           } else {
286             printOrganization(provider, resourceId, from, to, dataResolution, zoneId, printer, limit, offset);
287           }
288           break;
289         default:
290           throw new IllegalStateException("Unknown resource type: " + provider.getResourceType().name());
291       }
292     } catch (IOException e) {
293       return chuck(e);
294     }
295     return stringWriter.toString();
296   }
297 
298 
299   private void printEvent(
300       StatisticsProvider provider,
301       String resourceId,
302       Instant from,
303       Instant to,
304       DataResolution dataResolution,
305       ElasticsearchIndex index,
306       ZoneId zoneId,
307       CSVPrinter printer,
308       boolean fullMetaData,
309       int limit,
310       int offset
311   ) throws IOException, SearchIndexException, NotFoundException {
312     if (offset != 0) {
313       return;
314     }
315     final Optional<Event> event = indexService.getEvent(resourceId, index);
316     if (!event.isPresent()) {
317       throw new NotFoundException("Event not found in index: " + resourceId);
318     }
319     final TimeSeries dataEvent = statisticsService.getTimeSeriesData(
320         provider, resourceId, from, to, dataResolution, zoneId);
321     if (fullMetaData) {
322       this.printFullEventData(printer, dataEvent, dataResolution, resourceId, zoneId, true);
323     } else {
324       printData(printer, dataEvent, dataResolution, resourceId, event.get().getTitle(), zoneId, true);
325     }
326   }
327 
328   private void printSeries(StatisticsProvider provider, String resourceId, Instant from, Instant to,
329                            DataResolution dataResolution, ElasticsearchIndex index, ZoneId zoneId, CSVPrinter printer,
330                            boolean fullMetadata, int limit, int offset)
331           throws SearchIndexException, NotFoundException, IOException {
332     if (offset != 0) {
333       return;
334     }
335     final Optional<Series> series = index.getSeries(
336             resourceId, securityService.getOrganization().getId(), securityService.getUser());
337     if (!series.isPresent()) {
338       throw new NotFoundException("Series not found in index: " + resourceId);
339     }
340     final TimeSeries dataSeries = statisticsService.getTimeSeriesData(
341         provider, resourceId, from, to, dataResolution, zoneId);
342     if (fullMetadata) {
343       this.printFullSeriesData(printer, dataSeries, dataResolution, resourceId, zoneId, true);
344     } else {
345       printData(printer, dataSeries, dataResolution, resourceId, series.get().getTitle(), zoneId, true);
346     }
347   }
348 
349   private void printSeriesEvents(
350       StatisticsProvider provider,
351       String resourceId,
352       Instant from,
353       Instant to,
354       DataResolution dataResolution,
355       ElasticsearchIndex index,
356       ZoneId zoneId,
357       CSVPrinter printer,
358       boolean fullMetadata,
359       int limit,
360       int offset,
361       Map<String, String> filters
362   ) throws SearchIndexException, IOException {
363     final String eventProviderId = seriesToEventProviderMapping.get(provider.getId());
364     final StatisticsProvider eventProvider = statisticsService.getProvider(eventProviderId)
365         .orElseThrow(() -> new IllegalStateException(
366             "The configured provider " + eventProviderId + " is not available."));
367     EventSearchQuery query = (EventSearchQuery) new EventSearchQuery(securityService.getOrganization().getId(),
368             securityService.getUser()).withSeriesId(resourceId).withLimit(limit).withOffset(offset);
369     for (Map.Entry<String, String> filter: filters.entrySet()) {
370       query = (EventSearchQuery) applyFilter(filter.getKey(), filter.getValue(), query);
371     }
372 
373     final SearchResult<Event> result = index.getByQuery(query);
374     boolean first = offset == 0;
375     for (SearchResultItem<Event> currentEvent : result.getItems()) {
376       final TimeSeries dataEvent = statisticsService.getTimeSeriesData(eventProvider,
377           currentEvent.getSource().getIdentifier(), from, to, dataResolution, zoneId);
378       if (fullMetadata) {
379         this.printFullEventData(printer, dataEvent, dataResolution,
380             currentEvent.getSource().getIdentifier(), zoneId, first);
381       } else {
382         printData(printer, dataEvent, dataResolution, currentEvent.getSource().getIdentifier(),
383                 currentEvent.getSource().getTitle(), zoneId, first);
384       }
385       first = false;
386     }
387   }
388 
389   private void printOrganization(
390       StatisticsProvider provider,
391       String resourceId,
392       Instant from,
393       Instant to,
394       DataResolution dataResolution,
395       ZoneId zoneId,
396       CSVPrinter printer,
397       int limit,
398       int offset
399   ) throws UnauthorizedException, IOException {
400     if (offset != 0) {
401       return;
402     }
403     final Organization organization = securityService.getOrganization();
404     if (!resourceId.equals(organization.getId())) {
405       throw new UnauthorizedException("Can only export CSV statistics for own organization.");
406     }
407     final TimeSeries dataOrg = statisticsService.getTimeSeriesData(
408         provider, resourceId, from, to, dataResolution, zoneId);
409     printData(printer, dataOrg, dataResolution, resourceId, organization.getName(), zoneId, true);
410   }
411 
412   private void printOrganizationEvents(
413       StatisticsProvider provider,
414       String resourceId,
415       Instant from,
416       Instant to,
417       DataResolution dataResolution,
418       ElasticsearchIndex index,
419       ZoneId zoneId,
420       CSVPrinter printer,
421       boolean fullMetadata,
422       int limit,
423       int offset,
424       Map<String, String> filters
425   ) throws UnauthorizedException, SearchIndexException, IOException {
426 
427     final Organization organization = securityService.getOrganization();
428     if (!resourceId.equals(organization.getId())) {
429       throw new UnauthorizedException("Can only export CSV statistics for own organization.");
430     }
431 
432     final String eventProviderId = organizationToEventProviderMapping.get(provider.getId());
433     final StatisticsProvider eventProvider = statisticsService.getProvider(eventProviderId)
434         .orElseThrow(() -> new IllegalStateException(
435             "The configured provider " + eventProviderId + " is not available."));
436     EventSearchQuery query = (EventSearchQuery) new EventSearchQuery(securityService.getOrganization().getId(),
437             securityService.getUser()).withLimit(limit).withOffset(offset);
438     for (Map.Entry<String, String> filter: filters.entrySet()) {
439       query = (EventSearchQuery) applyFilter(filter.getKey(), filter.getValue(), query);
440     }
441     final SearchResult<Event> result = index.getByQuery(query);
442     boolean first = offset == 0;
443     for (SearchResultItem<Event> currentEvent : result.getItems()) {
444       final TimeSeries dataEvent = statisticsService.getTimeSeriesData(eventProvider,
445               currentEvent.getSource().getIdentifier(), from, to, dataResolution, zoneId);
446       if (fullMetadata) {
447         this.printFullEventData(printer, dataEvent, dataResolution,
448             currentEvent.getSource().getIdentifier(), zoneId, first);
449       } else {
450         printData(printer, dataEvent, dataResolution, currentEvent.getSource().getIdentifier(),
451                 currentEvent.getSource().getTitle(), zoneId, first);
452       }
453       first = false;
454     }
455   }
456 
457 
458   private void printOrganizationSeries(
459       StatisticsProvider provider,
460       String resourceId,
461       Instant from,
462       Instant to,
463       DataResolution dataResolution,
464       ElasticsearchIndex index,
465       ZoneId zoneId,
466       CSVPrinter printer,
467       boolean fullMetadata,
468       int limit,
469       int offset,
470       Map<String, String> filters
471   ) throws UnauthorizedException, SearchIndexException, IOException {
472 
473     final Organization organization = securityService.getOrganization();
474     if (!resourceId.equals(organization.getId())) {
475       throw new UnauthorizedException("Can only export CSV statistics for own organization.");
476     }
477 
478     final String seriesProviderId = organizationToSeriesProviderMapping.get(provider.getId());
479     final StatisticsProvider seriesProvider = statisticsService.getProvider(seriesProviderId)
480         .orElseThrow(() -> new IllegalStateException(
481             "The configured provider " + seriesProviderId + " is not available."));
482 
483     SeriesSearchQuery query = (SeriesSearchQuery) new SeriesSearchQuery(securityService.getOrganization().getId(),
484             securityService.getUser()).withLimit(limit).withOffset(offset);
485     for (Map.Entry<String, String> filter: filters.entrySet()) {
486       query = (SeriesSearchQuery) applyFilter(filter.getKey(), filter.getValue(), query);
487     }
488     final SearchResult<Series> result = index.getByQuery(query);
489     boolean first = offset == 0;
490     for (SearchResultItem<Series> currentSeries : result.getItems()) {
491       final TimeSeries dataEvent = statisticsService.getTimeSeriesData(seriesProvider,
492               currentSeries.getSource().getIdentifier(), from, to, dataResolution, zoneId);
493       if (fullMetadata) {
494         this.printFullSeriesData(printer, dataEvent, dataResolution,
495             currentSeries.getSource().getIdentifier(), zoneId, first);
496       } else {
497         printData(printer, dataEvent, dataResolution, currentSeries.getSource().getIdentifier(),
498                 currentSeries.getSource().getTitle(), zoneId, first);
499       }
500       first = false;
501     }
502   }
503 
504 
505   private static void printData(
506       CSVPrinter printer,
507       TimeSeries data,
508       DataResolution dataResolution,
509       String resourceId,
510       String title,
511       ZoneId zoneId,
512       boolean printHeader) throws IOException {
513     if (printHeader) {
514       printer.printRecord(header);
515     }
516     for (int i = 0; i < data.getLabels().size(); i++) {
517       printer.printRecord(
518           resourceId,
519           title,
520           formatDate(data.getLabels().get(i), dataResolution, zoneId),
521           data.getValues().get(i)
522       );
523     }
524   }
525 
526   private static void printFullData(
527           CSVPrinter printer,
528           TimeSeries data,
529           DataResolution dataResolution,
530           String resourceId,
531           ZoneId zoneId,
532           List<MetadataField> mdfs) throws IOException {
533     for (int i = 0; i < data.getLabels().size(); i++) {
534       List<Object> values = new ArrayList<>();
535       values.add(resourceId);
536       values.addAll(mdfs.stream().map(f -> f.getValue() == null ? "" : f.getValue()).collect(Collectors.toList()));
537       values.add(formatDate(data.getLabels().get(i), dataResolution, zoneId));
538       values.add(data.getValues().get(i));
539       printer.printRecord(values.toArray());
540     }
541   }
542 
543   private void printFullEventData(
544           CSVPrinter printer,
545           TimeSeries data,
546           DataResolution dataResolution,
547           String resourceId,
548           ZoneId zoneId,
549           boolean printHeader) throws IOException {
550     final List<MetadataField> mdfs = getEventMetadata(resourceId);
551     if (printHeader) {
552       printer.printRecord(getFullHeader(mdfs));
553     }
554     printFullData(printer, data, dataResolution, resourceId, zoneId, mdfs);
555   }
556 
557   private void printFullSeriesData(
558           CSVPrinter printer,
559           TimeSeries data,
560           DataResolution dataResolution,
561           String resourceId,
562           ZoneId zoneId,
563           boolean printHeader) throws IOException {
564     final List<MetadataField> mdfs = getSeriesMetadata(resourceId);
565     if (printHeader) {
566       printer.printRecord(getFullHeader(mdfs));
567     }
568     printFullData(printer, data, dataResolution, resourceId, zoneId, mdfs);
569   }
570 
571   private static List<String> getFullHeader(List<MetadataField> mdfs) {
572     final List<String> header = new ArrayList<>();
573     header.add("ID");
574     header.addAll(mdfs.stream().map(MetadataField::getInputID).collect(Collectors.toList()));
575     header.add("Date");
576     header.add("Value");
577     return header;
578   }
579 
580   private List<MetadataField> getSeriesMetadata(String resourceId) {
581     List<DublinCoreMetadataCollection> mdcs = this.indexService.getSeriesCatalogUIAdapters()
582         .stream()
583         .filter(a -> !a.equals(this.indexService.getCommonSeriesCatalogUIAdapter()))
584         .filter(a -> !a.getFlavor().equals(this.indexService.getCommonSeriesCatalogUIAdapter().getFlavor()))
585         .map(adapter -> adapter.getFields(resourceId))
586         .flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty))
587         .collect(Collectors.toList());
588     if (this.indexService.getCommonSeriesCatalogUIAdapter().getFields(resourceId).isPresent()) {
589       mdcs.add(0, this.indexService.getCommonSeriesCatalogUIAdapter().getFields(resourceId).get());
590     }
591     return mdcs.stream()
592             .map(DublinCoreMetadataCollection::getFields)
593             .flatMap(Collection::stream)
594             .collect(Collectors.toList());
595   }
596 
597   private List<MetadataField> getEventMetadata(String resourceId) {
598     final Optional<MediaPackage> optMp = this.assetManager.getMediaPackage(resourceId);
599     if (optMp.isEmpty()) {
600       return Collections.emptyList();
601     }
602     final List<DublinCoreMetadataCollection> mdcs = this.indexService.getEventCatalogUIAdapters()
603             .stream()
604             .filter(a -> !a.equals(this.indexService.getCommonEventCatalogUIAdapter()))
605             .filter(a -> !a.getFlavor().equals(this.indexService.getCommonEventCatalogUIAdapter().getFlavor()))
606             .map(adapter -> adapter.getFields(optMp.get()))
607             .collect(Collectors.toList());
608     mdcs.add(0, this.indexService.getCommonEventCatalogUIAdapter().getFields(optMp.get()));
609     return mdcs.stream()
610             .map(DublinCoreMetadataCollection::getFields)
611             .flatMap(Collection::stream)
612             .collect(Collectors.toList());
613   }
614 
615   private static SearchQuery applyFilter(final String name, final String value, final EventSearchQuery query) {
616     if ("presenters".equals(name)) {
617       return query.withPresenter(value);
618     } else if ("creator".equals(name)) {
619       return query.withCreator(value);
620     } else if ("contributors".equals(name)) {
621       return query.withContributor(value);
622     } else if ("location".equals(name)) {
623       return query.withLocation(value);
624     } else if ("textFilter".equals(name)) {
625       return query.withText("*" + value + "*");
626     } else if ("series".equals(name)) {
627       return query.withSeriesId(value);
628     } else if ("subject".equals(name)) {
629       return query.withSubject(value);
630     } else if ("title".equals(name)) {
631       return query.withTitle(value);
632     } else if ("description".equals(name)) {
633       return query.withDescription(value);
634     } else if ("series_name".equals(name)) {
635       return query.withSeriesName(value);
636     } else if ("language".equals(name)) {
637       return query.withLanguage(value);
638     } else if ("created".equals(name)) {
639       return query.withCreated(value);
640     } else if ("license".equals(name)) {
641       return query.withLicense(value);
642     } else if ("rightsholder".equals(name)) {
643       return query.withRights(value);
644     } else if ("is_part_of".equals(name)) {
645       return query.withSeriesId(value);
646     } else if ("source".equals(name)) {
647       return query.withSource(value);
648     } else if ("status".equals(name)) {
649       return query.withEventStatus(value);
650     } else if ("agent_id".equals(name)) {
651       return query.withAgentId(value);
652     } else if ("publisher".equals(name)) {
653       return query.withPublisher(value);
654     } else {
655       throw new IllegalArgumentException("Unknown filter :" + name);
656     }
657   }
658 
659   private static SearchQuery applyFilter(final String name, final String value, final SeriesSearchQuery query) {
660     if ("contributors".equals(name)) {
661       return query.withContributor(value);
662     } else if ("creator".equals(name)) {
663       return query.withCreator(value);
664     } else if ("textFilter".equals(name)) {
665       return query.withText("*" + value + "*");
666     } else if ("subject".equals(name)) {
667       return query.withSubject(value);
668     } else if ("title".equals(name)) {
669       return query.withTitle(value);
670     } else if ("description".equals(name)) {
671       return query.withDescription(value);
672     } else if ("language".equals(name)) {
673       return query.withLanguage(value);
674     } else if ("license".equals(name)) {
675       return query.withLicense(value);
676     } else if ("publisher".equals(name)) {
677       return query.withPublisher(value);
678     } else if ("organizer".equals(name)) {
679       return query.withOrganizer(value);
680     } else if ("rightsholder".equals(name)) {
681       return query.withRightsHolder(value);
682     } else {
683       throw new IllegalArgumentException("Unknown filter :" + name);
684     }
685   }
686 
687 }