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  package org.opencastproject.metadata.dublincore;
22  
23  import static org.opencastproject.metadata.dublincore.DublinCore.LANGUAGE_ANY;
24  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_AUDIENCE;
25  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CONTRIBUTOR;
26  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATED;
27  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATOR;
28  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_DESCRIPTION;
29  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_EXTENT;
30  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER;
31  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_ISSUED;
32  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IS_PART_OF;
33  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LANGUAGE;
34  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LICENSE;
35  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_PUBLISHER;
36  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_RIGHTS_HOLDER;
37  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SOURCE;
38  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SPATIAL;
39  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TEMPORAL;
40  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TITLE;
41  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TYPE;
42  
43  import org.opencastproject.mediapackage.EName;
44  
45  import org.apache.commons.lang3.StringUtils;
46  
47  import java.util.Date;
48  import java.util.List;
49  import java.util.Locale;
50  import java.util.MissingResourceException;
51  import java.util.Optional;
52  import java.util.stream.Collectors;
53  import java.util.stream.Stream;
54  
55  import javax.annotation.Nonnull;
56  import javax.annotation.ParametersAreNonnullByDefault;
57  
58  /**
59   * {@link DublinCoreCatalog} wrapper to deal with DublinCore metadata according to the Opencast schema.
60   * <p>
61   * <h2>General behaviour</h2>
62   * <ul>
63   * <li>Set methods that take a string parameter only execute if the string is not blank.
64   * <li>Set methods that take a list of strings only execute if the list contains at least one non-blank string.
65   * <li>Set methods--if executed--replace the whole property with the given value/s.
66   * <li>Update methods only execute if the parameter is some non-blank string. If executed they
67   * behave like a set method and replace all exiting entries.
68   * <li>Add methods only execute if the parameter is some non-blank string.
69   * </ul>
70   */
71  @ParametersAreNonnullByDefault
72  public abstract class OpencastDctermsDublinCore {
73    protected final DublinCoreCatalog dc;
74  
75    private OpencastDctermsDublinCore(DublinCoreCatalog dc) {
76      this.dc = dc;
77    }
78  
79    /** Return the wrapped catalog. */
80    public DublinCoreCatalog getCatalog() {
81      return dc;
82    }
83  
84    /* ------------------------------------------------------------------------------------------------------------------ */
85  
86    @Nonnull public List<String> getPublishers() {
87      return get(PROPERTY_PUBLISHER);
88    }
89  
90    public void setPublishers(List<String> publishers) {
91      set(PROPERTY_PUBLISHER, publishers);
92    }
93  
94    public void addPublisher(String publisher) {
95      add(PROPERTY_PUBLISHER, publisher);
96    }
97  
98    public void removePublishers() {
99      dc.remove(PROPERTY_PUBLISHER);
100   }
101 
102   /* ------------------------------------------------------------------------------------------------------------------ */
103 
104   @Nonnull public List<String> getRightsHolders() {
105     return get(PROPERTY_RIGHTS_HOLDER);
106   }
107 
108   public void setRightsHolders(List<String> rightsHolders) {
109     set(PROPERTY_RIGHTS_HOLDER, rightsHolders);
110   }
111 
112   public void addRightsHolder(String rightsHolder) {
113     add(PROPERTY_RIGHTS_HOLDER, rightsHolder);
114   }
115 
116   public void removeRightsHolders() {
117     dc.remove(PROPERTY_RIGHTS_HOLDER);
118   }
119 
120   /* ------------------------------------------------------------------------------------------------------------------ */
121 
122   @Nonnull public Optional<String> getLicense() {
123     return getFirst(PROPERTY_LICENSE);
124   }
125 
126   public void setLicense(String license) {
127     set(PROPERTY_LICENSE, license);
128   }
129 
130   public void removeLicense() {
131     dc.remove(PROPERTY_LICENSE);
132   }
133 
134   /* ------------------------------------------------------------------------------------------------------------------ */
135 
136   /** Get the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
137   @Nonnull public Optional<String> getDcIdentifier() {
138     return getFirst(PROPERTY_IDENTIFIER);
139   }
140 
141   /** Set the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
142   public void setDcIdentifier(String id) {
143     set(PROPERTY_IDENTIFIER, id);
144   }
145 
146   /** Update the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
147   public void updateDcIdentifier(Optional<String> id) {
148     update(PROPERTY_IDENTIFIER, id);
149   }
150 
151   /** Remove the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
152   public void removeDcIdentifier() {
153     dc.remove(PROPERTY_IDENTIFIER);
154   }
155 
156   /* ------------------------------------------------------------------------------------------------------------------ */
157 
158   /** Get the {@link DublinCore#PROPERTY_TITLE} property. */
159   @Nonnull public Optional<String> getTitle() {
160     return getFirst(PROPERTY_TITLE);
161   }
162 
163   /** Set the {@link DublinCore#PROPERTY_TITLE} property. */
164   public void setTitle(String title) {
165     set(PROPERTY_TITLE, title);
166   }
167 
168   /** Update the {@link DublinCore#PROPERTY_TITLE} property. */
169   public void updateTitle(Optional<String> title) {
170     update(PROPERTY_TITLE, title);
171   }
172 
173   /** Remove the {@link DublinCore#PROPERTY_TITLE} property. */
174   public void removeTitle() {
175     dc.remove(PROPERTY_TITLE);
176   }
177 
178   /* ------------------------------------------------------------------------------------------------------------------ */
179 
180   /** Get the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
181   @Nonnull public Optional<String> getDescription() {
182     return getFirst(PROPERTY_DESCRIPTION);
183   }
184 
185   /** Set the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
186   public void setDescription(String description) {
187     set(PROPERTY_DESCRIPTION, description);
188   }
189 
190   /** Update the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
191   public void updateDescription(Optional<String> description) {
192     update(PROPERTY_DESCRIPTION, description);
193   }
194 
195   /** Remove the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
196   public void removeDescription() {
197     dc.remove(PROPERTY_DESCRIPTION);
198   }
199 
200   /* ------------------------------------------------------------------------------------------------------------------ */
201 
202   /** Get all {@link DublinCore#PROPERTY_AUDIENCE} properties. */
203   @Nonnull public List<String> getAudiences() {
204     return get(PROPERTY_AUDIENCE);
205   }
206 
207   /** Set multiple {@link DublinCore#PROPERTY_AUDIENCE} properties. */
208   public void setAudiences(List<String> audiences) {
209     set(PROPERTY_AUDIENCE, audiences);
210   }
211 
212   /** Set the {@link DublinCore#PROPERTY_AUDIENCE} property. */
213   public void setAudience(String audience) {
214     set(PROPERTY_AUDIENCE, audience);
215   }
216 
217   /** Add an {@link DublinCore#PROPERTY_AUDIENCE} property. */
218   public void addAudience(String audience) {
219     add(PROPERTY_AUDIENCE, audience);
220   }
221 
222   /** Update the {@link DublinCore#PROPERTY_AUDIENCE} property. */
223   public void updateAudience(Optional<String> audience) {
224     update(PROPERTY_AUDIENCE, audience);
225   }
226 
227   /** Remove all {@link DublinCore#PROPERTY_AUDIENCE} properties. */
228   public void removeAudiences() {
229     dc.remove(PROPERTY_AUDIENCE);
230   }
231 
232   /* ------------------------------------------------------------------------------------------------------------------ */
233 
234   /** Get the {@link DublinCore#PROPERTY_CREATED} property. */
235   @Nonnull public Optional<Temporal> getCreated() {
236     return getFirstVal(PROPERTY_CREATED).map(OpencastMetadataCodec.decodeTemporal);
237   }
238 
239   /** Set the {@link DublinCore#PROPERTY_CREATED} property. The date is encoded with a precision of {@link Precision#Day}. */
240   public void setCreated(Date date) {
241     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
242     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
243     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
244     if (getTemporal().isEmpty()) {
245       setDate(PROPERTY_CREATED, date, Precision.Day);
246     }
247   }
248 
249   /** Set the {@link DublinCore#PROPERTY_CREATED} property. The date is encoded with a precision of {@link Precision#Day}. */
250   public void setCreated(Temporal t) {
251     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
252     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
253     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
254     if (getTemporal().isEmpty()) {
255       t.fold(new Temporal.Match<Void>() {
256         @Override
257         public Void period(DCMIPeriod period) {
258           setCreated(period.getStart());
259           return null;
260         }
261 
262         @Override
263         public Void instant(Date instant) {
264           setCreated(instant);
265           return null;
266         }
267 
268         @Override
269         public Void duration(long duration) {
270           // No action needed for duration
271           return null;
272         }
273       });
274     }
275   }
276 
277 
278   /** Remove the {@link DublinCore#PROPERTY_CREATED} property. */
279   public void removeCreated() {
280     dc.remove(PROPERTY_CREATED);
281   }
282 
283   /* ------------------------------------------------------------------------------------------------------------------ */
284 
285   /** Get all {@link DublinCore#PROPERTY_CREATOR} properties. */
286   @Nonnull public List<String> getCreators() {
287     return get(PROPERTY_CREATOR);
288   }
289 
290   /** Set multiple {@link DublinCore#PROPERTY_CREATOR} properties. */
291   public void setCreators(List<String> creators) {
292     set(PROPERTY_CREATOR, creators);
293   }
294 
295   /** Set the {@link DublinCore#PROPERTY_CREATOR} property. */
296   public void setCreator(String creator) {
297     set(PROPERTY_CREATOR, creator);
298   }
299 
300   /** Add a {@link DublinCore#PROPERTY_CREATOR} property. */
301   public void addCreator(String name) {
302     add(PROPERTY_CREATOR, name);
303   }
304 
305   /** Update the {@link DublinCore#PROPERTY_CREATOR} property. */
306   public void updateCreator(Optional<String> name) {
307     update(PROPERTY_CREATOR, name);
308   }
309 
310   /** Remove all {@link DublinCore#PROPERTY_CREATOR} properties. */
311   public void removeCreators() {
312     dc.remove(PROPERTY_CREATOR);
313   }
314 
315   /* ------------------------------------------------------------------------------------------------------------------ */
316 
317   /** Get the {@link DublinCore#PROPERTY_EXTENT} property. */
318   @Nonnull public Optional<Long> getExtent() {
319     return getFirst(PROPERTY_EXTENT).map(OpencastMetadataCodec.decodeDuration);
320   }
321 
322   /** Set the {@link DublinCore#PROPERTY_EXTENT} property. */
323   public void setExtent(Long extent) {
324     dc.set(PROPERTY_EXTENT, OpencastMetadataCodec.encodeDuration(extent));
325   }
326 
327   /** Remove the {@link DublinCore#PROPERTY_EXTENT} property. */
328   public void removeExtent() {
329     dc.remove(PROPERTY_EXTENT);
330   }
331 
332   /* ------------------------------------------------------------------------------------------------------------------ */
333 
334   /** Get the {@link DublinCore#PROPERTY_ISSUED} property. */
335   @Nonnull public Optional<Date> getIssued() {
336     return getFirst(PROPERTY_ISSUED).map(OpencastMetadataCodec.decodeDate);
337   }
338 
339   /** Set the {@link DublinCore#PROPERTY_ISSUED} property. */
340   public void setIssued(Date date) {
341     setDate(PROPERTY_ISSUED, date, Precision.Day);
342   }
343 
344   /** Update the {@link DublinCore#PROPERTY_ISSUED} property. */
345   public void updateIssued(Optional<Date> date) {
346     updateDate(PROPERTY_ISSUED, date, Precision.Day);
347   }
348 
349   /** Remove the {@link DublinCore#PROPERTY_ISSUED} property. */
350   public void removeIssued() {
351     dc.remove(PROPERTY_ISSUED);
352   }
353 
354   /* ------------------------------------------------------------------------------------------------------------------ */
355 
356   /** Get the {@link DublinCore#PROPERTY_LANGUAGE} property. */
357   @Nonnull public Optional<String> getLanguage() {
358     return getFirst(PROPERTY_LANGUAGE);
359   }
360 
361   /**
362    * Set the {@link DublinCore#PROPERTY_LANGUAGE} property.
363    * A 2- or 3-letter ISO code. 2-letter ISO codes are tried to convert into a 3-letter code.
364    * If this is not possible the provided string is used as is.
365    */
366   public void setLanguage(String lang) {
367     if (StringUtils.isNotBlank(lang)) {
368       String doLang = lang;
369       if (lang.length() == 2) {
370         try {
371           doLang = new Locale(lang).getISO3Language();
372         } catch (MissingResourceException ignore) {
373         }
374       }
375       set(PROPERTY_LANGUAGE, doLang);
376     }
377   }
378 
379   /** Remove the {@link DublinCore#PROPERTY_LANGUAGE} property. */
380   public void removeLanguage() {
381     dc.remove(PROPERTY_LANGUAGE);
382   }
383 
384   /* ------------------------------------------------------------------------------------------------------------------ */
385 
386   /** Get the {@link DublinCore#PROPERTY_SPATIAL} property. */
387   @Nonnull public Optional<String> getSpatial() {
388     return getFirst(PROPERTY_SPATIAL);
389   }
390 
391   /** Set the {@link DublinCore#PROPERTY_SPATIAL} property. */
392   public void setSpatial(String spatial) {
393     set(PROPERTY_SPATIAL, spatial);
394   }
395 
396   /** Update the {@link DublinCore#PROPERTY_SPATIAL} property. */
397   public void updateSpatial(Optional<String> spatial) {
398     update(PROPERTY_SPATIAL, spatial);
399   }
400 
401   /** Remove the {@link DublinCore#PROPERTY_SPATIAL} property. */
402   public void removeSpatial() {
403     dc.remove(PROPERTY_SPATIAL);
404   }
405 
406   /* ------------------------------------------------------------------------------------------------------------------ */
407 
408   /** Get the {@link DublinCore#PROPERTY_SOURCE} property. */
409   @Nonnull public Optional<String> getSource() {
410     return getFirst(PROPERTY_SOURCE);
411   }
412 
413   /** Set the {@link DublinCore#PROPERTY_SOURCE} property. */
414   public void setSource(String source) {
415     set(PROPERTY_SOURCE, source);
416   }
417 
418   /** Remove the {@link DublinCore#PROPERTY_SOURCE} property. */
419   public void removeSource() {
420     dc.remove(PROPERTY_SOURCE);
421   }
422 
423   /* ------------------------------------------------------------------------------------------------------------------ */
424 
425   /** Get all {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
426   @Nonnull public List<String> getContributors() {
427     return get(PROPERTY_CONTRIBUTOR);
428   }
429 
430   /** Set multiple {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
431   public void setContributors(List<String> contributors) {
432     set(PROPERTY_CONTRIBUTOR, contributors);
433   }
434 
435   /** Set the {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
436   public void setContributor(String contributor) {
437     set(PROPERTY_CONTRIBUTOR, contributor);
438   }
439 
440   /** Add a {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
441   public void addContributor(String contributor) {
442     add(PROPERTY_CONTRIBUTOR, contributor);
443   }
444 
445   /** Update the {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
446   public void updateContributor(Optional<String> contributor) {
447     update(PROPERTY_CONTRIBUTOR, contributor);
448   }
449 
450   /** Remove all {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
451   public void removeContributors() {
452     dc.remove(PROPERTY_CONTRIBUTOR);
453   }
454 
455   /* ------------------------------------------------------------------------------------------------------------------ */
456 
457   /** Get the {@link DublinCore#PROPERTY_TEMPORAL} property. */
458   @Nonnull public Optional<Temporal> getTemporal() {
459     return getFirstVal(PROPERTY_TEMPORAL).map(OpencastMetadataCodec.decodeTemporal);
460   }
461 
462   /**
463    * Set the {@link DublinCore#PROPERTY_TEMPORAL} property.
464    * The dates are encoded with a precision of {@link Precision#Second}.
465    */
466   public void setTemporal(Date from, Date to) {
467     setPeriod(PROPERTY_TEMPORAL, from, to, Precision.Second);
468 
469     // make sure that DC created is synchronized with start date, as discussed in MH-12250
470     setDate(PROPERTY_CREATED, from, Precision.Day);
471   }
472 
473   /** Remove the {@link DublinCore#PROPERTY_TEMPORAL} property. */
474   public void removeTemporal() {
475     dc.remove(PROPERTY_TEMPORAL);
476   }
477 
478   /* ------------------------------------------------------------------------------------------------------------------ */
479 
480   /** Get the {@link DublinCore#PROPERTY_TYPE} property split into its components. Components are separated by "/". */
481   @Nonnull public Optional<Stream<String>> getType() {
482     return getFirst(PROPERTY_TYPE).map(s -> Stream.of(s.split("/")));
483   }
484 
485   /** Get the {@link DublinCore#PROPERTY_TYPE} property as a single string. */
486   @Nonnull public Optional<String> getTypeCombined() {
487     return getFirst(PROPERTY_TYPE);
488   }
489 
490   /**
491    * Set the {@link DublinCore#PROPERTY_TYPE} property from a type and a subtype.
492    * Type and subtype are separated by "/".
493    */
494   public void setType(String type, String subtype) {
495     set(PROPERTY_TYPE, type + "/" + subtype);
496   }
497 
498   /** Set the {@link DublinCore#PROPERTY_TYPE} property from a single string. */
499   public void setType(String type) {
500     set(PROPERTY_TYPE, type);
501   }
502 
503   /** Remove the {@link DublinCore#PROPERTY_TYPE} property. */
504   public void removeType() {
505     dc.remove(PROPERTY_TYPE);
506   }
507 
508   /* ------------------------------------------------------------------------------------------------------------------ */
509 
510   public static final class Episode extends OpencastDctermsDublinCore {
511 
512     public Episode(DublinCoreCatalog dc) {
513       super(dc);
514     }
515 
516     /** Get the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
517     @Nonnull public Optional<String> getIsPartOf() {
518       return getFirst(PROPERTY_IS_PART_OF);
519     }
520 
521     /** Set the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
522     public void setIsPartOf(String seriesID) {
523       set(PROPERTY_IS_PART_OF, seriesID);
524     }
525 
526     /** Update the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
527     public void updateIsPartOf(Optional<String> seriesID) {
528       update(PROPERTY_IS_PART_OF, seriesID);
529     }
530 
531     /** Remove the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
532     public void removeIsPartOf() {
533       dc.remove(PROPERTY_IS_PART_OF);
534     }
535 
536   }
537 
538   /* ------------------------------------------------------------------------------------------------------------------ */
539 
540   public static final class Series extends OpencastDctermsDublinCore {
541     public Series(DublinCoreCatalog dc) {
542       super(dc);
543     }
544   }
545 
546   /* ------------------------------------------------------------------------------------------------------------------ */
547 
548   protected void setDate(EName property, Date date, Precision p) {
549     dc.set(property, OpencastMetadataCodec.encodeDate(date, p));
550   }
551 
552   protected void updateDate(EName property, Optional<Date> date, Precision p) {
553     if (date.isPresent()) {
554       setDate(property, date.get(), p);
555     }
556   }
557 
558   /** Encode with {@link Precision#Second}. */
559   protected void setPeriod(EName property, Date from, Date to, Precision p) {
560     dc.set(property, OpencastMetadataCodec.encodePeriod(from, to, p));
561   }
562 
563   protected List<String> get(EName property) {
564     return dc.get(property, LANGUAGE_ANY);
565   }
566 
567   /** Like {@link DublinCore#getFirst(EName)} but with the result wrapped in an Opt. */
568   protected Optional<String> getFirst(EName property) {
569     return Optional.ofNullable(dc.getFirst(property));
570   }
571 
572   /** Like {@link DublinCore#getFirstVal(EName)} but with the result wrapped in an Opt. */
573   protected Optional<DublinCoreValue> getFirstVal(EName property) {
574     return Optional.ofNullable(dc.getFirstVal(property));
575   }
576 
577   public void set(EName property, String value) {
578     if (StringUtils.isNotBlank(value)) {
579       dc.set(property, value);
580     }
581   }
582 
583   public void set(EName property, List<String> values) {
584     List<DublinCoreValue> valuesFiltered = values.stream()
585         .filter(s -> s != null && !s.trim().isEmpty())
586         .map(DublinCoreValue::mk) // assuming mkValue is DublinCoreValue::mk
587         .collect(Collectors.toList());
588     if (!valuesFiltered.isEmpty()) {
589       dc.remove(property);
590       dc.set(property, valuesFiltered);
591     }
592   }
593 
594   protected void add(EName property, String value) {
595     if (StringUtils.isNotBlank(value)) {
596       dc.add(property, value);
597     }
598   }
599 
600   protected void update(EName property, Optional<String> value) {
601     if (value.isPresent()) {
602       set(property, value.get());
603     }
604   }
605 }