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.opencastproject.list.api.ListProviderException;
25  import org.opencastproject.list.api.ListProvidersService;
26  import org.opencastproject.list.api.ResourceListQuery;
27  import org.opencastproject.list.impl.ResourceListQueryImpl;
28  import org.opencastproject.util.data.Option;
29  
30  import com.google.common.collect.Iterables;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.slf4j.Logger;
34  import org.slf4j.LoggerFactory;
35  
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collections;
39  import java.util.Comparator;
40  import java.util.HashMap;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Optional;
44  import java.util.stream.Collectors;
45  
46  public class DublinCoreMetadataCollection {
47    private static final Logger logger = LoggerFactory.getLogger(DublinCoreMetadataCollection.class);
48  
49    /** The list containing all the metadata */
50    private List<MetadataField> fieldsInOrder = new ArrayList<>();
51    private final Map<String, MetadataField> outputFields = new HashMap<>();
52  
53    public DublinCoreMetadataCollection() {
54      this(Collections.emptyList());
55    }
56  
57    public DublinCoreMetadataCollection(final Iterable<MetadataField> fields) {
58      for (final MetadataField field : fields) {
59        addField(field);
60      }
61    }
62  
63    public DublinCoreMetadataCollection(final DublinCoreMetadataCollection c) {
64      this(c.fieldsInOrder);
65    }
66  
67    public DublinCoreMetadataCollection readOnlyCopy() {
68      return new DublinCoreMetadataCollection(this.fieldsInOrder.stream().map(MetadataField::readOnlyCopy)
69              .collect(Collectors.toList()));
70    }
71  
72    public Map<String, MetadataField> getOutputFields() {
73      return outputFields;
74    }
75  
76    public void addField(final MetadataField metadata) {
77      if (metadata == null)
78        throw new IllegalArgumentException("The metadata must not be null.");
79      addFieldInOrder(metadata);
80      this.outputFields.put(metadata.getOutputID(), metadata);
81    }
82  
83    /**
84     * Adds a field in ui order to the collection. If no order is specified it will be added to the end.
85     *
86     * @param metadata
87     *          The metadata to add to the collection.
88     */
89    private void addFieldInOrder(final MetadataField metadata) {
90      removeFieldIfExists(metadata);
91  
92      // Find all of the ordered or unordered elements.
93      final ArrayList<MetadataField> orderedFields = new ArrayList<>();
94      final ArrayList<MetadataField> unorderedFields = new ArrayList<>();
95      for (final MetadataField field : fieldsInOrder) {
96        if (field.getOrder() != null) {
97          orderedFields.add(field);
98        } else {
99          unorderedFields.add(field);
100       }
101     }
102 
103     // Add the new field to either the ordered fields or the unordered fields.
104     if (metadata.getOrder() != null) {
105       orderedFields.add(metadata);
106     } else {
107       unorderedFields.add(metadata);
108     }
109 
110     // Sort the ordered elements so that early entries don't push later entries to the right
111     orderedFields.sort(Comparator.comparingInt(MetadataField::getOrder));
112 
113     // Add all the non-ordered elements to the collection
114     fieldsInOrder = new ArrayList<>(unorderedFields);
115 
116     // Add all of the fields that have an index to their location starting at the lowest value.
117     for (final MetadataField orderedField : orderedFields) {
118       final int index = orderedField.getOrder() < fieldsInOrder.size() ? orderedField.getOrder()
119               : fieldsInOrder.size();
120       fieldsInOrder.add(index, orderedField);
121     }
122   }
123 
124   public void addEmptyField(final MetadataField metadataField, final ListProvidersService listProvidersService) {
125     addField(metadataField, Collections.emptyList(), Optional.empty(), listProvidersService);
126   }
127 
128   public void addField(final MetadataField metadataField, final String value, final ListProvidersService
129           listProvidersService) {
130     addField(metadataField, Collections.singletonList(value), Optional.empty(), listProvidersService);
131   }
132 
133   public void addField(final MetadataField metadataField, final Optional<String> valueOpt, final
134     Optional<ResourceListQuery> collectionQueryOverrideOpt, final ListProvidersService listProvidersService) {
135     if (valueOpt.isPresent()) {
136       addField(metadataField, Collections.singletonList(valueOpt.get()), collectionQueryOverrideOpt,
137               listProvidersService);
138     } else {
139       addField(metadataField, Collections.emptyList(), collectionQueryOverrideOpt, listProvidersService);
140     }
141   }
142 
143   /**
144    * Set value to a metadata field of unknown type
145    */
146   private static void setValueFromDCCatalog(
147           final List<String> filteredValues,
148           final MetadataField metadataField) {
149     if (filteredValues.isEmpty()) {
150       throw new IllegalArgumentException("Values cannot be empty");
151     }
152 
153     if (filteredValues.size() > 1
154             && metadataField.getType() != MetadataField.Type.MIXED_TEXT
155             && metadataField.getType() != MetadataField.Type.ITERABLE_TEXT) {
156       logger.warn("Cannot put multiple values into a single-value field, only the last value is used. {}",
157               Arrays.toString(filteredValues.toArray()));
158     }
159 
160     switch (metadataField.getType()) {
161       case BOOLEAN:
162         metadataField.setValue(Boolean.parseBoolean(Iterables.getLast(filteredValues)), false);
163         break;
164       case DATE:
165         if (metadataField.getPattern() == null) {
166           metadataField.setPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
167         }
168         metadataField.setValue(EncodingSchemeUtils.decodeDate(Iterables.getLast(filteredValues)), false);
169         break;
170       case DURATION:
171         final String value = Iterables.getLast(filteredValues);
172         final DCMIPeriod period = EncodingSchemeUtils.decodePeriod(value);
173         if (period == null)
174           throw new IllegalArgumentException("period couldn't be parsed: " + value);
175         final long longValue = period.getEnd().getTime() - period.getStart().getTime();
176         metadataField.setValue(Long.toString(longValue), false);
177         break;
178       case ITERABLE_TEXT:
179       case MIXED_TEXT:
180         metadataField.setValue(filteredValues, false);
181         break;
182       case LONG:
183         metadataField.setValue(Long.parseLong(Iterables.getLast(filteredValues)), false);
184         break;
185       case START_DATE:
186         if (metadataField.getPattern() == null) {
187           metadataField.setPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
188         }
189         metadataField.setValue(Iterables.getLast(filteredValues), false);
190         break;
191       case TEXT:
192       case ORDERED_TEXT:
193       case TEXT_LONG:
194         metadataField.setValue(Iterables.getLast(filteredValues), false);
195         break;
196       default:
197         throw new IllegalArgumentException("Unknown metadata type! " + metadataField.getType());
198     }
199   }
200 
201   public void addField(final MetadataField metadataField, final List<String> values, final ListProvidersService
202           listProvidersService) {
203     addField(metadataField, values, Optional.empty(), listProvidersService);
204   }
205 
206   public void addField(final MetadataField metadataField, final List<String> values, final Optional<ResourceListQuery>
207           collectionQueryOverrideOpt, final ListProvidersService listProvidersService) {
208     final List<String> filteredValues = values.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
209 
210     if (!filteredValues.isEmpty()) {
211       setValueFromDCCatalog(filteredValues, metadataField);
212     }
213 
214     metadataField.setIsTranslatable(getCollectionIsTranslatable(metadataField, listProvidersService));
215     metadataField.setCollection(getCollection(metadataField, listProvidersService, collectionQueryOverrideOpt));
216 
217     addField(metadataField);
218   }
219 
220   private static Boolean getCollectionIsTranslatable(
221           final MetadataField metadataField,
222           final ListProvidersService listProvidersService) {
223     if (listProvidersService != null && metadataField.getListprovider() != null) {
224       try {
225         return listProvidersService.isTranslatable(metadataField.getListprovider());
226       } catch (final ListProviderException ex) {
227         // failed to get is-translatable property on list-provider-service
228         // as this field is optional, it is fine to pass here
229       }
230     }
231     return null;
232   }
233 
234   private static Map<String, String> getCollection(final MetadataField metadataField, final ListProvidersService
235           listProvidersService, final Optional<ResourceListQuery> collectionQueryOverrideOpt) {
236     try {
237       if (listProvidersService != null && metadataField.getListprovider() != null) {
238 
239         // use collection query override?
240         ResourceListQuery resourceListQuery;
241         if (collectionQueryOverrideOpt.isPresent()) {
242           resourceListQuery = collectionQueryOverrideOpt.get();
243 
244           // shortcut: don't query list provider if limit is set to 0
245           Option<Integer> limit = resourceListQuery.getLimit();
246           if (limit.isSome() && limit.get() == 0) {
247             return Collections.emptyMap();
248           }
249         } else {
250           resourceListQuery = new ResourceListQueryImpl();
251         }
252 
253         return listProvidersService.getList(metadataField.getListprovider(), resourceListQuery, true);
254       }
255       return null;
256     } catch (final ListProviderException e) {
257       logger.warn("Unable to set collection on metadata because", e);
258       return null;
259     }
260   }
261 
262   /**
263    * Removes a {@link MetadataField} if it already exists in the ordered collection.
264    *
265    * @param metadata
266    *          The field to remove.
267    */
268   private void removeFieldIfExists(final MetadataField metadata) {
269     int index = -1;
270     for (final MetadataField field : fieldsInOrder) {
271       if (field.getInputID().equalsIgnoreCase(metadata.getInputID())
272               && field.getOutputID().equalsIgnoreCase(metadata.getOutputID())) {
273         index = fieldsInOrder.indexOf(field);
274       }
275     }
276 
277     if (index >= 0) {
278       fieldsInOrder.remove(index);
279     }
280   }
281 
282   public void removeField(final MetadataField metadata) {
283     if (metadata == null)
284       throw new IllegalArgumentException("The metadata must not be null.");
285     this.fieldsInOrder.remove(metadata);
286     this.outputFields.remove(metadata.getOutputID());
287   }
288 
289   public List<MetadataField> getFields() {
290     return this.fieldsInOrder;
291   }
292 
293   public void updateStringField(final MetadataField current, final String value) {
294     if (current.getValue() != null && !(current.getValue() instanceof String)) {
295       throw new IllegalArgumentException("Unable to update a field to a different type than String with this method!");
296     }
297     removeField(current);
298     final MetadataField field = new MetadataField(current);
299     field.setValue(value);
300     addField(field);
301   }
302 
303   public boolean isUpdated() {
304     for (final MetadataField field : fieldsInOrder) {
305       if (field.isUpdated()) {
306         return true;
307       }
308     }
309     return false;
310   }
311 
312 }