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.metadata.dublincore;
23  
24  import org.apache.commons.lang3.StringUtils;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import java.text.SimpleDateFormat;
29  import java.util.ArrayList;
30  import java.util.Map;
31  import java.util.TimeZone;
32  
33  /**
34   * This is a generic and very abstract view of a certain field/property in a metadata catalog. The main purpose of this
35   * class is to have a generic access to the variety of information stored in metadata catalogs.
36   */
37  public class MetadataField {
38  
39    private static final Logger logger = LoggerFactory.getLogger(MetadataField.class);
40  
41    /** Keys for the different values in the configuration file */
42    public static final String CONFIG_COLLECTION_ID_KEY = "collectionID";
43    private static final String CONFIG_PATTERN_KEY = "pattern";
44    private static final String CONFIG_DELIMITER_KEY = "delimiter";
45    public static final String CONFIG_INPUT_ID_KEY = "inputID";
46    public static final String CONFIG_LABEL_KEY = "label";
47    public static final String CONFIG_LIST_PROVIDER_KEY = "listprovider";
48    private static final String CONFIG_NAMESPACE_KEY = "namespace";
49    private static final String CONFIG_ORDER_KEY = "order";
50    private static final String CONFIG_OUTPUT_ID_KEY = "outputID";
51    public static final String CONFIG_PROPERTY_PREFIX = "property";
52    public static final String CONFIG_READ_ONLY_KEY = "readOnly";
53    public static final String CONFIG_REQUIRED_KEY = "required";
54    public static final String CONFIG_TYPE_KEY = "type";
55  
56    /**
57     * Possible types for the metadata field. The types are used in the frontend and backend to know how the metadata
58     * fields should be formatted (if needed).
59     */
60    public enum Type {
61      BOOLEAN, DATE, DURATION, ITERABLE_TEXT, MIXED_TEXT, ORDERED_TEXT, LONG, START_DATE, START_TIME, TEXT, TEXT_LONG
62    }
63  
64    /** The id of a collection to validate values against. */
65    private String collectionID;
66    /** The format to use for temporal date properties. */
67    private String pattern;
68    /** The delimiter used to display and parse list values. */
69    private String delimiter;
70    /** The id of the field used to identify it in the dublin core. */
71    private final String inputID;
72    /** The i18n id for the label to show the property. */
73    private final String label;
74    /** The provider to populate the property with. */
75    private final String listprovider;
76    /** The optional namespace of the field used if a field can be found in more than one namespace */
77    private final String namespace;
78    /**
79     * In the order of properties where this property should be oriented in the UI i.e. 0 means the property should come
80     * first, 1 means it should come second etc.
81     */
82    private final Integer order;
83    /** The optional id of the field used to output for the ui, if not present will assume the same as the inputID. */
84    private final String outputID;
85    /** Whether the property should not be edited. */
86    private boolean readOnly;
87    /** Whether the property is required to update the metadata. */
88    private final boolean required;
89    /** The type of the metadata for example text, date etc. */
90    private Type type;
91  
92    private Object value;
93    private Boolean translatable;
94    private boolean updated = false;
95    private Map<String, String> collection;
96  
97    // this can only be true if the metadata field is representing multiple events with different values
98    private Boolean hasDifferentValues = null;
99  
100   /**
101    * Copy constructor
102    *
103    * @param other
104    *          Other metadata field
105    */
106   public MetadataField(final MetadataField other) {
107     this.inputID = other.inputID;
108     this.outputID = other.outputID;
109     this.label = other.label;
110     this.readOnly = other.readOnly;
111     this.required = other.required;
112     this.value = other.value;
113     this.translatable = other.translatable;
114     this.hasDifferentValues = other.hasDifferentValues;
115     this.type = other.type;
116     this.collection = other.collection;
117     this.collectionID = other.collectionID;
118     this.order = other.order;
119     this.namespace = other.namespace;
120     this.updated = other.updated;
121     this.pattern = other.pattern;
122     this.delimiter = other.delimiter;
123     this.listprovider = other.listprovider;
124   }
125 
126   /**
127    * Metadata field constructor
128    *
129    * @param inputID
130    *          The identifier of the new metadata field
131    * @param label
132    *          the label of the field. The string displayed next to the field value on the frontend. This is usually be a
133    *          translation key
134    * @param readOnly
135    *          Define if the new metadata field can be or not edited
136    * @param required
137    *          Define if the new metadata field is or not required
138    * @param value
139    *          The metadata field value
140    * @param type
141    *          The metadata field type @ EventMetadata.Type}
142    * @param collection
143    *          If the field has a limited list of possible value, the option should contain this one. Otherwise it should
144    *          be none. This is also possible to use the collectionId parameter for that.
145    * @param collectionID
146    *          The id of the limit list of possible value that should be get through the resource endpoint.
147    * @param listprovider An optional list provider ID
148    * @param pattern Pattern for time/date fields
149    * @param delimiter Delimiter
150    * @throws IllegalArgumentException
151    *           if the id, label, type parameters is/are null
152    */
153   public MetadataField(
154           final String inputID,
155           final String outputID,
156           final String label,
157           final boolean readOnly,
158           final boolean required,
159           final Object value,
160           final Boolean translatable,
161           final Type type,
162           final Map<String, String> collection,
163           final String collectionID,
164           final Integer order,
165           final String namespace,
166           final String listprovider,
167           final String pattern,
168           final String delimiter) throws IllegalArgumentException {
169     if (StringUtils.isBlank(inputID)) {
170       throw new IllegalArgumentException("The metadata input id must not be null.");
171     }
172     if (StringUtils.isBlank(label)) {
173       throw new IllegalArgumentException("The metadata label must not be null.");
174     }
175     if (type == null) {
176       throw new IllegalArgumentException("The metadata type must not be null.");
177     }
178     this.inputID = inputID;
179     this.outputID = outputID;
180     this.label = label;
181     this.readOnly = readOnly;
182     this.required = required;
183     this.value = value;
184     this.translatable = translatable;
185     this.type = type;
186     this.collection = collection;
187     this.collectionID = collectionID;
188     this.order = order;
189     this.namespace = namespace;
190     this.listprovider = listprovider;
191     this.pattern = pattern;
192     this.delimiter = delimiter;
193   }
194 
195   /**
196    * Set the option of a limited list of possible values.
197    *
198    * @param collection
199    *          The option of a limited list of possible values
200    */
201   public void setCollection(final Map<String, String> collection) {
202     this.collection = collection;
203   }
204 
205   public Map<String, String> getCollection() {
206     return collection;
207   }
208 
209   public Object getValue() {
210     return value;
211   }
212 
213   public Boolean isTranslatable() {
214     return translatable;
215   }
216 
217   public boolean isUpdated() {
218     return updated;
219   }
220 
221   public void setValue(final Object value) {
222     setValue(value, true);
223   }
224 
225   public void setValue(final Object value, final boolean setUpdated) {
226     this.value = value;
227 
228     if (setUpdated) {
229       this.updated = true;
230     }
231   }
232 
233   public void setIsTranslatable(final Boolean translatable) {
234     this.translatable = translatable;
235   }
236 
237   public static SimpleDateFormat getSimpleDateFormatter(final String pattern) {
238     final SimpleDateFormat dateFormat;
239     if (StringUtils.isNotBlank(pattern)) {
240       dateFormat = new SimpleDateFormat(pattern);
241     } else {
242       dateFormat = new SimpleDateFormat();
243     }
244     dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
245     return dateFormat;
246   }
247 
248   public static MetadataField createMetadataField(final Map<String,String> configuration) {
249     final String inputID = configuration.get(CONFIG_INPUT_ID_KEY);
250     final String label = configuration.get(CONFIG_LABEL_KEY);
251 
252     final String collectionID = configuration.get(CONFIG_COLLECTION_ID_KEY);
253     final String delimiter = configuration.get(CONFIG_DELIMITER_KEY);
254     final String outputID = configuration.get(CONFIG_OUTPUT_ID_KEY);
255     final String listprovider = configuration.get(CONFIG_LIST_PROVIDER_KEY);
256     final String namespace = configuration.get(CONFIG_NAMESPACE_KEY);
257 
258     final Type type = configuration.containsKey(CONFIG_TYPE_KEY)
259             ? Type.valueOf(configuration.get(CONFIG_TYPE_KEY).toUpperCase()) : null;
260     final boolean required = configuration.containsKey(CONFIG_REQUIRED_KEY) && Boolean
261             .parseBoolean(configuration.get(CONFIG_REQUIRED_KEY).toUpperCase());
262     final boolean readOnly = configuration.containsKey(CONFIG_READ_ONLY_KEY) && Boolean
263             .parseBoolean(configuration.get(CONFIG_READ_ONLY_KEY).toUpperCase());
264 
265     final String pattern = configuration.getOrDefault(CONFIG_PATTERN_KEY, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
266 
267     Integer order = null;
268     if (configuration.containsKey(CONFIG_ORDER_KEY)) {
269       try {
270         order = Integer.parseInt(configuration.get(CONFIG_ORDER_KEY));
271       } catch (final NumberFormatException e) {
272         logger.warn("Unable to parse order value {} of metadata field {}", configuration.get(CONFIG_ORDER_KEY),
273                 inputID, e);
274       }
275     }
276 
277     if (type == null) {
278       throw new IllegalArgumentException("type is null");
279     }
280 
281     switch (type) {
282       case BOOLEAN:
283         return new MetadataField(
284                 inputID,
285                 outputID,
286                 label,
287                 readOnly,
288                 required,
289                 null,
290                 null,
291                 type,
292                 null,
293                 null,
294                 order,
295                 namespace,
296                 listprovider,
297                 null,
298                 null);
299       case DATE:
300         return new MetadataField(
301                 inputID,
302                 outputID,
303                 label,
304                 readOnly,
305                 required,
306                 null,
307                 null,
308                 type,
309                 null,
310                 null,
311                 order,
312                 namespace,
313                 listprovider,
314                 StringUtils.isNotBlank(pattern) ? pattern : null,
315                 null);
316       case DURATION:
317       case TEXT:
318       case ORDERED_TEXT:
319       case TEXT_LONG:
320         return new MetadataField(
321                 inputID,
322                 outputID,
323                 label,
324                 readOnly,
325                 required,
326                 "",
327                 null,
328                 type,
329                 null,
330                 collectionID,
331                 order,
332                 namespace,
333                 listprovider,
334                 null,
335                 null);
336       case ITERABLE_TEXT:
337       case MIXED_TEXT:
338         return new MetadataField(
339                 inputID,
340                 outputID,
341                 label,
342                 readOnly,
343                 required,
344                 new ArrayList<>(),
345                 null,
346                 type,
347                 null,
348                 collectionID,
349                 order,
350                 namespace,
351                 listprovider,
352                 null,
353                 delimiter);
354       case LONG:
355         return new MetadataField(
356                 inputID,
357                 outputID,
358                 label,
359                 readOnly,
360                 required,
361                 0L,
362                 null,
363                 Type.LONG,
364                 null,
365                 collectionID,
366                 order,
367                 namespace,
368                 listprovider,
369                 null, null);
370       case START_DATE:
371       case START_TIME:
372         if (StringUtils.isBlank(pattern)) {
373           throw new IllegalArgumentException(
374                   "For temporal metadata field " + inputID + " of type " + type + " there needs to be a pattern.");
375         }
376 
377         return new MetadataField(
378                 inputID,
379                 outputID,
380                 label,
381                 readOnly,
382                 required,
383                 null,
384                 null,
385                 type,
386                 null,
387                 null,
388                 order,
389                 namespace,
390                 listprovider,
391                 pattern,
392                 null);
393       default:
394         throw new IllegalArgumentException("Unknown metadata type! " + type);
395     }
396   }
397 
398   public String getCollectionID() {
399     return collectionID;
400   }
401 
402   public void setCollectionID(final String collectionID) {
403     this.collectionID = collectionID;
404   }
405 
406   public String getInputID() {
407     return inputID;
408   }
409 
410   public String getLabel() {
411     return label;
412   }
413 
414   public String getListprovider() {
415     return listprovider;
416   }
417 
418   public String getNamespace() {
419     return namespace;
420   }
421 
422   public Integer getOrder() {
423     return order;
424   }
425 
426   /**
427    * @return The outputID if available, inputID if it is missing.
428    */
429   public String getOutputID() {
430     if (outputID != null) {
431       return outputID;
432     }
433     return inputID;
434   }
435 
436   public String getPattern() {
437     return pattern;
438   }
439 
440   public void setPattern(final String pattern) {
441     this.pattern = pattern;
442   }
443 
444   public String getDelimiter() {
445     return delimiter;
446   }
447 
448   public void setDelimiter(final String delimiter) {
449     this.delimiter = delimiter;
450   }
451 
452   public void setReadOnly(final boolean readOnly) {
453     this.readOnly = readOnly;
454   }
455 
456   public boolean isReadOnly() {
457     return readOnly;
458   }
459 
460   public boolean isRequired() {
461     return required;
462   }
463 
464   public void setUpdated(final boolean updated) {
465     this.updated = updated;
466   }
467 
468   public Type getType() {
469     return type;
470   }
471 
472   public void setType(final Type type) {
473     this.type = type;
474   }
475 
476   public void setDifferentValues() {
477     value = null;
478     hasDifferentValues = true;
479   }
480 
481   public Boolean hasDifferentValues() {
482     return hasDifferentValues;
483   }
484 
485   public MetadataField readOnlyCopy() {
486     final MetadataField metadataField = new MetadataField(this);
487     metadataField.setReadOnly(true);
488     return metadataField;
489   }
490 }