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 com.entwinemedia.fn.Stream.$;
24  import static org.opencastproject.metadata.dublincore.DublinCore.LANGUAGE_ANY;
25  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_AUDIENCE;
26  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CONTRIBUTOR;
27  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATED;
28  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATOR;
29  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_DESCRIPTION;
30  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_EXTENT;
31  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER;
32  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_ISSUED;
33  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IS_PART_OF;
34  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LANGUAGE;
35  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LICENSE;
36  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_PUBLISHER;
37  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_RIGHTS_HOLDER;
38  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SOURCE;
39  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SPATIAL;
40  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TEMPORAL;
41  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TITLE;
42  import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TYPE;
43  
44  import org.opencastproject.mediapackage.EName;
45  import org.opencastproject.metadata.dublincore.Temporal.Match;
46  
47  import com.entwinemedia.fn.Fn;
48  import com.entwinemedia.fn.Stream;
49  import com.entwinemedia.fn.Unit;
50  import com.entwinemedia.fn.data.Opt;
51  import com.entwinemedia.fn.fns.Strings;
52  
53  import org.apache.commons.lang3.StringUtils;
54  
55  import java.util.Date;
56  import java.util.List;
57  import java.util.Locale;
58  import java.util.MissingResourceException;
59  
60  import javax.annotation.Nonnull;
61  import javax.annotation.ParametersAreNonnullByDefault;
62  
63  /**
64   * {@link DublinCoreCatalog} wrapper to deal with DublinCore metadata according to the Opencast schema.
65   * <p>
66   * <h2>General behaviour</h2>
67   * <ul>
68   * <li>Set methods that take a string parameter only execute if the string is not blank.
69   * <li>Set methods that take a list of strings only execute if the list contains at least one non-blank string.
70   * <li>Set methods--if executed--replace the whole property with the given value/s.
71   * <li>Update methods only execute if the parameter is some non-blank string. If executed they
72   * behave like a set method and replace all exiting entries.
73   * <li>Add methods only execute if the parameter is some non-blank string.
74   * </ul>
75   */
76  @ParametersAreNonnullByDefault
77  public abstract class OpencastDctermsDublinCore {
78    protected final DublinCoreCatalog dc;
79  
80    private OpencastDctermsDublinCore(DublinCoreCatalog dc) {
81      this.dc = dc;
82    }
83  
84    /** Return the wrapped catalog. */
85    public DublinCoreCatalog getCatalog() {
86      return dc;
87    }
88  
89    /* ------------------------------------------------------------------------------------------------------------------ */
90  
91    @Nonnull public List<String> getPublishers() {
92      return get(PROPERTY_PUBLISHER);
93    }
94  
95    public void setPublishers(List<String> publishers) {
96      set(PROPERTY_PUBLISHER, publishers);
97    }
98  
99    public void addPublisher(String publisher) {
100     add(PROPERTY_PUBLISHER, publisher);
101   }
102 
103   public void removePublishers() {
104     dc.remove(PROPERTY_PUBLISHER);
105   }
106 
107   /* ------------------------------------------------------------------------------------------------------------------ */
108 
109   @Nonnull public List<String> getRightsHolders() {
110     return get(PROPERTY_RIGHTS_HOLDER);
111   }
112 
113   public void setRightsHolders(List<String> rightsHolders) {
114     set(PROPERTY_RIGHTS_HOLDER, rightsHolders);
115   }
116 
117   public void addRightsHolder(String rightsHolder) {
118     add(PROPERTY_RIGHTS_HOLDER, rightsHolder);
119   }
120 
121   public void removeRightsHolders() {
122     dc.remove(PROPERTY_RIGHTS_HOLDER);
123   }
124 
125   /* ------------------------------------------------------------------------------------------------------------------ */
126 
127   @Nonnull public Opt<String> getLicense() {
128     return getFirst(PROPERTY_LICENSE);
129   }
130 
131   public void setLicense(String license) {
132     set(PROPERTY_LICENSE, license);
133   }
134 
135   public void removeLicense() {
136     dc.remove(PROPERTY_LICENSE);
137   }
138 
139   /* ------------------------------------------------------------------------------------------------------------------ */
140 
141   /** Get the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
142   @Nonnull public Opt<String> getDcIdentifier() {
143     return getFirst(PROPERTY_IDENTIFIER);
144   }
145 
146   /** Set the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
147   public void setDcIdentifier(String id) {
148     set(PROPERTY_IDENTIFIER, id);
149   }
150 
151   /** Update the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
152   public void updateDcIdentifier(Opt<String> id) {
153     update(PROPERTY_IDENTIFIER, id);
154   }
155 
156   /** Remove the {@link DublinCore#PROPERTY_IDENTIFIER} property. */
157   public void removeDcIdentifier() {
158     dc.remove(PROPERTY_IDENTIFIER);
159   }
160 
161   /* ------------------------------------------------------------------------------------------------------------------ */
162 
163   /** Get the {@link DublinCore#PROPERTY_TITLE} property. */
164   @Nonnull public Opt<String> getTitle() {
165     return getFirst(PROPERTY_TITLE);
166   }
167 
168   /** Set the {@link DublinCore#PROPERTY_TITLE} property. */
169   public void setTitle(String title) {
170     set(PROPERTY_TITLE, title);
171   }
172 
173   /** Update the {@link DublinCore#PROPERTY_TITLE} property. */
174   public void updateTitle(Opt<String> title) {
175     update(PROPERTY_TITLE, title);
176   }
177 
178   /** Remove the {@link DublinCore#PROPERTY_TITLE} property. */
179   public void removeTitle() {
180     dc.remove(PROPERTY_TITLE);
181   }
182 
183   /* ------------------------------------------------------------------------------------------------------------------ */
184 
185   /** Get the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
186   @Nonnull public Opt<String> getDescription() {
187     return getFirst(PROPERTY_DESCRIPTION);
188   }
189 
190   /** Set the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
191   public void setDescription(String description) {
192     set(PROPERTY_DESCRIPTION, description);
193   }
194 
195   /** Update the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
196   public void updateDescription(Opt<String> description) {
197     update(PROPERTY_DESCRIPTION, description);
198   }
199 
200   /** Remove the {@link DublinCore#PROPERTY_DESCRIPTION} property. */
201   public void removeDescription() {
202     dc.remove(PROPERTY_DESCRIPTION);
203   }
204 
205   /* ------------------------------------------------------------------------------------------------------------------ */
206 
207   /** Get all {@link DublinCore#PROPERTY_AUDIENCE} properties. */
208   @Nonnull public List<String> getAudiences() {
209     return get(PROPERTY_AUDIENCE);
210   }
211 
212   /** Set multiple {@link DublinCore#PROPERTY_AUDIENCE} properties. */
213   public void setAudiences(List<String> audiences) {
214     set(PROPERTY_AUDIENCE, audiences);
215   }
216 
217   /** Set the {@link DublinCore#PROPERTY_AUDIENCE} property. */
218   public void setAudience(String audience) {
219     set(PROPERTY_AUDIENCE, audience);
220   }
221 
222   /** Add an {@link DublinCore#PROPERTY_AUDIENCE} property. */
223   public void addAudience(String audience) {
224     add(PROPERTY_AUDIENCE, audience);
225   }
226 
227   /** Update the {@link DublinCore#PROPERTY_AUDIENCE} property. */
228   public void updateAudience(Opt<String> audience) {
229     update(PROPERTY_AUDIENCE, audience);
230   }
231 
232   /** Remove all {@link DublinCore#PROPERTY_AUDIENCE} properties. */
233   public void removeAudiences() {
234     dc.remove(PROPERTY_AUDIENCE);
235   }
236 
237   /* ------------------------------------------------------------------------------------------------------------------ */
238 
239   /** Get the {@link DublinCore#PROPERTY_CREATED} property. */
240   @Nonnull public Opt<Temporal> getCreated() {
241     return getFirstVal(PROPERTY_CREATED).map(OpencastMetadataCodec.decodeTemporal);
242   }
243 
244   /** Set the {@link DublinCore#PROPERTY_CREATED} property. The date is encoded with a precision of {@link Precision#Day}. */
245   public void setCreated(Date date) {
246     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
247     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
248     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
249     if (getTemporal().isNone()) {
250       setDate(PROPERTY_CREATED, date, Precision.Day);
251     }
252   }
253 
254   /** Set the {@link DublinCore#PROPERTY_CREATED} property. The date is encoded with a precision of {@link Precision#Day}. */
255   public void setCreated(Temporal t) {
256     // only allow to set a created date, if no start date is set. Otherwise DC created will be changed by changing the
257     // start date with setTemporal. Synchronization is not vice versa, as setting DC created to arbitraty dates might
258     // have unwanted side effects, like setting the wrong recording time, on imported data, or third-party REST calls.
259     if (getTemporal().isNone()) {
260       t.fold(new Match<Unit>() {
261         @Override public Unit period(DCMIPeriod period) {
262           setCreated(period.getStart());
263           return Unit.unit;
264         }
265 
266         @Override public Unit instant(Date instant) {
267           setCreated(instant);
268           return Unit.unit;
269         }
270 
271         @Override public Unit duration(long duration) {
272           return Unit.unit;
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(Opt<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 Opt<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 Opt<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(Opt<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 Opt<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 Opt<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(Opt<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 Opt<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(Opt<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 Opt<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 Opt<Stream<String>> getType() {
482     return getFirst(PROPERTY_TYPE).map(Strings.split("/"));
483   }
484 
485   /** Get the {@link DublinCore#PROPERTY_TYPE} property as a single string. */
486   @Nonnull public Opt<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 Opt<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(Opt<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, Opt<Date> date, Precision p) {
553     for (Date d : date) {
554       setDate(property, d, 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 Opt<String> getFirst(EName property) {
569     return Opt.nul(dc.getFirst(property));
570   }
571 
572   /** Like {@link DublinCore#getFirstVal(EName)} but with the result wrapped in an Opt. */
573   protected Opt<DublinCoreValue> getFirstVal(EName property) {
574     return Opt.nul(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     final List<DublinCoreValue> valuesFiltered = $(values).filter(Strings.isNotBlank).map(mkValue).toList();
585     if (!valuesFiltered.isEmpty()) {
586       dc.remove(property);
587       dc.set(property, valuesFiltered);
588     }
589   }
590 
591   protected void add(EName property, String value) {
592     if (StringUtils.isNotBlank(value)) {
593       dc.add(property, value);
594     }
595   }
596 
597   protected void update(EName property, Opt<String> value) {
598     for (String v : value) {
599       set(property, v);
600     }
601   }
602 
603   private final Fn<String, DublinCoreValue> mkValue = new Fn<String, DublinCoreValue>() {
604     @Override public DublinCoreValue apply(String v) {
605       return DublinCoreValue.mk(v);
606     }
607   };
608 }