View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.datamodel.metadata;
20  
21  import java.util.ArrayList;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.function.Predicate;
26  import java.util.stream.Collectors;
27  import java.util.stream.Stream;
28  import java.util.stream.StreamSupport;
29  
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.jdom2.Element;
33  import org.mycore.common.MCRException;
34  import org.mycore.common.config.MCRConfiguration2;
35  import org.mycore.common.config.MCRConfigurationException;
36  
37  import com.google.gson.JsonObject;
38  
39  /**
40   * This class implements all methode for handling one object metadata part. This
41   * class uses only metadata type classes of the general datamodel code of
42   * MyCoRe.
43   * 
44   * @author Jens Kupferschmidt
45   * @author Mathias Hegner
46   * @version $Revision$ $Date$
47   */
48  public class MCRObjectMetadata implements Iterable<MCRMetaElement> {
49      private static final Logger LOGGER = LogManager.getLogger();
50  
51      // common data
52      private boolean heritedXML;
53  
54      // metadata list
55      private final ArrayList<MCRMetaElement> metadataElements;
56  
57      /**
58       * This is the constructor of the MCRObjectMetadata class. It set the
59       * default language for all metadata to the value from the configuration
60       * propertie <em>MCR.Metadata.DefaultLang</em>.
61       * 
62       * @exception MCRConfigurationException
63       *                a special exception for configuartion data
64       */
65      public MCRObjectMetadata() throws MCRConfigurationException {
66          heritedXML = MCRConfiguration2.getBoolean("MCR.Metadata.HeritedForXML").orElse(true);
67          metadataElements = new ArrayList<>();
68      }
69  
70      /**
71       * <em>size</em> returns the number of tag names in the ArrayList.
72       * 
73       * @return int number of tags and meta elements
74       */
75      public int size() {
76          return metadataElements.size();
77      }
78  
79      /**
80       * <em>getHeritableMetadata</em> returns an instance of MCRObjectMetadata
81       * containing all the heritable MetaElement's of this object.
82       * 
83       * @return MCRObjectMetadata the heritable part of this MCRObjectMetadata
84       */
85      public final MCRObjectMetadata getHeritableMetadata() {
86          MCRObjectMetadata heritableMetadata = new MCRObjectMetadata();
87          stream().filter(MCRMetaElement::isHeritable).map(MCRMetaElement::clone).forEach(metaElement -> {
88              metaElement.stream().forEach(MCRMetaInterface::incrementInherited);
89              heritableMetadata.setMetadataElement(metaElement);
90          });
91          return heritableMetadata;
92      }
93  
94      /**
95       * <em>removeInheritedMetadata</em> removes all inherited metadata elements  
96       */
97      public final void removeInheritedMetadata() {
98          Iterator<MCRMetaElement> elements = metadataElements.iterator();
99          while (elements.hasNext()) {
100             MCRMetaElement me = elements.next();
101             me.removeInheritedMetadata();
102             //remove meta element if empty (else isValid() will fail)
103             if (me.size() == 0) {
104                 elements.remove();
105             }
106         }
107     }
108 
109     /**
110      * This method append MCRMetaElement's from a given MCRObjectMetadata to
111      * this data set.
112      * 
113      * @param input
114      *            the MCRObjectMetadata, that should merged into this data set
115      */
116     public final void appendMetadata(MCRObjectMetadata input) {
117 
118         for (MCRMetaElement newelm : input) {
119             int pos = -1;
120 
121             for (int j = 0; j < size(); j++) {
122                 if (metadataElements.get(j)
123                     .getTag()
124                     .equals(newelm.getTag())) {
125                     pos = j;
126                 }
127             }
128 
129             if (pos != -1) {
130                 if (!metadataElements.get(pos)
131                     .inheritsNot()) {
132                     metadataElements.get(pos)
133                         .setHeritable(true);
134 
135                     for (int j = 0; j < newelm.size(); j++) {
136                         MCRMetaInterface obj = newelm.getElement(j);
137                         metadataElements.get(pos)
138                             .addMetaObject(obj);
139                     }
140                 }
141             } else {
142                 metadataElements.add(newelm);
143             }
144         }
145     }
146 
147     /**
148      * This method return the MCRMetaElement selected by tag. If this was not
149      * found, null was returned.
150      * 
151      * @param tag
152      *            the element tag
153      * @return the MCRMetaElement for the tag
154      */
155     public final MCRMetaElement getMetadataElement(String tag) {
156         for (MCRMetaElement sub : this) {
157             if (sub.getTag().equals(tag)) {
158                 return sub;
159             }
160         }
161         return null;
162     }
163 
164     /**
165      * This method return the MCRMetaElement selected by an index. If this was
166      * not found, null was returned.
167      * 
168      * @param index
169      *            the element index
170      * @return the MCRMetaElement for the index
171      */
172     public final MCRMetaElement getMetadataElement(int index) {
173         return metadataElements.get(index);
174     }
175 
176     /**
177      * sets the given MCRMetaElement to the list. If the tag exists
178      * the MCRMetaElement was replaced.
179      * 
180      * @param obj
181      *            the MCRMetaElement object
182      */
183     public final void setMetadataElement(MCRMetaElement obj) {
184         MCRMetaElement old = getMetadataElement(obj.getTag());
185         if (old == null) {
186             metadataElements.add(obj);
187             return;
188         }
189         int i = metadataElements.indexOf(old);
190         metadataElements.remove(i);
191         metadataElements.add(i, obj);
192     }
193 
194     /**
195      * Removes the given element.
196      *
197      * @param element the meta element to remove
198      * @return true if the element was removed
199      */
200     public final boolean removeMetadataElement(MCRMetaElement element) {
201         return metadataElements.remove(element);
202     }
203 
204     /**
205      * This method remove the MCRMetaElement selected by tag from the list.
206      * 
207      * @return true if set was successful, otherwise false
208      */
209     public final MCRMetaElement removeMetadataElement(String tag) {
210         MCRMetaElement old = getMetadataElement(tag);
211         if (old != null) {
212             removeMetadataElement(old);
213             return old;
214         }
215         return null;
216     }
217 
218     /**
219      * This method remove the MCRMetaElement selected a index from the list.
220      * 
221      * @return true if set was successful, otherwise false
222      */
223     public final MCRMetaElement removeMetadataElement(int index) {
224         if (index < 0 || index > size()) {
225             return null;
226         }
227 
228         return metadataElements.remove(index);
229     }
230 
231     /**
232      * Finds the first, not inherited {@link MCRMetaInterface} with the given tag.
233      * 
234      * @param tag the metadata tag e.g. 'maintitles'
235      * @return an optional of the first meta interface
236      */
237     public final <T extends MCRMetaInterface> Optional<T> findFirst(String tag) {
238         return findFirst(tag, null, 0);
239     }
240 
241     /**
242      * Finds the first, not inherited {@link MCRMetaInterface} with the given tag
243      * where the @type attribute is equal to the given type. If the type is null,
244      * this method doesn't care if the @type attribute is set or not.
245      * 
246      * @param tag the metadata tag e.g. 'subtitles'
247      * @param type the @type attribute which have to match
248      * @return an optional of the first meta interface
249      */
250     public final <T extends MCRMetaInterface> Optional<T> findFirst(String tag, String type) {
251         return findFirst(tag, type, 0);
252     }
253 
254     /**
255      * Finds the first {@link MCRMetaInterface} with the given tag where the
256      * inheritance level is equal the inherited value.
257      * 
258      * @param tag the metadata tag e.g. 'maintitles'
259      * @param inherited level of inheritance. Zero is the current level,
260      *        parent is one and so on.
261      * @return an optional of the first meta interface
262      */
263     public final <T extends MCRMetaInterface> Optional<T> findFirst(String tag, Integer inherited) {
264         return findFirst(tag, null, inherited);
265     }
266 
267     /**
268      * Finds the first {@link MCRMetaInterface} with the given tag where the
269      * inheritance level is equal the inherited value and the @type attribute
270      * is equal to the given type. If the type is null, this method doesn't
271      * care if the @type attribute is set or not.
272      * 
273      * @param tag the metadata tag e.g. 'subtitles'
274      * @param type the @type attribute which have to match
275      * @param inherited level of inheritance. Zero is the current level,
276      *        parent is one and so on.
277      * @return an optional of the first meta interface
278      */
279     public final <T extends MCRMetaInterface> Optional<T> findFirst(String tag, String type, Integer inherited) {
280         Stream<T> stream = stream(tag);
281         return stream.filter(filterByType(type))
282             .filter(filterByInherited(inherited))
283             .findFirst();
284     }
285 
286     /**
287      * Streams the {@link MCRMetaElement}'s.
288      *
289      * @return stream of MCRMetaElement's
290      */
291     public final Stream<MCRMetaElement> stream() {
292         return metadataElements.stream();
293     }
294 
295     /**
296      * Streams the {@link MCRMetaInterface}s of the given tag.
297      * <pre>
298      * {@code
299      *   Stream<MCRMetaLangText> stream = mcrObjectMetadata.stream("maintitles");
300      * }
301      * </pre>
302      * 
303      * @param tag tag the metadata tag e.g. 'maintitles'
304      * @return a stream of the requested meta interfaces
305      */
306     public final <T extends MCRMetaInterface> Stream<T> stream(String tag) {
307         Optional<MCRMetaElement> metadata = Optional.ofNullable(getMetadataElement(tag));
308         // waiting for https://bugs.openjdk.java.net/browse/JDK-8050820
309         return metadata.map(
310             mcrMetaInterfaces -> StreamSupport.stream(mcrMetaInterfaces.spliterator(), false).map(metaInterface -> {
311                 @SuppressWarnings("unchecked")
312                 T t = (T) metaInterface;
313                 return t;
314             })).orElseGet(Stream::empty);
315     }
316 
317     /**
318     * Lists the {@link MCRMetaInterface}s of the given tag. This is not a
319     * live list. Removals or adds are not reflected on the
320     * {@link MCRMetaElement}. Use {@link #getMetadataElement(String)}
321     * for those operations.
322     * 
323     * <pre>
324     * {@code
325     *   List<MCRMetaLangText> list = mcrObjectMetadata.list("maintitles");
326     * }
327     * </pre>
328     * 
329     * @param tag tag the metadata tag e.g. 'maintitles'
330     * @return a list of the requested meta interfaces
331     */
332     public final <T extends MCRMetaInterface> List<T> list(String tag) {
333         Stream<T> stream = stream(tag);
334         return stream.collect(Collectors.toList());
335     }
336 
337     /**
338      * Checks if the type of an {@link MCRMetaInterface} is equal
339      * the given type. If the given type is null, true is returned.
340      * 
341      * @param type the type to compare
342      * @return true if the types are equal
343      */
344     private Predicate<MCRMetaInterface> filterByType(String type) {
345         return (metaInterface) -> type == null || type.equals(metaInterface.getType());
346     }
347 
348     /**
349      * Checks if the inheritance level of an {@link MCRMetaInterface}
350      * is equal the given inherited value.
351      * 
352      * @param inherited the inherited value
353      * @return true if the inherited values are equal
354      */
355     private Predicate<MCRMetaInterface> filterByInherited(Integer inherited) {
356         return (metaInterface) -> metaInterface.getInherited() == inherited;
357     }
358 
359     /**
360      * This methode read the XML input stream part from a DOM part for the
361      * metadata of the document.
362      * 
363      * @param element
364      *            a list of relevant DOM elements for the metadata
365      * @exception MCRException
366      *                if a problem occured
367      */
368     public final void setFromDOM(Element element) throws MCRException {
369         List<Element> elements = element.getChildren();
370         metadataElements.clear();
371         for (Element sub : elements) {
372             MCRMetaElement obj = new MCRMetaElement();
373             obj.setFromDOM(sub);
374             metadataElements.add(obj);
375         }
376     }
377 
378     /**
379      * This methode create a XML stream for all metadata.
380      * 
381      * @exception MCRException
382      *                if the content of this class is not valid
383      * @return a JDOM Element with the XML data of the metadata part
384      */
385     public final Element createXML() throws MCRException {
386         try {
387             validate();
388         } catch (MCRException exc) {
389             throw new MCRException("MCRObjectMetadata : The content is not valid.", exc);
390         }
391         Element elm = new Element("metadata");
392         for (MCRMetaElement e : this) {
393             elm.addContent(e.createXML(heritedXML));
394         }
395         return elm;
396     }
397 
398     /**
399      * Creates the JSON representation of this metadata container.
400      * 
401      * <pre>
402      *   {
403      *    "def.maintitles": {
404      *      {@link MCRMetaLangText#createJSON()}
405      *    }
406      *    "def.dates": {
407      *      {@link MCRMetaISO8601Date#createJSON()}
408      *    }
409      *    ...
410      *   }
411      * </pre>
412      * 
413      * @return a json gson representation of this metadata container
414      */
415     public JsonObject createJSON() {
416         JsonObject metadata = new JsonObject();
417         StreamSupport.stream(spliterator(), true)
418             .forEach(metaElement -> metadata.add(metaElement.getTag(), metaElement.createJSON(heritedXML)));
419         return metadata;
420     }
421 
422     /**
423      * This methode check the validation of the content of this class. The
424      * methode returns <em>true</em> if
425      * <ul>
426      * <li>the array is empty
427      * <li>the default lang value was supported
428      * </ul>
429      * otherwise the methode return <em>false</em>
430      * 
431      * @return a boolean value
432      */
433     public final boolean isValid() {
434         try {
435             validate();
436             return true;
437         } catch (MCRException exc) {
438             LOGGER.warn("The <metadata> element is invalid.", exc);
439         }
440         return false;
441     }
442 
443     /**
444      * Validates this MCRObjectMetadata. This method throws an exception if:
445      * <ul>
446      * <li>one of the MCRMetaElement children is invalid</li>
447      * </ul>
448      * 
449      * @throws MCRException the MCRObjectMetadata is invalid
450      */
451     public void validate() throws MCRException {
452         for (MCRMetaElement e : this) {
453             try {
454                 e.validate();
455             } catch (Exception exc) {
456                 throw new MCRException("The <metadata> element is invalid because '" + e.getTag() + "' is invalid.",
457                     exc);
458             }
459         }
460     }
461 
462     /**
463      * This method put debug data to the logger (for the debug mode).
464      */
465     public final void debug() {
466         if (LOGGER.isDebugEnabled()) {
467             for (MCRMetaElement sub : this) {
468                 sub.debug();
469             }
470         }
471     }
472 
473     @Override
474     public Iterator<MCRMetaElement> iterator() {
475         return metadataElements.iterator();
476     }
477 
478 }