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.Comparator;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.stream.Collectors;
26  
27  import org.apache.logging.log4j.LogManager;
28  import org.apache.logging.log4j.Logger;
29  import org.jdom2.Element;
30  import org.mycore.common.MCRException;
31  
32  import com.google.gson.JsonArray;
33  import com.google.gson.JsonObject;
34  
35  /**
36   * This class implements code for the inheritance of metadata of linked objects
37   * and the linking of derivates onto an MCRObject. These links are described by
38   * the <em>MCRMetaLink</em> class. For links to another object, there are
39   * "locators" in use only, and the href variable gives the ID of the linked
40   * object, while the label and title attributes can be used freely. Subtag name = "
41   * &lt;child&gt;" means a child link from a "parent" object (collected in the
42   * "children" and "parents" section of the "structure" part, respectively). The
43   * child inherits all heritable metadata of the parent. If the parent itself is
44   * a child of another parent, the heritable metadata of this "grand parent" is
45   * inherited by the child as well. This mechanism recursively traces the full
46   * inheritance hierarchy. So if the grand parent itself has a parent, this grand
47   * parent parent's heritable metadata will be inherited and so on. Note, that it
48   * is impossible to inherit metadata from multiple parents. In cases of multiple
49   * inheritance request, an exception is thrown. A child link cannot occur twice
50   * from the same object to the same href (preventing from doubled links). Not
51   * supported by this class are links from or to a defined place of a document
52   * (inner structure and combination of inner and outer structures of the
53   * objects). This will possibly be done in a later extension of
54   * <em>MCRMetaLink</em> and <em>MCRObjectStructure</em>.
55   * 
56   * @author Mathias Hegner
57   * @author Jens Kupferschmidt
58   * @version $Revision$ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb
59   *          2008) $
60   */
61  public class MCRObjectStructure {
62  
63      private MCRMetaLinkID parent;
64  
65      private final ArrayList<MCRMetaLinkID> children;
66  
67      private final ArrayList<MCRMetaEnrichedLinkID> derivates;
68  
69      private static final Logger LOGGER = LogManager.getLogger();
70  
71      /**
72       * The constructor initializes NL (non-static, in order to enable different
73       * NL's for different objects) and the link vectors the elements of which
74       * are MCRMetaLink's.
75       */
76      public MCRObjectStructure() {
77          children = new ArrayList<>();
78          derivates = new ArrayList<>();
79      }
80  
81      /**
82       * This method clean the data lists parent, children and derivates of this
83       * class.
84       */
85      public final void clear() {
86          parent = null;
87          children.clear();
88          derivates.clear();
89      }
90  
91      /**
92       * This method clean the data lists children of this class.
93       */
94      public final void clearChildren() {
95          children.clear();
96      }
97  
98      /**
99       * This method clean the data lists derivate of this class.
100      */
101     public final void clearDerivates() {
102         derivates.clear();
103     }
104 
105     /**
106      * The method returns the parent link.
107      * 
108      * @return MCRMetaLinkID the corresponding link
109      */
110     public final MCRMetaLinkID getParent() {
111         return parent;
112     }
113 
114     /**
115      * The method return the parent reference as a MCRObjectID.
116      * 
117      * @return the parent MCRObjectID or null if there is no parent present
118      */
119     public final MCRObjectID getParentID() {
120         if (parent == null) {
121             return null;
122         }
123         return parent.getXLinkHrefID();
124     }
125 
126     /**
127      * This method set the parent value from a given MCRMetaLinkID.
128      * 
129      * @param parent
130      *            the MCRMetaLinkID to set
131      *  
132      */
133     public final void setParent(MCRMetaLinkID parent) {
134         this.parent = parent;
135     }
136 
137     public final void setParent(MCRObjectID parentID) {
138         setParent(parentID.toString());
139     }
140 
141     public final void setParent(String parentID) {
142         parent = new MCRMetaLinkID();
143         parent.setSubTag("parent");
144         parent.setReference(parentID, null, null);
145     }
146 
147     /**
148      * Removes the parent reference. Use this method with care!
149      */
150     public final void removeParent() {
151         parent = null;
152     }
153 
154     /**
155      * The method appends a child ID to the child link list if and only if it is
156      * not already contained in the list, preventing from doubly-linked objects.
157      * If the link could be added a "true" will be returned, otherwise "false".
158      * 
159      * @param child
160      *            the MCRMetaLinkID of the child
161      * @return boolean true, if successfully done
162      */
163     public final boolean addChild(MCRMetaLinkID child) {
164         for (MCRMetaLinkID c : children) {
165             if (c.getXLinkHrefID().equals(child.getXLinkHrefID())) {
166                 return false;
167             }
168         }
169         children.add(child);
170 
171         return true;
172     }
173 
174     /**
175      * removes a child link to another object.
176      *  If the link was found a "true" will be returned, otherwise
177      * "false".
178      * 
179      * @param href
180      *            the MCRObjectID of the child
181      * @return boolean true, if successfully completed
182      */
183     public final boolean removeChild(MCRObjectID href) {
184         if (LOGGER.isDebugEnabled()) {
185             LOGGER.debug("Remove child ID {}", href);
186         }
187         return removeMetaLink(getChildren(), href);
188     }
189 
190     /**
191      * Checks if the child is in the children vector.
192      *
193      * @param childId child to check
194      */
195     public final boolean containsChild(MCRObjectID childId) {
196         return getChildren().stream().map(MCRMetaLinkID::getXLinkHrefID).anyMatch(childId::equals);
197     }
198 
199     /**
200      * removes a derivate link.
201      * If the link was found a "true" will be returned, otherwise
202      * "false".
203      * 
204      * @param href
205      *            the MCRObjectID of the child
206      * @return boolean true, if successfully completed
207      */
208     public final boolean removeDerivate(MCRObjectID href) {
209         if (LOGGER.isDebugEnabled()) {
210             LOGGER.debug("Remove derivate ID {}", href);
211         }
212         return removeMetaLink(getDerivates(), href);
213     }
214 
215     /**
216      * Removes a MCRMetaLinkID instance by it MCRObjectID.
217      * @param list
218      * @param href
219      * @return
220      */
221     private boolean removeMetaLink(List<? extends MCRMetaLinkID> list, MCRObjectID href) {
222         final List<MCRMetaLink> toRemove = list.stream()
223             .filter(ml -> ml.getXLinkHrefID().equals(href))
224             .collect(Collectors.toList());
225         return list.removeAll(toRemove);
226     }
227 
228     /**
229      * Returns all children in this structure
230      * */
231     public final List<MCRMetaLinkID> getChildren() {
232         return children;
233     }
234 
235     /**
236      * <em>addDerivate</em> methode append the given derivate link data to the
237      * derivate vector. If the link could be added a "true" will be returned,
238      * otherwise "false".
239      * 
240      * @param derivate
241      *            the link to be added as MCRMetaLinkID
242      */
243     public final boolean addDerivate(MCRMetaEnrichedLinkID derivate) {
244         MCRObjectID href = derivate.getXLinkHrefID();
245         if (containsDerivate(href)) {
246             return false;
247         }
248         if (!MCRMetadataManager.exists(href)) {
249             LOGGER.warn("Cannot find derivate {}, will add it anyway.", href);
250         }
251         derivates.add(derivate);
252         derivates.sort(Comparator.comparingInt(MCRMetaEnrichedLinkID::getOrder));
253         return true;
254     }
255 
256     /**
257      * Adds or updates the derivate link. Returns true if the derivate is added
258      * or updated. Returns false when nothing is done.
259      * 
260      * @param derivateLink the link to add or update
261      * @return true when the structure is changed
262      */
263     public final boolean addOrUpdateDerivate(MCRMetaEnrichedLinkID derivateLink) {
264         if (derivateLink == null) {
265             return false;
266         }
267         MCRObjectID derivateId = derivateLink.getXLinkHrefID();
268         MCRMetaLinkID oldLink = getDerivateLink(derivateId);
269         if (derivateLink.equals(oldLink)) {
270             return false;
271         }
272         if (oldLink != null) {
273             removeDerivate(oldLink.getXLinkHrefID());
274         }
275         return addDerivate(derivateLink);
276     }
277 
278     /**
279      * Checks if the derivate is in the derivate vector.
280      * 
281      * @param derivateId derivate to check
282      */
283     public final boolean containsDerivate(MCRObjectID derivateId) {
284         return getDerivateLink(derivateId) != null;
285     }
286 
287     /**
288      * Returns the derivate link by id or null.
289      */
290     public final MCRMetaEnrichedLinkID getDerivateLink(MCRObjectID derivateId) {
291         return getDerivates().stream()
292             .filter(derivate -> derivate.getXLinkHrefID().equals(derivateId))
293             .findAny()
294             .orElse(null);
295     }
296 
297     /** 
298      * @return a list with all related derivate ids encapsulated within a {@link MCRMetaLinkID}
299      * */
300     public List<MCRMetaEnrichedLinkID> getDerivates() {
301         return this.derivates;
302     }
303 
304     /**
305      * While the preceding methods dealt with the structure's copy in memory
306      * only, the following three will affect the operations to or from datastore
307      * too. Thereby <em>setFromDOM</em> will read the structure data from an
308      * XML input stream (the "structure" entry).
309      * 
310      * @param element
311      *            the structure node list
312      */
313     public final void setFromDOM(Element element) {
314         clear();
315         Element subElement = element.getChild("children");
316 
317         if (subElement != null) {
318             List<Element> childList = subElement.getChildren();
319 
320             for (Element linkElement : childList) {
321                 MCRMetaLinkID link = new MCRMetaLinkID();
322                 link.setFromDOM(linkElement);
323                 children.add(link);
324             }
325         }
326 
327         // Stricture parent part
328         subElement = element.getChild("parents");
329 
330         if (subElement != null) {
331             parent = new MCRMetaLinkID();
332             parent.setFromDOM(subElement.getChild("parent"));
333         }
334 
335         // Structure derivate part
336         subElement = element.getChild("derobjects");
337 
338         if (subElement != null) {
339             List<Element> derobjectList = subElement.getChildren();
340 
341             for (Element derElement : derobjectList) {
342                 addDerivate(MCRMetaEnrichedLinkID.fromDom(derElement));
343             }
344         }
345     }
346 
347     /**
348      * <em>createXML</em> is the inverse of setFromDOM and converts the
349      * structure's memory copy into XML.
350      * 
351      * @exception MCRException
352      *                if the content of this class is not valid
353      * @return the structure XML
354      */
355     public final Element createXML() throws MCRException {
356         try {
357             validate();
358         } catch (MCRException exc) {
359             throw new MCRException("The content is not valid.", exc);
360         }
361 
362         Element elm = new Element("structure");
363 
364         if (parent != null) {
365             Element elmm = new Element("parents");
366             elmm.setAttribute("class", "MCRMetaLinkID");
367             elmm.addContent(parent.createXML());
368             elm.addContent(elmm);
369         }
370 
371         if (children.size() > 0) {
372             Element elmm = new Element("children");
373             elmm.setAttribute("class", "MCRMetaLinkID");
374             for (MCRMetaLinkID child : getChildren()) {
375                 elmm.addContent(child.createXML());
376             }
377             elm.addContent(elmm);
378         }
379 
380         if (derivates.size() > 0) {
381             Element elmm = new Element("derobjects");
382             elmm.setAttribute("class", "MCRMetaEnrichedLinkID");
383             for (MCRMetaLinkID derivate : getDerivates()) {
384                 elmm.addContent(derivate.createXML());
385             }
386             elm.addContent(elmm);
387         }
388 
389         return elm;
390     }
391 
392     /**
393      * Creates the JSON representation of this structure.
394      * 
395      * <pre>
396      *   {
397      *     parent: {@link MCRMetaLinkID#createJSON()},
398      *     children: [
399      *      {@link MCRMetaLinkID#createJSON()}
400      *      ...
401      *     ],
402      *     derivates: [
403      *       {@link MCRMetaLinkID#createJSON()}
404      *        ...
405      *     ]
406      *   }
407      * </pre>
408      * 
409      * @return a json gson representation of this structure
410      */
411     public JsonObject createJSON() {
412         JsonObject structure = new JsonObject();
413         // parent
414         Optional.ofNullable(getParent()).ifPresent(link -> structure.add("parent", link.createJSON()));
415         // children
416         JsonArray children = new JsonArray();
417         getChildren().forEach(child -> children.add(child.createJSON()));
418         structure.add("children", children);
419         // derivates
420         JsonArray derivates = new JsonArray();
421         getDerivates().forEach(derivate -> derivates.add(derivate.createJSON()));
422         structure.add("derivates", derivates);
423         return structure;
424     }
425 
426     /**
427      * The method print all informations about this MCRObjectStructure.
428      */
429     public final void debug() {
430         if (LOGGER.isDebugEnabled()) {
431             for (MCRMetaLinkID linkID : derivates) {
432                 linkID.debug();
433             }
434             if (parent != null) {
435                 parent.debug();
436             }
437             for (MCRMetaLinkID linkID : children) {
438                 linkID.debug();
439             }
440         }
441     }
442 
443     /**
444      * <em>isValid</em> checks whether all of the MCRMetaLink's in the link
445      * vectors are valid or not.
446      * 
447      * @return boolean true, if structure is valid
448      */
449     public final boolean isValid() {
450         try {
451             validate();
452             return true;
453         } catch (MCRException exc) {
454             LOGGER.warn("The <structure> part of a <mycoreobject> is invalid.", exc);
455         }
456         return false;
457     }
458 
459     /**
460      * Validates this MCRObjectStructure. This method throws an exception if:
461      *  <ul>
462      *  <li>the parent is not null but invalid</li>
463      *  <li>one of the children is invalid</li>
464      *  <li>one of the derivates is invalid</li>
465      *  </ul>
466      * 
467      * @throws MCRException the MCRObjectStructure is invalid
468      */
469     public void validate() throws MCRException {
470         for (MCRMetaLinkID child : getChildren()) {
471             try {
472                 child.validate();
473             } catch (Exception exc) {
474                 throw new MCRException("The link to the children '" + child.getXLinkHref() + "' is invalid.", exc);
475             }
476         }
477 
478         if (parent != null) {
479             try {
480                 parent.validate();
481             } catch (Exception exc) {
482                 throw new MCRException("The link to the parent '" + parent.getXLinkHref() + "' is invalid.", exc);
483             }
484         }
485 
486         for (MCRMetaLinkID derivate : getDerivates()) {
487             try {
488                 derivate.validate();
489             } catch (Exception exc) {
490                 throw new MCRException("The link to the derivate '" + derivate.getXLinkHref() + "' is invalid.", exc);
491             }
492             if (!derivate.getXLinkType().equals("locator")) {
493                 throw new MCRException("The xlink:type of the derivate link '" + derivate.getXLinkHref()
494                     + "' has to be 'locator' and not '" + derivate.getXLinkType() + "'.");
495             }
496 
497             String typeId = derivate.getXLinkHrefID().getTypeId();
498             if (!typeId.equals("derivate")) {
499                 throw new MCRException("The derivate link '" + derivate.getXLinkHref()
500                     + "' is invalid. The _type_ has to be 'derivate' and not '" + typeId + "'.");
501             }
502         }
503     }
504 
505 }