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