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.mods;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.stream.Collectors;
30  
31  import org.jdom2.Content;
32  import org.jdom2.Element;
33  import org.jdom2.JDOMException;
34  import org.jdom2.filter.Filters;
35  import org.jdom2.xpath.XPathExpression;
36  import org.jdom2.xpath.XPathFactory;
37  import org.mycore.common.MCRConstants;
38  import org.mycore.common.MCRException;
39  import org.mycore.common.config.MCRConfiguration2;
40  import org.mycore.datamodel.classifications2.MCRCategoryID;
41  import org.mycore.datamodel.metadata.MCRMetaElement;
42  import org.mycore.datamodel.metadata.MCRMetaXML;
43  import org.mycore.datamodel.metadata.MCRObject;
44  import org.mycore.datamodel.metadata.MCRObjectID;
45  import org.mycore.datamodel.metadata.MCRObjectMetadata;
46  import org.mycore.datamodel.metadata.MCRObjectService;
47  import org.mycore.mods.classification.MCRClassMapper;
48  
49  /**
50   * @author Frank L\u00FCtzenkirchen
51   * @author Thomas Scheffler
52   */
53  public class MCRMODSWrapper {
54  
55      //ancestor::mycoreobject required for MCR-927
56      private static final String LINKED_RELATED_ITEMS = "mods:relatedItem[@type='host'"
57          + " and ancestor::mycoreobject/structure/parents/parent or"
58          + " string-length(substring-after(@xlink:href,'_')) > 0 and"
59          + " string-length(substring-after(substring-after(@xlink:href,'_'), '_')) > 0 and"
60          + " number(substring-after(substring-after(@xlink:href,'_'),'_')) > 0 and" + " contains('"
61          + MCRMODSRelationshipType.xPathList() + "', @type)]";
62  
63      private static final String MODS_CONTAINER = "modsContainer";
64  
65      private static final String DEF_MODS_CONTAINER = "def.modsContainer";
66  
67      public static final String MODS_OBJECT_TYPE = MCRConfiguration2.getStringOrThrow("MCR.MODS.NewObjectType");
68  
69      private static final String MODS_DATAMODEL = "datamodel-mods.xsd";
70  
71      private static List<String> topLevelElementOrder = new ArrayList<>();
72  
73      private static Set<String> SUPPORTED_TYPES = MCRConfiguration2
74          .getOrThrow("MCR.MODS.Types", MCRConfiguration2::splitValue)
75          .collect(Collectors.toSet());
76  
77      private MCRObject object;
78  
79      static {
80          topLevelElementOrder.add("typeOfResource");
81          topLevelElementOrder.add("titleInfo");
82          topLevelElementOrder.add("name");
83          topLevelElementOrder.add("genre");
84          topLevelElementOrder.add("originInfo");
85          topLevelElementOrder.add("language");
86          topLevelElementOrder.add("abstract");
87          topLevelElementOrder.add("note");
88          topLevelElementOrder.add("subject");
89          topLevelElementOrder.add("classification");
90          topLevelElementOrder.add("relatedItem");
91          topLevelElementOrder.add("identifier");
92          topLevelElementOrder.add("location");
93          topLevelElementOrder.add("accessCondition");
94      }
95  
96      private static int getRankOf(Element topLevelElement) {
97          return topLevelElementOrder.indexOf(topLevelElement.getName());
98      }
99  
100     public static MCRObject wrapMODSDocument(Element modsDefinition, String projectID) {
101         MCRMODSWrapper wrapper = new MCRMODSWrapper();
102         wrapper.setID(projectID, 0);
103         wrapper.setMODS(modsDefinition);
104         return wrapper.getMCRObject();
105     }
106 
107     /**
108      * returns true if the given MCRObject can handle MODS metadata
109      * @param obj - the MCRObject
110      * @return true, if mods is supported
111      */
112     public static boolean isSupported(MCRObject obj) {
113         if (isSupported(obj.getId())) {
114             return true;
115         }
116         return obj.getMetadata() != null && obj.getMetadata().getMetadataElement(DEF_MODS_CONTAINER) != null
117             && obj.getMetadata().getMetadataElement(DEF_MODS_CONTAINER).getElementByName(MODS_CONTAINER) != null;
118     }
119 
120     /**
121      * Returns true of the given {@link MCRObjectID} has a mods type. Does not look at the object.
122      * @param id - the {@link MCRObjectID}
123      * @return true if has a mods type
124      */
125     public static boolean isSupported(MCRObjectID id) {
126         return SUPPORTED_TYPES.contains(id.getTypeId());
127     }
128 
129     public MCRMODSWrapper() {
130         this(new MCRObject());
131     }
132 
133     public MCRMODSWrapper(MCRObject object) {
134         this.object = object;
135         if (object.getSchema() == null || object.getSchema().isEmpty()) {
136             object.setSchema(MODS_DATAMODEL);
137         }
138     }
139 
140     /**
141      * @return the mods:mods Element at /metadata/def.modsContainer/modsContainer
142      */
143     public Element getMODS() {
144         try {
145             MCRMetaXML mx = (MCRMetaXML) (object.getMetadata().getMetadataElement(DEF_MODS_CONTAINER).getElement(0));
146             for (Content content : mx.getContent()) {
147                 if (content instanceof Element) {
148                     return (Element) content;
149                 }
150             }
151         } catch (NullPointerException | IndexOutOfBoundsException e) {
152             //do nothing
153         }
154         return null;
155     }
156 
157     public MCRObject getMCRObject() {
158         return object;
159     }
160 
161     public MCRObjectID setID(String projectID, int id) {
162         MCRObjectID objID = MCRObjectID.getInstance(MCRObjectID.formatID(projectID, MODS_OBJECT_TYPE, id));
163         object.setId(objID);
164         return objID;
165     }
166 
167     public void setMODS(Element mods) {
168         MCRObjectMetadata om = object.getMetadata();
169         if (om.getMetadataElement(DEF_MODS_CONTAINER) != null) {
170             om.removeMetadataElement(DEF_MODS_CONTAINER);
171         }
172 
173         MCRMetaXML modsContainer = new MCRMetaXML(MODS_CONTAINER, null, 0);
174         List<MCRMetaXML> list = Collections.nCopies(1, modsContainer);
175         MCRMetaElement defModsContainer = new MCRMetaElement(MCRMetaXML.class, DEF_MODS_CONTAINER, false, true, list);
176         om.setMetadataElement(defModsContainer);
177 
178         modsContainer.addContent(mods);
179     }
180 
181     private XPathExpression<Element> buildXPath(String xPath) throws JDOMException {
182         return XPathFactory.instance().compile(xPath, Filters.element(), null, MCRConstants.MODS_NAMESPACE,
183             MCRConstants.XLINK_NAMESPACE);
184     }
185 
186     public Element getElement(String xPath) {
187         try {
188             return buildXPath(xPath).evaluateFirst(getMODS());
189         } catch (JDOMException ex) {
190             String msg = "Could not get MODS element from " + xPath;
191             throw new MCRException(msg, ex);
192         }
193     }
194 
195     public List<Element> getElements(String xPath) {
196         try {
197             Element eMODS = getMODS();
198             if (eMODS != null) {
199                 return buildXPath(xPath).evaluate(eMODS);
200             } else {
201                 return Collections.emptyList();
202             }
203         } catch (JDOMException ex) {
204             String msg = "Could not get elements at " + xPath;
205             throw new MCRException(msg, ex);
206         }
207     }
208 
209     public List<Element> getLinkedRelatedItems() {
210         return getElements(LINKED_RELATED_ITEMS);
211     }
212 
213     public String getElementValue(String xPath) {
214         Element element = getElement(xPath);
215         return (element == null ? null : element.getTextTrim());
216     }
217 
218     /**
219      * Sets or adds an element with target name and value. The element name and attributes are used as xpath expression
220      * to filter for an element. The attributes are used with and operation if present.
221      */
222     public Optional<Element> setElement(String elementName, String elementValue, Map<String, String> attributes) {
223         boolean isAttributeDataPresent = attributes != null && !attributes.isEmpty();
224         boolean isValuePresent = elementValue != null && !elementValue.isEmpty();
225 
226         if (!isValuePresent && !isAttributeDataPresent) {
227             return Optional.empty();
228         }
229 
230         StringBuilder xPath = new StringBuilder("mods:");
231         xPath.append(elementName);
232 
233         // add attributes to xpath with and operator
234         if (isAttributeDataPresent) {
235 
236             xPath.append('[');
237             Iterator<Map.Entry<String, String>> attributeIterator = attributes.entrySet().iterator();
238             while (attributeIterator.hasNext()) {
239                 Map.Entry<String, String> attribute = attributeIterator.next();
240                 xPath.append('@').append(attribute.getKey()).append("='").append(attribute.getValue()).append('\'');
241 
242                 if (attributeIterator.hasNext()) {
243                     xPath.append(" and ");
244                 }
245             }
246             xPath.append(']');
247         }
248         Element element = getElement(xPath.toString());
249 
250         if (element == null) {
251             element = addElement(elementName);
252             if (isAttributeDataPresent) {
253                 for (Map.Entry<String, String> entry : attributes.entrySet()) {
254                     element.setAttribute(entry.getKey(), entry.getValue());
255                 }
256             }
257         }
258 
259         if (isValuePresent) {
260             element.setText(elementValue.trim());
261         }
262 
263         return Optional.of(element);
264     }
265 
266     public Optional<Element> setElement(String elementName, String attributeName, String attributeValue,
267         String elementValue) {
268         Map<String, String> attributes = Collections.emptyMap();
269         if (attributeName != null && attributeValue != null) {
270             attributes = new HashMap<>();
271             attributes.put(attributeName, attributeValue);
272         }
273         return setElement(elementName, elementValue, attributes);
274     }
275 
276     public Optional<Element> setElement(String elementName, String elementValue) {
277         return setElement(elementName, null, null, elementValue);
278     }
279 
280     public Element addElement(String elementName) {
281         Element element = new Element(elementName, MCRConstants.MODS_NAMESPACE);
282         insertTopLevelElement(element);
283         return element;
284     }
285 
286     public void addElement(Element element) {
287         if (!element.getNamespace().equals(MCRConstants.MODS_NAMESPACE)) {
288             throw new IllegalArgumentException("given element is no mods element");
289         }
290 
291         insertTopLevelElement(element);
292     }
293 
294     private void insertTopLevelElement(Element element) {
295         int rankOfNewElement = getRankOf(element);
296         List<Element> topLevelElements = getMODS().getChildren();
297         for (int pos = 0; pos < topLevelElements.size(); pos++) {
298             if (getRankOf(topLevelElements.get(pos)) > rankOfNewElement) {
299                 getMODS().addContent(pos, element);
300                 return;
301             }
302         }
303 
304         getMODS().addContent(element);
305     }
306 
307     public void removeElements(String xPath) {
308         Iterator<Element> selected;
309         try {
310             selected = buildXPath(xPath).evaluate(getMODS()).iterator();
311         } catch (JDOMException ex) {
312             String msg = "Could not remove elements at " + xPath;
313             throw new MCRException(msg, ex);
314         }
315 
316         while (selected.hasNext()) {
317             Element element = selected.next();
318             element.detach();
319         }
320     }
321 
322     public void removeInheritedMetadata() {
323         String xPath = LINKED_RELATED_ITEMS + "/*[local-name()!='part']";
324         removeElements(xPath);
325     }
326 
327     public String getServiceFlag(String type) {
328         MCRObjectService os = object.getService();
329         return (os.isFlagTypeSet(type) ? os.getFlags(type).get(0) : null);
330     }
331 
332     public void setServiceFlag(String type, String value) {
333         MCRObjectService os = object.getService();
334         if (os.isFlagTypeSet(type)) {
335             os.removeFlags(type);
336         }
337         if ((value != null) && !value.trim().isEmpty()) {
338             os.addFlag(type, value.trim());
339         }
340     }
341 
342     public List<MCRCategoryID> getMcrCategoryIDs() {
343         final List<Element> categoryNodes = getCategoryNodes();
344         final List<MCRCategoryID> categories = new ArrayList<>(categoryNodes.size());
345         for (Element node : categoryNodes) {
346             final MCRCategoryID categoryID = MCRClassMapper.getCategoryID(node);
347             if (categoryID != null) {
348                 categories.add(categoryID);
349             }
350         }
351         return categories;
352     }
353 
354     private List<Element> getCategoryNodes() {
355         return getElements(
356             "mods:typeOfResource | mods:accessCondition | .//*[(@authority or @authorityURI)"
357                 + " and not(ancestor::mods:relatedItem)]");
358     }
359 }