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
240    * {@link Precision#Day}. */
241   public void setCreated(Date date) {
242     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
243     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
244     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
245     if (getTemporal().isEmpty()) {
246       setDate(PROPERTY_CREATED, date, Precision.Day);
247     }
248   }
249 
250   /** Set the {@link DublinCore#PROPERTY_CREATED} property. The date is encoded with a precision of
251    * {@link Precision#Day}. */
252   public void setCreated(Temporal t) {
253     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
254     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
255     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
256     if (getTemporal().isEmpty()) {
257       t.fold(new Temporal.Match<Void>() {
258         @Override
259         public Void period(DCMIPeriod period) {
260           setCreated(period.getStart());
261           return null;
262         }
263 
264         @Override
265         public Void instant(Date instant) {
266           setCreated(instant);
267           return null;
268         }
269 
270         @Override
271         public Void duration(long duration) {
272           // No action needed for duration
273           return null;
274         }
275       });
276     }
277   }
278 
279 
280   /** Remove the {@link DublinCore#PROPERTY_CREATED} property. */
281   public void removeCreated() {
282     dc.remove(PROPERTY_CREATED);
283   }
284 
285   /* ---------------------------------------------------------------------------------------------------------------- */
286 
287   /** Get all {@link DublinCore#PROPERTY_CREATOR} properties. */
288   @Nonnull public List<String> getCreators() {
289     return get(PROPERTY_CREATOR);
290   }
291 
292   /** Set multiple {@link DublinCore#PROPERTY_CREATOR} properties. */
293   public void setCreators(List<String> creators) {
294     set(PROPERTY_CREATOR, creators);
295   }
296 
297   /** Set the {@link DublinCore#PROPERTY_CREATOR} property. */
298   public void setCreator(String creator) {
299     set(PROPERTY_CREATOR, creator);
300   }
301 
302   /** Add a {@link DublinCore#PROPERTY_CREATOR} property. */
303   public void addCreator(String name) {
304     add(PROPERTY_CREATOR, name);
305   }
306 
307   /** Update the {@link DublinCore#PROPERTY_CREATOR} property. */
308   public void updateCreator(Optional<String> name) {
309     update(PROPERTY_CREATOR, name);
310   }
311 
312   /** Remove all {@link DublinCore#PROPERTY_CREATOR} properties. */
313   public void removeCreators() {
314     dc.remove(PROPERTY_CREATOR);
315   }
316 
317   /* ---------------------------------------------------------------------------------------------------------------- */
318 
319   /** Get the {@link DublinCore#PROPERTY_EXTENT} property. */
320   @Nonnull public Optional<Long> getExtent() {
321     return getFirst(PROPERTY_EXTENT).map(OpencastMetadataCodec.decodeDuration);
322   }
323 
324   /** Set the {@link DublinCore#PROPERTY_EXTENT} property. */
325   public void setExtent(Long extent) {
326     dc.set(PROPERTY_EXTENT, OpencastMetadataCodec.encodeDuration(extent));
327   }
328 
329   /** Remove the {@link DublinCore#PROPERTY_EXTENT} property. */
330   public void removeExtent() {
331     dc.remove(PROPERTY_EXTENT);
332   }
333 
334   /* ---------------------------------------------------------------------------------------------------------------- */
335 
336   /** Get the {@link DublinCore#PROPERTY_ISSUED} property. */
337   @Nonnull public Optional<Date> getIssued() {
338     return getFirst(PROPERTY_ISSUED).map(OpencastMetadataCodec.decodeDate);
339   }
340 
341   /** Set the {@link DublinCore#PROPERTY_ISSUED} property. */
342   public void setIssued(Date date) {
343     setDate(PROPERTY_ISSUED, date, Precision.Day);
344   }
345 
346   /** Update the {@link DublinCore#PROPERTY_ISSUED} property. */
347   public void updateIssued(Optional<Date> date) {
348     updateDate(PROPERTY_ISSUED, date, Precision.Day);
349   }
350 
351   /** Remove the {@link DublinCore#PROPERTY_ISSUED} property. */
352   public void removeIssued() {
353     dc.remove(PROPERTY_ISSUED);
354   }
355 
356   /* ---------------------------------------------------------------------------------------------------------------- */
357 
358   /** Get the {@link DublinCore#PROPERTY_LANGUAGE} property. */
359   @Nonnull public Optional<String> getLanguage() {
360     return getFirst(PROPERTY_LANGUAGE);
361   }
362 
363   /**
364    * Set the {@link DublinCore#PROPERTY_LANGUAGE} property.
365    * A 2- or 3-letter ISO code. 2-letter ISO codes are tried to convert into a 3-letter code.
366    * If this is not possible the provided string is used as is.
367    */
368   public void setLanguage(String lang) {
369     if (StringUtils.isNotBlank(lang)) {
370       String doLang = lang;
371       if (lang.length() == 2) {
372         try {
373           doLang = new Locale(lang).getISO3Language();
374         } catch (MissingResourceException ignore) {
375         }
376       }
377       set(PROPERTY_LANGUAGE, doLang);
378     }
379   }
380 
381   /** Remove the {@link DublinCore#PROPERTY_LANGUAGE} property. */
382   public void removeLanguage() {
383     dc.remove(PROPERTY_LANGUAGE);
384   }
385 
386   /* ---------------------------------------------------------------------------------------------------------------- */
387 
388   /** Get the {@link DublinCore#PROPERTY_SPATIAL} property. */
389   @Nonnull public Optional<String> getSpatial() {
390     return getFirst(PROPERTY_SPATIAL);
391   }
392 
393   /** Set the {@link DublinCore#PROPERTY_SPATIAL} property. */
394   public void setSpatial(String spatial) {
395     set(PROPERTY_SPATIAL, spatial);
396   }
397 
398   /** Update the {@link DublinCore#PROPERTY_SPATIAL} property. */
399   public void updateSpatial(Optional<String> spatial) {
400     update(PROPERTY_SPATIAL, spatial);
401   }
402 
403   /** Remove the {@link DublinCore#PROPERTY_SPATIAL} property. */
404   public void removeSpatial() {
405     dc.remove(PROPERTY_SPATIAL);
406   }
407 
408   /* ---------------------------------------------------------------------------------------------------------------- */
409 
410   /** Get the {@link DublinCore#PROPERTY_SOURCE} property. */
411   @Nonnull public Optional<String> getSource() {
412     return getFirst(PROPERTY_SOURCE);
413   }
414 
415   /** Set the {@link DublinCore#PROPERTY_SOURCE} property. */
416   public void setSource(String source) {
417     set(PROPERTY_SOURCE, source);
418   }
419 
420   /** Remove the {@link DublinCore#PROPERTY_SOURCE} property. */
421   public void removeSource() {
422     dc.remove(PROPERTY_SOURCE);
423   }
424 
425   /* ---------------------------------------------------------------------------------------------------------------- */
426 
427   /** Get all {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
428   @Nonnull public List<String> getContributors() {
429     return get(PROPERTY_CONTRIBUTOR);
430   }
431 
432   /** Set multiple {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
433   public void setContributors(List<String> contributors) {
434     set(PROPERTY_CONTRIBUTOR, contributors);
435   }
436 
437   /** Set the {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
438   public void setContributor(String contributor) {
439     set(PROPERTY_CONTRIBUTOR, contributor);
440   }
441 
442   /** Add a {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
443   public void addContributor(String contributor) {
444     add(PROPERTY_CONTRIBUTOR, contributor);
445   }
446 
447   /** Update the {@link DublinCore#PROPERTY_CONTRIBUTOR} property. */
448   public void updateContributor(Optional<String> contributor) {
449     update(PROPERTY_CONTRIBUTOR, contributor);
450   }
451 
452   /** Remove all {@link DublinCore#PROPERTY_CONTRIBUTOR} properties. */
453   public void removeContributors() {
454     dc.remove(PROPERTY_CONTRIBUTOR);
455   }
456 
457   /* ---------------------------------------------------------------------------------------------------------------- */
458 
459   /** Get the {@link DublinCore#PROPERTY_TEMPORAL} property. */
460   @Nonnull public Optional<Temporal> getTemporal() {
461     return getFirstVal(PROPERTY_TEMPORAL).map(OpencastMetadataCodec.decodeTemporal);
462   }
463 
464   /**
465    * Set the {@link DublinCore#PROPERTY_TEMPORAL} property.
466    * The dates are encoded with a precision of {@link Precision#Second}.
467    */
468   public void setTemporal(Date from, Date to) {
469     setPeriod(PROPERTY_TEMPORAL, from, to, Precision.Second);
470 
471     // make sure that DC created is synchronized with start date, as discussed in MH-12250
472     setDate(PROPERTY_CREATED, from, Precision.Day);
473   }
474 
475   /** Remove the {@link DublinCore#PROPERTY_TEMPORAL} property. */
476   public void removeTemporal() {
477     dc.remove(PROPERTY_TEMPORAL);
478   }
479 
480   /* ---------------------------------------------------------------------------------------------------------------- */
481 
482   /** Get the {@link DublinCore#PROPERTY_TYPE} property split into its components. Components are separated by "/". */
483   @Nonnull public Optional<Stream<String>> getType() {
484     return getFirst(PROPERTY_TYPE).map(s -> Stream.of(s.split("/")));
485   }
486 
487   /** Get the {@link DublinCore#PROPERTY_TYPE} property as a single string. */
488   @Nonnull public Optional<String> getTypeCombined() {
489     return getFirst(PROPERTY_TYPE);
490   }
491 
492   /**
493    * Set the {@link DublinCore#PROPERTY_TYPE} property from a type and a subtype.
494    * Type and subtype are separated by "/".
495    */
496   public void setType(String type, String subtype) {
497     set(PROPERTY_TYPE, type + "/" + subtype);
498   }
499 
500   /** Set the {@link DublinCore#PROPERTY_TYPE} property from a single string. */
501   public void setType(String type) {
502     set(PROPERTY_TYPE, type);
503   }
504 
505   /** Remove the {@link DublinCore#PROPERTY_TYPE} property. */
506   public void removeType() {
507     dc.remove(PROPERTY_TYPE);
508   }
509 
510   /* ---------------------------------------------------------------------------------------------------------------- */
511 
512   public static final class Episode extends OpencastDctermsDublinCore {
513 
514     public Episode(DublinCoreCatalog dc) {
515       super(dc);
516     }
517 
518     /** Get the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
519     @Nonnull public Optional<String> getIsPartOf() {
520       return getFirst(PROPERTY_IS_PART_OF);
521     }
522 
523     /** Set the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
524     public void setIsPartOf(String seriesID) {
525       set(PROPERTY_IS_PART_OF, seriesID);
526     }
527 
528     /** Update the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
529     public void updateIsPartOf(Optional<String> seriesID) {
530       update(PROPERTY_IS_PART_OF, seriesID);
531     }
532 
533     /** Remove the {@link DublinCore#PROPERTY_IS_PART_OF} property. */
534     public void removeIsPartOf() {
535       dc.remove(PROPERTY_IS_PART_OF);
536     }
537 
538   }
539 
540   /* ---------------------------------------------------------------------------------------------------------------- */
541 
542   public static final class Series extends OpencastDctermsDublinCore {
543     public Series(DublinCoreCatalog dc) {
544       super(dc);
545     }
546   }
547 
548   /* ---------------------------------------------------------------------------------------------------------------- */
549 
550   protected void setDate(EName property, Date date, Precision p) {
551     dc.set(property, OpencastMetadataCodec.encodeDate(date, p));
552   }
553 
554   protected void updateDate(EName property, Optional<Date> date, Precision p) {
555     if (date.isPresent()) {
556       setDate(property, date.get(), p);
557     }
558   }
559 
560   /** Encode with {@link Precision#Second}. */
561   protected void setPeriod(EName property, Date from, Date to, Precision p) {
562     dc.set(property, OpencastMetadataCodec.encodePeriod(from, to, p));
563   }
564 
565   protected List<String> get(EName property) {
566     return dc.get(property, LANGUAGE_ANY);
567   }
568 
569   /** Like {@link DublinCore#getFirst(EName)} but with the result wrapped in an Opt. */
570   protected Optional<String> getFirst(EName property) {
571     return Optional.ofNullable(dc.getFirst(property));
572   }
573 
574   /** Like {@link DublinCore#getFirstVal(EName)} but with the result wrapped in an Opt. */
575   protected Optional<DublinCoreValue> getFirstVal(EName property) {
576     return Optional.ofNullable(dc.getFirstVal(property));
577   }
578 
579   public void set(EName property, String value) {
580     if (StringUtils.isNotBlank(value)) {
581       dc.set(property, value);
582     }
583   }
584 
585   public void set(EName property, List<String> values) {
586     List<DublinCoreValue> valuesFiltered = values.stream()
587         .filter(s -> s != null && !s.trim().isEmpty())
588         .map(DublinCoreValue::mk) // assuming mkValue is DublinCoreValue::mk
589         .collect(Collectors.toList());
590     if (!valuesFiltered.isEmpty()) {
591       dc.remove(property);
592       dc.set(property, valuesFiltered);
593     }
594   }
595 
596   protected void add(EName property, String value) {
597     if (StringUtils.isNotBlank(value)) {
598       dc.add(property, value);
599     }
600   }
601 
602   protected void update(EName property, Optional<String> value) {
603     if (value.isPresent()) {
604       set(property, value.get());
605     }
606   }
607 }