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     if (StringUtils.isBlank(label))
172       throw new IllegalArgumentException("The metadata label must not be null.");
173     if (type == null)
174       throw new IllegalArgumentException("The metadata type must not be null.");
175     this.inputID = inputID;
176     this.outputID = outputID;
177     this.label = label;
178     this.readOnly = readOnly;
179     this.required = required;
180     this.value = value;
181     this.translatable = translatable;
182     this.type = type;
183     this.collection = collection;
184     this.collectionID = collectionID;
185     this.order = order;
186     this.namespace = namespace;
187     this.listprovider = listprovider;
188     this.pattern = pattern;
189     this.delimiter = delimiter;
190   }
191 
192   /**
193    * Set the option of a limited list of possible values.
194    *
195    * @param collection
196    *          The option of a limited list of possible values
197    */
198   public void setCollection(final Map<String, String> collection) {
199     this.collection = collection;
200   }
201 
202   public Map<String, String> getCollection() {
203     return collection;
204   }
205 
206   public Object getValue() {
207     return value;
208   }
209 
210   public Boolean isTranslatable() {
211     return translatable;
212   }
213 
214   public boolean isUpdated() {
215     return updated;
216   }
217 
218   public void setValue(final Object value) {
219     setValue(value, true);
220   }
221 
222   public void setValue(final Object value, final boolean setUpdated) {
223     this.value = value;
224 
225     if (setUpdated) {
226       this.updated = true;
227     }
228   }
229 
230   public void setIsTranslatable(final Boolean translatable) {
231     this.translatable = translatable;
232   }
233 
234   public static SimpleDateFormat getSimpleDateFormatter(final String pattern) {
235     final SimpleDateFormat dateFormat;
236     if (StringUtils.isNotBlank(pattern)) {
237       dateFormat = new SimpleDateFormat(pattern);
238     } else {
239       dateFormat = new SimpleDateFormat();
240     }
241     dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
242     return dateFormat;
243   }
244 
245   public static MetadataField createMetadataField(final Map<String,String> configuration) {
246     final String inputID = configuration.get(CONFIG_INPUT_ID_KEY);
247     final String label = configuration.get(CONFIG_LABEL_KEY);
248 
249     final String collectionID = configuration.get(CONFIG_COLLECTION_ID_KEY);
250     final String delimiter = configuration.get(CONFIG_DELIMITER_KEY);
251     final String outputID = configuration.get(CONFIG_OUTPUT_ID_KEY);
252     final String listprovider = configuration.get(CONFIG_LIST_PROVIDER_KEY);
253     final String namespace = configuration.get(CONFIG_NAMESPACE_KEY);
254 
255     final Type type = configuration.containsKey(CONFIG_TYPE_KEY)
256             ? Type.valueOf(configuration.get(CONFIG_TYPE_KEY).toUpperCase()) : null;
257     final boolean required = configuration.containsKey(CONFIG_REQUIRED_KEY) && Boolean
258             .parseBoolean(configuration.get(CONFIG_REQUIRED_KEY).toUpperCase());
259     final boolean readOnly = configuration.containsKey(CONFIG_READ_ONLY_KEY) && Boolean
260             .parseBoolean(configuration.get(CONFIG_READ_ONLY_KEY).toUpperCase());
261 
262     final String pattern = configuration.getOrDefault(CONFIG_PATTERN_KEY, "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
263 
264     Integer order = null;
265     if (configuration.containsKey(CONFIG_ORDER_KEY)) {
266       try {
267         order = Integer.parseInt(configuration.get(CONFIG_ORDER_KEY));
268       } catch (final NumberFormatException e) {
269         logger.warn("Unable to parse order value {} of metadata field {}", configuration.get(CONFIG_ORDER_KEY),
270                 inputID, e);
271       }
272     }
273 
274     if (type == null)
275       throw new IllegalArgumentException("type is null");
276 
277     switch (type) {
278       case BOOLEAN:
279         return new MetadataField(
280                 inputID,
281                 outputID,
282                 label,
283                 readOnly,
284                 required,
285                 null,
286                 null,
287                 type,
288                 null,
289                 null,
290                 order,
291                 namespace,
292                 listprovider,
293                 null,
294                 null);
295       case DATE:
296         return new MetadataField(
297                 inputID,
298                 outputID,
299                 label,
300                 readOnly,
301                 required,
302                 null,
303                 null,
304                 type,
305                 null,
306                 null,
307                 order,
308                 namespace,
309                 listprovider,
310                 StringUtils.isNotBlank(pattern) ? pattern : null,
311                 null);
312       case DURATION:
313       case TEXT:
314       case ORDERED_TEXT:
315       case TEXT_LONG:
316         return new MetadataField(
317                 inputID,
318                 outputID,
319                 label,
320                 readOnly,
321                 required,
322                 "",
323                 null,
324                 type,
325                 null,
326                 collectionID,
327                 order,
328                 namespace,
329                 listprovider,
330                 null,
331                 null);
332       case ITERABLE_TEXT:
333       case MIXED_TEXT:
334         return new MetadataField(
335                 inputID,
336                 outputID,
337                 label,
338                 readOnly,
339                 required,
340                 new ArrayList<>(),
341                 null,
342                 type,
343                 null,
344                 collectionID,
345                 order,
346                 namespace,
347                 listprovider,
348                 null,
349                 delimiter);
350       case LONG:
351         return new MetadataField(
352                 inputID,
353                 outputID,
354                 label,
355                 readOnly,
356                 required,
357                 0L,
358                 null,
359                 Type.LONG,
360                 null,
361                 collectionID,
362                 order,
363                 namespace,
364                 listprovider,
365                 null, null);
366       case START_DATE:
367       case START_TIME:
368         if (StringUtils.isBlank(pattern)) {
369           throw new IllegalArgumentException(
370                   "For temporal metadata field " + inputID + " of type " + type + " there needs to be a pattern.");
371         }
372 
373         return new MetadataField(
374                 inputID,
375                 outputID,
376                 label,
377                 readOnly,
378                 required,
379                 null,
380                 null,
381                 type,
382                 null,
383                 null,
384                 order,
385                 namespace,
386                 listprovider,
387                 pattern,
388                 null);
389       default:
390         throw new IllegalArgumentException("Unknown metadata type! " + type);
391     }
392   }
393 
394   public String getCollectionID() {
395     return collectionID;
396   }
397 
398   public void setCollectionID(final String collectionID) {
399     this.collectionID = collectionID;
400   }
401 
402   public String getInputID() {
403     return inputID;
404   }
405 
406   public String getLabel() {
407     return label;
408   }
409 
410   public String getListprovider() {
411     return listprovider;
412   }
413 
414   public String getNamespace() {
415     return namespace;
416   }
417 
418   public Integer getOrder() {
419     return order;
420   }
421 
422   /**
423    * @return The outputID if available, inputID if it is missing.
424    */
425   public String getOutputID() {
426     if (outputID != null) {
427       return outputID;
428     }
429     return inputID;
430   }
431 
432   public String getPattern() {
433     return pattern;
434   }
435 
436   public void setPattern(final String pattern) {
437     this.pattern = pattern;
438   }
439 
440   public String getDelimiter() {
441     return delimiter;
442   }
443 
444   public void setDelimiter(final String delimiter) {
445     this.delimiter = delimiter;
446   }
447 
448   public void setReadOnly(final boolean readOnly) {
449     this.readOnly = readOnly;
450   }
451 
452   public boolean isReadOnly() {
453     return readOnly;
454   }
455 
456   public boolean isRequired() {
457     return required;
458   }
459 
460   public void setUpdated(final boolean updated) {
461     this.updated = updated;
462   }
463 
464   public Type getType() {
465     return type;
466   }
467 
468   public void setType(final Type type) {
469     this.type = type;
470   }
471 
472   public void setDifferentValues() {
473     value = null;
474     hasDifferentValues = true;
475   }
476 
477   public Boolean hasDifferentValues() {
478     return hasDifferentValues;
479   }
480 
481   public MetadataField readOnlyCopy() {
482     final MetadataField metadataField = new MetadataField(this);
483     metadataField.setReadOnly(true);
484     return metadataField;
485   }
486 }