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.list.common.provider;
23  
24  import org.opencastproject.elasticsearch.api.SearchIndexException;
25  import org.opencastproject.elasticsearch.api.SearchResult;
26  import org.opencastproject.elasticsearch.api.SearchResultItem;
27  import org.opencastproject.elasticsearch.index.ElasticsearchIndex;
28  import org.opencastproject.elasticsearch.index.objects.series.Series;
29  import org.opencastproject.elasticsearch.index.objects.series.SeriesIndexSchema;
30  import org.opencastproject.elasticsearch.index.objects.series.SeriesSearchQuery;
31  import org.opencastproject.list.api.ListProviderException;
32  import org.opencastproject.list.api.ResourceListFilter;
33  import org.opencastproject.list.api.ResourceListProvider;
34  import org.opencastproject.list.api.ResourceListQuery;
35  import org.opencastproject.list.common.query.SeriesListQuery;
36  import org.opencastproject.security.api.Permissions;
37  import org.opencastproject.security.api.SecurityService;
38  import org.opencastproject.util.data.Tuple;
39  import org.opencastproject.util.requests.SortCriterion;
40  
41  import org.apache.commons.lang3.StringUtils;
42  import org.osgi.framework.BundleContext;
43  import org.osgi.service.component.annotations.Activate;
44  import org.osgi.service.component.annotations.Component;
45  import org.osgi.service.component.annotations.Reference;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.Calendar;
52  import java.util.Date;
53  import java.util.HashMap;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.function.Function;
57  import java.util.stream.Collectors;
58  
59  @Component(
60      service = ResourceListProvider.class,
61      property = {
62          "service.description=Series list provider",
63          "opencast.service.type=org.opencastproject.list.provider.SeriesListProvider"
64      }
65  )
66  public class SeriesListProvider implements ResourceListProvider {
67    private static final Logger logger = LoggerFactory.getLogger(SeriesListProvider.class);
68  
69    public static final String PROVIDER_PREFIX = "SERIES";
70  
71    public static final String NAME = PROVIDER_PREFIX + ".NAME";
72    public static final String CONTRIBUTORS = PROVIDER_PREFIX + ".CONTRIBUTORS";
73    public static final String SUBJECT = PROVIDER_PREFIX + ".SUBJECT";
74    public static final String TITLE = PROVIDER_PREFIX + ".TITLE";
75    public static final String TITLE_EXTENDED = PROVIDER_PREFIX + ".TITLE_EXTENDED";
76    public static final String LANGUAGE = PROVIDER_PREFIX + ".LANGUAGE";
77    public static final String ORGANIZERS = PROVIDER_PREFIX + ".ORGANIZERS";
78    public static final String LICENSE = PROVIDER_PREFIX + ".LICENSE";
79    public static final String SERIES_WRITE_ONLY = PROVIDER_PREFIX + ".WRITE_ONLY";
80  
81    private static final String[] NAMES = { PROVIDER_PREFIX, CONTRIBUTORS, ORGANIZERS, TITLE_EXTENDED,
82        SERIES_WRITE_ONLY };
83  
84    /** The search index. */
85    private ElasticsearchIndex searchIndex;
86  
87    /** The security service. */
88    private SecurityService securityService;
89  
90    @Activate
91    protected void activate(BundleContext bundleContext) {
92      logger.info("Series list provider activated!");
93    }
94  
95    /** OSGi callback for series services. */
96    @Reference
97    public void setSearchIndex(ElasticsearchIndex searchIndex) {
98      this.searchIndex = searchIndex;
99    }
100 
101   /** OSGi callback for security service */
102   @Reference
103   public void setSecurityService(SecurityService securityService) {
104     this.securityService = securityService;
105   }
106 
107   @Override
108   public String[] getListNames() {
109     return NAMES;
110   }
111 
112   @Override
113   public Map<String, String> getList(String listName, ResourceListQuery query)
114           throws ListProviderException {
115     SeriesSearchQuery seriesQuery = toSearchQuery(query);
116     Map<String, String> result = new HashMap<>();
117     if (TITLE.equals(listName)) {
118       seriesQuery.sortByTitle(SortCriterion.Order.Ascending);
119       for (String title : searchIndex.getTermsForField(SeriesIndexSchema.TITLE, Series.DOCUMENT_TYPE)) {
120         result.put(title, title);
121       }
122     } else if (CONTRIBUTORS.equals(listName)) {
123       seriesQuery.sortByContributors(SortCriterion.Order.Ascending);
124       for (String contributor : searchIndex.getTermsForField(SeriesIndexSchema.CONTRIBUTORS, Series.DOCUMENT_TYPE)) {
125         result.put(contributor, contributor);
126       }
127     } else if (ORGANIZERS.equals(listName)) {
128       seriesQuery.sortByOrganizers(SortCriterion.Order.Ascending);
129       for (String organizer : searchIndex.getTermsForField(SeriesIndexSchema.ORGANIZERS, Series.DOCUMENT_TYPE)) {
130         result.put(organizer, organizer);
131       }
132     } else {
133       try {
134         seriesQuery.sortByTitle(SortCriterion.Order.Ascending);
135         seriesQuery.sortByCreatedDateTime(SortCriterion.Order.Descending);
136         seriesQuery.sortByOrganizers(SortCriterion.Order.Ascending);
137         if (SERIES_WRITE_ONLY.equals(listName)) {
138           seriesQuery.withAction(Permissions.Action.WRITE);
139         }
140         SearchResult<Series> searchResult = searchIndex.getByQuery(seriesQuery);
141         Calendar calendar = Calendar.getInstance();
142         //We might have duplicate series names, so let's count them and see
143         Map<String, Long> duplicates = Arrays.stream(searchResult.getItems())
144             .map(series -> series.getSource().getTitle())
145             .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
146         for (SearchResultItem<Series> item : searchResult.getItems()) {
147           Series s = item.getSource();
148           if (TITLE_EXTENDED.equals(listName)) {
149             Date created = s.getCreatedDateTime();
150             List<String> organizers = s.getOrganizers();
151             StringBuilder sb = new StringBuilder(s.getTitle());
152             if (created != null || (organizers != null && !organizers.isEmpty())) {
153               List<String> extendedTitleData = new ArrayList<>();
154               if (created != null) {
155                 calendar.setTime(created);
156                 extendedTitleData.add(Integer.toString(calendar.get(Calendar.YEAR)));
157               }
158               if (organizers != null && !organizers.isEmpty()) {
159                 extendedTitleData.addAll(organizers);
160               }
161               sb.append(" (").append(StringUtils.join(extendedTitleData, ", ")).append(")");
162             }
163             result.put(s.getIdentifier(), sb.toString());
164           } else if (PROVIDER_PREFIX.equals(listName)) {
165             String newSeriesName = s.getTitle();
166             if (duplicates.get(newSeriesName) > 1L) {
167               // If a series name is repeated, will add the first 7 characters of the series ID to the display name on
168               // the admin-ui
169               if (s.getIdentifier().length() > 8) {
170                 newSeriesName += " " + "(ID: " + s.getIdentifier().substring(0, 8) + "...)";
171               } else {
172                 newSeriesName += " " + "(ID: " + s.getIdentifier() + ")";
173               }
174               logger.trace(String.format("Repeated series title \"%s\" found, changing to \"%s\" for admin-ui display",
175                   s.getTitle(), newSeriesName));
176             }
177             result.put(s.getIdentifier(), newSeriesName);
178           } else {
179             result.put(s.getIdentifier(), s.getTitle());
180           }
181         }
182       } catch (SearchIndexException e) {
183         logger.warn("Unable to query series.", e);
184       }
185     }
186     return result;
187   }
188 
189   @Override
190   public boolean isTranslatable(String listName) {
191     return false;
192   }
193 
194   @Override
195   public String getDefault() {
196     return null;
197   }
198 
199   /**
200    * Creates a series search query from resource list query.
201    *
202    * @param query a resource list query
203    * @return a series search query
204    */
205   protected SeriesSearchQuery toSearchQuery(ResourceListQuery query) {
206     SeriesSearchQuery seriesQuery = new SeriesSearchQuery(securityService.getOrganization().getId(),
207         securityService.getUser());
208     if (query.getLimit().isPresent()) {
209       seriesQuery.withLimit(query.getLimit().get());
210     }
211     if (query.getOffset().isPresent()) {
212       seriesQuery.withOffset(query.getOffset().get());
213     }
214 
215     for (ResourceListFilter filter : query.getFilters()) {
216       if (filter.getValue().isEmpty()) {
217         continue;
218       } else if (SeriesListQuery.FILTER_CREATIONDATE_NAME.equals(filter.getName())) {
219         Tuple<Date, Date> creationDate = (Tuple<Date, Date>) filter.getValue().get();
220         if (creationDate.getA() != null) {
221           seriesQuery.withCreatedFrom(creationDate.getA());
222         }
223         if (creationDate.getB() != null) {
224           seriesQuery.withCreatedTo(creationDate.getB());
225         }
226       } else if (SeriesListQuery.FILTER_CREATOR_NAME.equals(filter.getName())) {
227         seriesQuery.withCreator((String)filter.getValue().get());
228       } else if (SeriesListQuery.FILTER_CONTRIBUTORS_NAME.equals(filter.getName())) {
229         seriesQuery.withContributor((String)filter.getValue().get());
230       } else if (SeriesListQuery.FILTER_LANGUAGE_NAME.equals(filter.getName())) {
231         seriesQuery.withLanguage((String)filter.getValue().get());
232       } else if (SeriesListQuery.FILTER_LICENSE_NAME.equals(filter.getName())) {
233         seriesQuery.withLicense((String)filter.getValue().get());
234       } else if (SeriesListQuery.FILTER_ORGANIZERS_NAME.equals(filter.getName())) {
235         seriesQuery.withOrganizer((String)filter.getValue().get());
236       } else if (SeriesListQuery.FILTER_SUBJECT_NAME.equals(filter.getName())) {
237         seriesQuery.withSubject((String)filter.getValue().get());
238       } else if (SeriesListQuery.FILTER_TEXT_NAME.equals(filter.getName())) {
239         seriesQuery.withText((String)filter.getValue().get());
240       } else if (SeriesListQuery.FILTER_TITLE_NAME.equals(filter.getName())) {
241         seriesQuery.withTitle((String)filter.getValue().get());
242       }
243     }
244 
245     if (query instanceof SeriesListQuery) {
246       if (((SeriesListQuery) query).getReadPermission().isPresent()
247           || ((SeriesListQuery) query).getWritePermission().isPresent()) {
248         seriesQuery.withoutActions();
249         if (((SeriesListQuery) query).getReadPermission().orElse(true)) {
250           seriesQuery.withAction(Permissions.Action.READ);
251         }
252         if (((SeriesListQuery) query).getWritePermission().orElse(false)) {
253           seriesQuery.withAction(Permissions.Action.WRITE);
254         }
255       }
256     }
257     return seriesQuery;
258   }
259 }