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