1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.datamodel.metadata.validator;
20
21 import static org.jdom2.Namespace.XML_NAMESPACE;
22 import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
23 import static org.mycore.common.MCRConstants.XSI_NAMESPACE;
24
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.stream.Collectors;
35
36 import org.apache.logging.log4j.LogManager;
37 import org.apache.logging.log4j.Logger;
38 import org.jdom2.Attribute;
39 import org.jdom2.Document;
40 import org.jdom2.Element;
41 import org.jdom2.JDOMException;
42 import org.jdom2.filter.Filters;
43 import org.jdom2.input.SAXBuilder;
44 import org.jdom2.output.Format;
45 import org.jdom2.output.XMLOutputter;
46 import org.jdom2.xpath.XPathFactory;
47 import org.mycore.access.MCRAccessManager;
48 import org.mycore.access.MCRRuleAccessInterface;
49 import org.mycore.common.MCRClassTools;
50 import org.mycore.common.MCRException;
51 import org.mycore.common.MCRSessionMgr;
52 import org.mycore.common.MCRUtils;
53 import org.mycore.common.config.MCRConfiguration2;
54 import org.mycore.common.content.MCRJDOMContent;
55 import org.mycore.datamodel.metadata.MCRMetaAccessRule;
56 import org.mycore.datamodel.metadata.MCRMetaAddress;
57 import org.mycore.datamodel.metadata.MCRMetaBoolean;
58 import org.mycore.datamodel.metadata.MCRMetaClassification;
59 import org.mycore.datamodel.metadata.MCRMetaDerivateLink;
60 import org.mycore.datamodel.metadata.MCRMetaEnrichedLinkID;
61 import org.mycore.datamodel.metadata.MCRMetaHistoryDate;
62 import org.mycore.datamodel.metadata.MCRMetaISO8601Date;
63 import org.mycore.datamodel.metadata.MCRMetaInstitutionName;
64 import org.mycore.datamodel.metadata.MCRMetaInterface;
65 import org.mycore.datamodel.metadata.MCRMetaLangText;
66 import org.mycore.datamodel.metadata.MCRMetaLink;
67 import org.mycore.datamodel.metadata.MCRMetaLinkID;
68 import org.mycore.datamodel.metadata.MCRMetaNumber;
69 import org.mycore.datamodel.metadata.MCRMetaPersonName;
70 import org.mycore.datamodel.metadata.MCRObject;
71 import org.mycore.datamodel.metadata.MCRObjectID;
72 import org.xml.sax.SAXParseException;
73
74
75
76
77
78 public class MCREditorOutValidator {
79
80 private static final String CONFIG_PREFIX = "MCR.EditorOutValidator.";
81
82 private static final SAXBuilder SAX_BUILDER = new SAXBuilder();
83
84 private static Logger LOGGER = LogManager.getLogger();
85
86 private static Map<String, MCREditorMetadataValidator> VALIDATOR_MAP = getValidatorMap();
87
88 private static Map<String, Class<? extends MCRMetaInterface>> CLASS_MAP = new HashMap<>();
89
90 private Document input;
91
92 private MCRObjectID id;
93
94 private List<String> errorlog;
95
96
97
98
99
100
101
102
103
104
105 public MCREditorOutValidator(Document jdomIn, MCRObjectID id) throws JDOMException, IOException {
106 errorlog = new ArrayList<>();
107 input = jdomIn;
108 this.id = id;
109 if (LOGGER.isDebugEnabled()) {
110 LOGGER.debug("XML before validation:\n{}", new XMLOutputter(Format.getPrettyFormat()).outputString(input));
111 }
112 checkObject();
113 if (LOGGER.isDebugEnabled()) {
114 LOGGER.debug("XML after validation:\n{}", new XMLOutputter(Format.getPrettyFormat()).outputString(input));
115 }
116 }
117
118 private static Map<String, MCREditorMetadataValidator> getValidatorMap() {
119 Map<String, MCREditorMetadataValidator> map = new HashMap<>();
120 map.put(MCRMetaBoolean.class.getSimpleName(), getObjectCheckInstance(MCRMetaBoolean.class));
121 map.put(MCRMetaPersonName.class.getSimpleName(), getObjectCheckWithLangInstance(MCRMetaPersonName.class));
122 map.put(MCRMetaInstitutionName.class.getSimpleName(),
123 getObjectCheckWithLangInstance(MCRMetaInstitutionName.class));
124 map.put(MCRMetaAddress.class.getSimpleName(), new MCRMetaAdressCheck());
125 map.put(MCRMetaNumber.class.getSimpleName(), getObjectCheckWithLangNotEmptyInstance(MCRMetaNumber.class));
126 map.put(MCRMetaLinkID.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaLinkID.class));
127 map.put(MCRMetaEnrichedLinkID.class.getSimpleName(),
128 getObjectCheckWithLinksInstance(MCRMetaEnrichedLinkID.class));
129 map.put(MCRMetaDerivateLink.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaDerivateLink.class));
130 map.put(MCRMetaLink.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaLink.class));
131 map.put(MCRMetaISO8601Date.class.getSimpleName(),
132 getObjectCheckWithLangNotEmptyInstance(MCRMetaISO8601Date.class));
133 map.put(MCRMetaLangText.class.getSimpleName(), getObjectCheckWithLangNotEmptyInstance(MCRMetaLangText.class));
134 map.put(MCRMetaAccessRule.class.getSimpleName(), getObjectCheckInstance(MCRMetaAccessRule.class));
135 map.put(MCRMetaClassification.class.getSimpleName(), new MCRMetaClassificationCheck());
136 map.put(MCRMetaHistoryDate.class.getSimpleName(), new MCRMetaHistoryDateCheck());
137 Map<String, String> props = MCRConfiguration2.getPropertiesMap()
138 .entrySet()
139 .stream()
140 .filter(p -> p.getKey().startsWith(CONFIG_PREFIX + "class."))
141 .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
142 for (Entry<String, String> entry : props.entrySet()) {
143 try {
144 String className = entry.getKey();
145 className = className.substring(className.lastIndexOf('.') + 1);
146 LOGGER.info("Adding Validator {} for class {}", entry.getValue(), className);
147 @SuppressWarnings("unchecked")
148 Class<? extends MCREditorMetadataValidator> cl = (Class<? extends MCREditorMetadataValidator>) Class
149 .forName(entry.getValue());
150 map.put(className, cl.getDeclaredConstructor().newInstance());
151 } catch (Exception e) {
152 final String msg = "Cannot instantiate " + entry.getValue() + " as validator for class "
153 + entry.getKey();
154 LOGGER.error(msg);
155 throw new MCRException(msg, e);
156 }
157 }
158 return map;
159 }
160
161 @SuppressWarnings("unchecked")
162 public static Class<? extends MCRMetaInterface> getClass(String mcrclass) throws ClassNotFoundException {
163 Class<? extends MCRMetaInterface> clazz = CLASS_MAP.get(mcrclass);
164 if (clazz == null) {
165 clazz = MCRClassTools.forName("org.mycore.datamodel.metadata." + mcrclass);
166 CLASS_MAP.put(mcrclass, clazz);
167 }
168 return clazz;
169 }
170
171 public static String checkMetaObject(Element datasubtag, Class<? extends MCRMetaInterface> metaClass,
172 boolean keepLang) {
173 if (!keepLang) {
174 datasubtag.removeAttribute("lang", XML_NAMESPACE);
175 }
176 MCRMetaInterface test = null;
177 try {
178 test = metaClass.getDeclaredConstructor().newInstance();
179 } catch (Exception e) {
180 throw new MCRException("Could not instantiate " + metaClass.getCanonicalName());
181 }
182 test.setFromDOM(datasubtag);
183 test.validate();
184 return null;
185 }
186
187 public static String checkMetaObjectWithLang(Element datasubtag, Class<? extends MCRMetaInterface> metaClass) {
188 if (datasubtag.getAttribute("lang") != null) {
189 datasubtag.getAttribute("lang").setNamespace(XML_NAMESPACE);
190 LOGGER.warn("namespace add for xml:lang attribute in {}", datasubtag.getName());
191 }
192 return checkMetaObject(datasubtag, metaClass, true);
193 }
194
195 public static String checkMetaObjectWithLangNotEmpty(Element datasubtag,
196 Class<? extends MCRMetaInterface> metaClass) {
197 String text = datasubtag.getTextTrim();
198 if (text == null || text.length() == 0) {
199 return "Element " + datasubtag.getName() + " has no text.";
200 }
201 return checkMetaObjectWithLang(datasubtag, metaClass);
202 }
203
204 public static String checkMetaObjectWithLinks(Element datasubtag, Class<? extends MCRMetaInterface> metaClass) {
205 if (datasubtag.getAttributeValue("href") == null
206 && datasubtag.getAttributeValue("href", XLINK_NAMESPACE) == null) {
207 return datasubtag.getName() + " has no href attribute defined";
208 }
209 if (datasubtag.getAttribute("xtype") != null) {
210 datasubtag.getAttribute("xtype").setNamespace(XLINK_NAMESPACE).setName("type");
211 } else if (datasubtag.getAttribute("type") != null
212 && datasubtag.getAttribute("type", XLINK_NAMESPACE) == null) {
213 datasubtag.getAttribute("type").setNamespace(XLINK_NAMESPACE);
214 LOGGER.warn("namespace add for xlink:type attribute in {}", datasubtag.getName());
215 }
216 if (datasubtag.getAttribute("href") != null) {
217 datasubtag.getAttribute("href").setNamespace(XLINK_NAMESPACE);
218 LOGGER.warn("namespace add for xlink:href attribute in {}", datasubtag.getName());
219 }
220
221 if (datasubtag.getAttribute("title") != null) {
222 datasubtag.getAttribute("title").setNamespace(XLINK_NAMESPACE);
223 LOGGER.warn("namespace add for xlink:title attribute in {}", datasubtag.getName());
224 }
225
226 if (datasubtag.getAttribute("label") != null) {
227 datasubtag.getAttribute("label").setNamespace(XLINK_NAMESPACE);
228 LOGGER.warn("namespace add for xlink:label attribute in {}", datasubtag.getName());
229 }
230 return checkMetaObject(datasubtag, metaClass, false);
231 }
232
233 static MCREditorMetadataValidator getObjectCheckInstance(final Class<? extends MCRMetaInterface> clazz) {
234 return datasubtag -> MCREditorOutValidator.checkMetaObject(datasubtag, clazz, false);
235 }
236
237 static MCREditorMetadataValidator getObjectCheckWithLangInstance(final Class<? extends MCRMetaInterface> clazz) {
238 return datasubtag -> MCREditorOutValidator.checkMetaObjectWithLang(datasubtag, clazz);
239 }
240
241 static MCREditorMetadataValidator getObjectCheckWithLangNotEmptyInstance(
242 final Class<? extends MCRMetaInterface> clazz) {
243 return datasubtag -> MCREditorOutValidator.checkMetaObjectWithLangNotEmpty(datasubtag, clazz);
244 }
245
246 static MCREditorMetadataValidator getObjectCheckWithLinksInstance(final Class<? extends MCRMetaInterface> clazz) {
247 return datasubtag -> MCREditorOutValidator.checkMetaObjectWithLinks(datasubtag, clazz);
248 }
249
250
251
252
253 public static void setDefaultDerivateACLs(Element service) {
254
255 InputStream aclxml = MCREditorOutValidator.class.getResourceAsStream("/editor_default_acls_derivate.xml");
256 if (aclxml == null) {
257 LOGGER.warn("Can't find default derivate ACL file editor_default_acls_derivate.xml.");
258 return;
259 }
260 try {
261 Document xml = SAX_BUILDER.build(aclxml);
262 Element acls = xml.getRootElement().getChild("servacls");
263 if (acls != null) {
264 service.addContent(acls.detach());
265 }
266 } catch (Exception e) {
267 LOGGER.warn("Error while parsing file editor_default_acls_derivate.xml.");
268 }
269 }
270
271
272
273
274
275
276 public Document generateValidMyCoReObject() throws JDOMException, SAXParseException, IOException {
277 MCRObject obj;
278
279 XPathFactory.instance()
280 .compile("/mycoreobject/*/*/*/@editor.output", Filters.attribute())
281 .evaluate(input)
282 .forEach(Attribute::detach);
283 try {
284 byte[] xml = new MCRJDOMContent(input).asByteArray();
285 obj = new MCRObject(xml, true);
286 } catch (SAXParseException e) {
287 XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat());
288 LOGGER.warn("Failure while parsing document:\n{}", xout.outputString(input));
289 throw e;
290 }
291
292
293
294
295
296 input = obj.createXML();
297 return input;
298 }
299
300
301
302
303
304
305 public List<String> getErrorLog() {
306 return errorlog;
307 }
308
309
310
311
312
313
314 private void checkObject() throws JDOMException, IOException {
315
316 Element root = input.getRootElement();
317 root.addNamespaceDeclaration(XLINK_NAMESPACE);
318 root.addNamespaceDeclaration(XSI_NAMESPACE);
319
320 String mcrSchema = "datamodel-" + id.getTypeId() + ".xsd";
321 root.setAttribute("noNamespaceSchemaLocation", mcrSchema, XSI_NAMESPACE);
322
323 String label = MCRUtils.filterTrimmedNotEmpty(root.getAttributeValue("label"))
324 .orElse(null);
325 if (label == null) {
326 root.setAttribute("label", id.toString());
327 }
328
329 Element pathes = root.getChild("pathes");
330 if (pathes != null) {
331 root.removeChildren("pathes");
332 }
333 Element structure = root.getChild("structure");
334 if (structure == null) {
335 root.addContent(new Element("structure"));
336 } else {
337 checkObjectStructure(structure);
338 }
339 Element metadata = root.getChild("metadata");
340 checkObjectMetadata(metadata);
341 Element service = root.getChild("service");
342 checkObjectService(root, service);
343 }
344
345
346
347
348 private boolean checkMetaTags(Element datatag) {
349 String mcrclass = datatag.getAttributeValue("class");
350 List<Element> datataglist = datatag.getChildren();
351 Iterator<Element> datatagIt = datataglist.iterator();
352
353 while (datatagIt.hasNext()) {
354 Element datasubtag = datatagIt.next();
355 MCREditorMetadataValidator validator = VALIDATOR_MAP.get(mcrclass);
356 String returns = null;
357 if (validator != null) {
358 returns = validator.checkDataSubTag(datasubtag);
359 } else {
360 LOGGER.warn("Tag <{}> of type {} has no validator defined, fallback to default behaviour",
361 datatag.getName(), mcrclass);
362
363 try {
364 Class<? extends MCRMetaInterface> metaClass = getClass(mcrclass);
365
366 returns = checkMetaObject(datasubtag, metaClass, true);
367 } catch (ClassNotFoundException e) {
368 throw new MCRException("Failure while trying fallback. Class not found: " + mcrclass, e);
369 }
370 }
371 if (returns != null) {
372 datatagIt.remove();
373 final String msg = datatag.getName() + ": " + returns;
374 errorlog.add(msg);
375 }
376 }
377 return datatag.getChildren().size() != 0;
378 }
379
380
381
382
383
384
385 private void checkObjectService(Element root, Element service) throws JDOMException, IOException {
386 if (service == null) {
387 service = new Element("service");
388 root.addContent(service);
389 }
390 List<Element> servicelist = service.getChildren();
391 for (Element datatag : servicelist) {
392 checkMetaTags(datatag);
393 }
394
395 if (service.getChild("servacls") == null &&
396 MCRAccessManager.getAccessImpl() instanceof MCRRuleAccessInterface) {
397 Collection<String> li = MCRAccessManager.getPermissionsForID(id.toString());
398 if (li == null || li.isEmpty()) {
399 setDefaultObjectACLs(service);
400 }
401 }
402 }
403
404
405
406
407
408
409
410
411 private void setDefaultObjectACLs(Element service) throws JDOMException, IOException {
412 if (!MCRConfiguration2.getBoolean("MCR.Access.AddObjectDefaultRule").orElse(true)) {
413 LOGGER.info("Adding object default acl rule is disabled.");
414 return;
415 }
416 String resourcetype = "/editor_default_acls_" + id.getTypeId() + ".xml";
417 String resourcebase = "/editor_default_acls_" + id.getBase() + ".xml";
418
419 InputStream aclxml = MCREditorOutValidator.class.getResourceAsStream(resourcebase);
420 if (aclxml == null) {
421 aclxml = MCREditorOutValidator.class.getResourceAsStream(resourcetype);
422 if (aclxml == null) {
423 LOGGER.warn("Can't find default object ACL file {} or {}", resourcebase.substring(1),
424 resourcetype.substring(1));
425 String resource = "/editor_default_acls.xml";
426 aclxml = MCREditorOutValidator.class.getResourceAsStream(resource);
427 if (aclxml == null) {
428 return;
429 }
430 }
431 }
432 Document xml = SAX_BUILDER.build(aclxml);
433 Element acls = xml.getRootElement().getChild("servacls");
434 if (acls == null) {
435 return;
436 }
437 for (Element acl : acls.getChildren()) {
438 Element condition = acl.getChild("condition");
439 if (condition == null) {
440 continue;
441 }
442 Element rootbool = condition.getChild("boolean");
443 if (rootbool == null) {
444 continue;
445 }
446 for (Element orbool : rootbool.getChildren("boolean")) {
447 for (Element firstcond : orbool.getChildren("condition")) {
448 if (firstcond == null) {
449 continue;
450 }
451 String value = firstcond.getAttributeValue("value");
452 if (value == null) {
453 continue;
454 }
455 if (value.equals("$CurrentUser")) {
456 String thisuser = MCRSessionMgr.getCurrentSession().getUserInformation().getUserID();
457 firstcond.setAttribute("value", thisuser);
458 continue;
459 }
460 if (value.equals("$CurrentGroup")) {
461 throw new MCRException(
462 "The parameter $CurrentGroup in default ACLs is not supported as of MyCoRe 2014.06"
463 + " because it is not supported in Servlet API 3.0");
464 }
465 int i = value.indexOf("$CurrentIP");
466 if (i != -1) {
467 String thisip = MCRSessionMgr.getCurrentSession().getCurrentIP();
468 firstcond.setAttribute("value",
469 value.substring(0, i) + thisip + value.substring(i + 10));
470 }
471 }
472 }
473 }
474 service.addContent(acls.detach());
475 }
476
477
478
479
480 private void checkObjectMetadata(Element metadata) {
481 if (metadata.getAttribute("lang") != null) {
482 metadata.getAttribute("lang").setNamespace(XML_NAMESPACE);
483 }
484
485 List<Element> metadatalist = metadata.getChildren();
486 Iterator<Element> metaIt = metadatalist.iterator();
487
488 while (metaIt.hasNext()) {
489 Element datatag = metaIt.next();
490 if (!checkMetaTags(datatag)) {
491
492 LOGGER.debug("Removing element :{}", datatag.getName());
493 metaIt.remove();
494 }
495 }
496 }
497
498 private void checkObjectStructure(Element structure) {
499
500 structure.getChildren().removeIf(datatag -> !checkMetaTags(datatag));
501 }
502
503 static class MCRMetaHistoryDateCheck implements MCREditorMetadataValidator {
504 public String checkDataSubTag(Element datasubtag) {
505 Element[] children = datasubtag.getChildren("text").toArray(Element[]::new);
506 int textCount = children.length;
507 for (int i = 0; i < children.length; i++) {
508 Element child = children[i];
509 String text = child.getTextTrim();
510 if (text == null || text.length() == 0) {
511 child.detach();
512 textCount--;
513 continue;
514 }
515 if (child.getAttribute("lang") != null) {
516 child.getAttribute("lang").setNamespace(XML_NAMESPACE);
517 LOGGER.warn("namespace add for xml:lang attribute in {}", datasubtag.getName());
518 }
519 }
520 if (textCount == 0) {
521 return "history date is empty";
522 }
523 return checkMetaObjectWithLang(datasubtag, MCRMetaHistoryDate.class);
524 }
525 }
526
527 static class MCRMetaClassificationCheck implements MCREditorMetadataValidator {
528 public String checkDataSubTag(Element datasubtag) {
529 String categid = datasubtag.getAttributeValue("categid");
530 if (categid == null) {
531 return "Attribute categid is empty";
532 }
533 return checkMetaObject(datasubtag, MCRMetaClassification.class, false);
534 }
535 }
536
537 static class MCRMetaAdressCheck implements MCREditorMetadataValidator {
538 public String checkDataSubTag(Element datasubtag) {
539 if (datasubtag.getChildren().size() == 0) {
540 return "adress is empty";
541 }
542 return checkMetaObjectWithLang(datasubtag, MCRMetaAddress.class);
543 }
544 }
545
546 static class MCRMetaPersonNameCheck implements MCREditorMetadataValidator {
547 public String checkDataSubTag(Element datasubtag) {
548 if (datasubtag.getChildren().size() == 0) {
549 return "person name is empty";
550 }
551 return checkMetaObjectWithLang(datasubtag, MCRMetaAddress.class);
552 }
553 }
554
555 }