001 /*
002 * $Revision: 1 $ $Date: 08.05.2009 15:51:37 $
003 *
004 * This file is part of *** M y C o R e ***
005 * See http://www.mycore.de/ for details.
006 *
007 * This program is free software; you can use it, redistribute it
008 * and / or modify it under the terms of the GNU General Public License
009 * (GPL) as published by the Free Software Foundation; either version 2
010 * of the License or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful, but
013 * WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with this program, in a file called gpl.txt or license.txt.
019 * If not, write to the Free Software Foundation Inc.,
020 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
021 */
022
023 package org.mycore.datamodel.metadata.validator;
024
025 import static org.jdom.Namespace.XML_NAMESPACE;
026 import static org.mycore.common.MCRConstants.XLINK_NAMESPACE;
027 import static org.mycore.common.MCRConstants.XSI_NAMESPACE;
028
029 import java.io.IOException;
030 import java.io.InputStream;
031 import java.util.ArrayList;
032 import java.util.Collection;
033 import java.util.Date;
034 import java.util.HashMap;
035 import java.util.Iterator;
036 import java.util.List;
037 import java.util.Map;
038 import java.util.Properties;
039 import java.util.Map.Entry;
040
041 import org.apache.log4j.Logger;
042 import org.jdom.Document;
043 import org.jdom.Element;
044 import org.jdom.JDOMException;
045 import org.jdom.input.SAXBuilder;
046 import org.jdom.xpath.XPath;
047 import org.mycore.access.MCRAccessManager;
048 import org.mycore.common.MCRConfiguration;
049 import org.mycore.common.MCRException;
050 import org.mycore.common.MCRSessionMgr;
051 import org.mycore.common.MCRUtils;
052 import org.mycore.datamodel.metadata.MCRMetaAccessRule;
053 import org.mycore.datamodel.metadata.MCRMetaAddress;
054 import org.mycore.datamodel.metadata.MCRMetaBoolean;
055 import org.mycore.datamodel.metadata.MCRMetaClassification;
056 import org.mycore.datamodel.metadata.MCRMetaDerivateLink;
057 import org.mycore.datamodel.metadata.MCRMetaHistoryDate;
058 import org.mycore.datamodel.metadata.MCRMetaISBN;
059 import org.mycore.datamodel.metadata.MCRMetaISO8601Date;
060 import org.mycore.datamodel.metadata.MCRMetaInstitutionName;
061 import org.mycore.datamodel.metadata.MCRMetaInterface;
062 import org.mycore.datamodel.metadata.MCRMetaLangText;
063 import org.mycore.datamodel.metadata.MCRMetaLink;
064 import org.mycore.datamodel.metadata.MCRMetaLinkID;
065 import org.mycore.datamodel.metadata.MCRMetaNBN;
066 import org.mycore.datamodel.metadata.MCRMetaNumber;
067 import org.mycore.datamodel.metadata.MCRMetaPersonName;
068 import org.mycore.datamodel.metadata.MCRObject;
069 import org.mycore.datamodel.metadata.MCRObjectID;
070 import org.mycore.user.MCRUserMgr;
071
072 /**
073 * @author Thomas Scheffler (yagee)
074 * @version $Revision: 1 $ $Date: 08.05.2009 15:51:37 $
075 */
076 public class MCREditorOutValidator {
077
078 private static Logger LOGGER = Logger.getLogger(MCREditorOutValidator.class);
079
080 private static Map<String, MCREditorMetadataValidator> VALIDATOR_MAP = getValidatorMap();
081
082 private static Map<String, Class<? extends MCRMetaInterface>> CLASS_MAP = new HashMap<String, Class<? extends MCRMetaInterface>>();
083
084 private static final String CONFIG_PREFIX = "MCR.EditorOutValidator.";
085
086 private static final SAXBuilder SAX_BUILDER = new org.jdom.input.SAXBuilder();
087
088 private Document input;
089
090 private MCRObjectID id;
091
092 private List<String> errorlog;
093
094 /**
095 * instantiate the validator with the editor input <code>jdom_in</code>.
096 *
097 * <code>id</code> will be set as the MCRObjectID for the resulting object
098 * that can be fetched with <code>generateValidMyCoReObject()</code>
099 *
100 * @param jdom_in
101 * editor input
102 * @throws IOException
103 * @throws JDOMException
104 */
105 public MCREditorOutValidator(Document jdom_in, MCRObjectID id) throws JDOMException, IOException {
106 this.errorlog = new ArrayList<String>();
107 this.input = jdom_in;
108 this.id = id;
109 if (LOGGER.isDebugEnabled()) {
110 LOGGER.debug("XML before validation:\n" + new String(MCRUtils.getByteArray(input)));
111 }
112 checkObject();
113 if (LOGGER.isDebugEnabled()) {
114 LOGGER.debug("XML after validation:\n" + new String(MCRUtils.getByteArray(input)));
115 }
116 }
117
118 /**
119 * tries to generate a valid MCRObject as JDOM Document.
120 *
121 * @return MCRObject
122 */
123 public Document generateValidMyCoReObject() {
124 MCRObject obj = new MCRObject();
125 try {
126 // load the JDOM object
127 XPath editorOutput = XPath.newInstance("/mycoreobject/*/*/*[@editor.output]");
128 for (Object node : editorOutput.selectNodes(input)) {
129 Element e = (Element) node;
130 LOGGER.debug("removing \"editor.output\" Attribute from " + e.getName());
131 e.removeAttribute("editor.output");
132 }
133 byte[] xml = MCRUtils.getByteArray(input);
134 obj.setFromXML(xml, true);
135 Date curTime = new Date();
136 obj.getService().setDate("createdate", curTime);
137 obj.getService().setDate("modifydate", curTime);
138
139 // return the XML tree
140 input = obj.createXML();
141 } catch (MCRException e) {
142 errorlog.add(e.getMessage());
143
144 Exception ex = e.getException();
145
146 if (ex != null) {
147 errorlog.add(ex.getMessage());
148 }
149 } catch (JDOMException e) {
150 errorlog.add(e.getMessage());
151 }
152 return input;
153 }
154
155 /**
156 * returns a List of Error log entries
157 *
158 * @return log entries for the whole validation process
159 */
160 public List<String> getErrorLog() {
161 return errorlog;
162 }
163
164 private static Map<String, MCREditorMetadataValidator> getValidatorMap() {
165 Map<String, MCREditorMetadataValidator> map = new HashMap<String, MCREditorMetadataValidator>();
166 map.put(MCRMetaBoolean.class.getSimpleName(), getObjectCheckInstance(MCRMetaBoolean.class));
167 map.put(MCRMetaPersonName.class.getSimpleName(), getObjectCheckWithLangInstance(MCRMetaPersonName.class));
168 map.put(MCRMetaInstitutionName.class.getSimpleName(), getObjectCheckWithLangInstance(MCRMetaInstitutionName.class));
169 map.put(MCRMetaAddress.class.getSimpleName(), new MCRMetaAdressCheck());
170 map.put(MCRMetaNumber.class.getSimpleName(), getObjectCheckWithLangNotEmptyInstance(MCRMetaNumber.class));
171 map.put(MCRMetaLinkID.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaLinkID.class));
172 map.put(MCRMetaDerivateLink.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaDerivateLink.class));
173 map.put(MCRMetaLink.class.getSimpleName(), getObjectCheckWithLinksInstance(MCRMetaLink.class));
174 map.put(MCRMetaISO8601Date.class.getSimpleName(), getObjectCheckWithLangNotEmptyInstance(MCRMetaISO8601Date.class));
175 map.put(MCRMetaLangText.class.getSimpleName(), getObjectCheckWithLangNotEmptyInstance(MCRMetaLangText.class));
176 map.put(MCRMetaAccessRule.class.getSimpleName(), getObjectCheckInstance(MCRMetaAccessRule.class));
177 map.put(MCRMetaNBN.class.getSimpleName(), getObjectCheckInstance(MCRMetaNBN.class));
178 map.put(MCRMetaISBN.class.getSimpleName(), getObjectCheckInstance(MCRMetaISBN.class));
179 map.put(MCRMetaClassification.class.getSimpleName(), new MCRMetaClassificationCheck());
180 map.put(MCRMetaHistoryDate.class.getSimpleName(), new MCRMetaHistoryDateCheck());
181 Properties props = MCRConfiguration.instance().getProperties(CONFIG_PREFIX + "class.");
182 for (Entry<Object, Object> entry : props.entrySet()) {
183 try {
184 String className = entry.getKey().toString();
185 className = className.substring(className.lastIndexOf('.') + 1);
186 LOGGER.info("Adding Validator " + entry.getValue().toString() + " for class " + className);
187 @SuppressWarnings("unchecked")
188 Class<? extends MCREditorMetadataValidator> cl = (Class<? extends MCREditorMetadataValidator>) Class.forName(entry
189 .getValue().toString());
190 map.put(className, cl.newInstance());
191 } catch (Exception e) {
192 final String msg = "Cannot instantiate " + entry.getValue() + " as validator for class " + entry.getKey();
193 LOGGER.error(msg);
194 throw new MCRException(msg, e);
195 }
196 }
197 return map;
198 }
199
200 @SuppressWarnings("unchecked")
201 public static Class<? extends MCRMetaInterface> getClass(String mcrclass) throws ClassNotFoundException {
202 Class<? extends MCRMetaInterface> clazz = CLASS_MAP.get(mcrclass);
203 if (clazz == null) {
204 clazz = (Class<? extends MCRMetaInterface>) Class.forName("org.mycore.datamodel.metadata." + mcrclass);
205 CLASS_MAP.put(mcrclass, clazz);
206 }
207 return clazz;
208 }
209
210 public static String checkMetaObject(Element datasubtag, Class<? extends MCRMetaInterface> metaClass, boolean keepLang) {
211 if (!keepLang) {
212 datasubtag.removeAttribute("lang", XML_NAMESPACE);
213 }
214 MCRMetaInterface test = null;
215 try {
216 test = metaClass.newInstance();
217 } catch (Exception e) {
218 throw new MCRException("Could not instantiate " + metaClass.getCanonicalName());
219 }
220 test.setFromDOM(datasubtag);
221
222 if (!test.isValid()) {
223 throw new MCRException("Element " + datasubtag.getName() + " is not valid.");
224 }
225 return null;
226 }
227
228 public static String checkMetaObjectWithLang(Element datasubtag, Class<? extends MCRMetaInterface> metaClass) {
229 if (datasubtag.getAttribute("lang") != null) {
230 datasubtag.getAttribute("lang").setNamespace(XML_NAMESPACE);
231 LOGGER.warn("namespace add for xml:lang attribute in " + datasubtag.getName());
232 }
233 return checkMetaObject(datasubtag, metaClass, true);
234 }
235
236 public static String checkMetaObjectWithLangNotEmpty(Element datasubtag, Class<? extends MCRMetaInterface> metaClass) {
237 String text = datasubtag.getTextTrim();
238 if ((text == null) || (text.length() == 0)) {
239 return "Element " + datasubtag.getName() + " has no text.";
240 }
241 return checkMetaObjectWithLang(datasubtag, metaClass);
242 }
243
244 public static String checkMetaObjectWithLinks(Element datasubtag, Class<? extends MCRMetaInterface> metaClass) {
245 if (datasubtag.getAttributeValue("href") == null && datasubtag.getAttributeValue("href", XLINK_NAMESPACE) == null) {
246 return datasubtag.getName() + " has no href attribute defined";
247 }
248 if (datasubtag.getAttribute("xtype") != null) {
249 datasubtag.getAttribute("xtype").setNamespace(XLINK_NAMESPACE).setName("type");
250 } else if (datasubtag.getAttribute("type") != null && datasubtag.getAttribute("type", XLINK_NAMESPACE) == null) {
251 datasubtag.getAttribute("type").setNamespace(XLINK_NAMESPACE);
252 LOGGER.warn("namespace add for xlink:type attribute in " + datasubtag.getName());
253 }
254 if (datasubtag.getAttribute("href") != null) {
255 datasubtag.getAttribute("href").setNamespace(XLINK_NAMESPACE);
256 LOGGER.warn("namespace add for xlink:href attribute in " + datasubtag.getName());
257 }
258
259 if (datasubtag.getAttribute("title") != null) {
260 datasubtag.getAttribute("title").setNamespace(XLINK_NAMESPACE);
261 LOGGER.warn("namespace add for xlink:title attribute in " + datasubtag.getName());
262 }
263
264 if (datasubtag.getAttribute("label") != null) {
265 datasubtag.getAttribute("label").setNamespace(XLINK_NAMESPACE);
266 LOGGER.warn("namespace add for xlink:label attribute in " + datasubtag.getName());
267 }
268 return checkMetaObject(datasubtag, metaClass, false);
269 }
270
271 static MCREditorMetadataValidator getObjectCheckInstance(final Class<? extends MCRMetaInterface> clazz) {
272 return new MCREditorMetadataValidator() {
273 public String checkDataSubTag(Element datasubtag) {
274 return MCREditorOutValidator.checkMetaObject(datasubtag, clazz, false);
275 }
276 };
277 }
278
279 static MCREditorMetadataValidator getObjectCheckWithLangInstance(final Class<? extends MCRMetaInterface> clazz) {
280 return new MCREditorMetadataValidator() {
281 public String checkDataSubTag(Element datasubtag) {
282 return MCREditorOutValidator.checkMetaObjectWithLang(datasubtag, clazz);
283 }
284 };
285 }
286
287 static MCREditorMetadataValidator getObjectCheckWithLangNotEmptyInstance(final Class<? extends MCRMetaInterface> clazz) {
288 return new MCREditorMetadataValidator() {
289 public String checkDataSubTag(Element datasubtag) {
290 return MCREditorOutValidator.checkMetaObjectWithLangNotEmpty(datasubtag, clazz);
291 }
292 };
293 }
294
295 static MCREditorMetadataValidator getObjectCheckWithLinksInstance(final Class<? extends MCRMetaInterface> clazz) {
296 return new MCREditorMetadataValidator() {
297 public String checkDataSubTag(Element datasubtag) {
298 return MCREditorOutValidator.checkMetaObjectWithLinks(datasubtag, clazz);
299 }
300 };
301 }
302
303 static class MCRMetaHistoryDateCheck implements MCREditorMetadataValidator {
304 public String checkDataSubTag(Element datasubtag) {
305 @SuppressWarnings("unchecked")
306 List<Element> children = datasubtag.getChildren("text");
307 if (children.size() == 0)
308 return "history date is empty";
309 for (int i = 0; i < children.size(); i++) {
310 Element child = children.get(i);
311 if (child.getAttribute("lang") != null) {
312 child.getAttribute("lang").setNamespace(XML_NAMESPACE);
313 LOGGER.warn("namespace add for xml:lang attribute in " + datasubtag.getName());
314 }
315 }
316 return checkMetaObjectWithLang(datasubtag, MCRMetaHistoryDate.class);
317 }
318 }
319
320 static class MCRMetaClassificationCheck implements MCREditorMetadataValidator {
321 public String checkDataSubTag(Element datasubtag) {
322 String categid = datasubtag.getAttributeValue("categid");
323 if (categid == null) {
324 return "Attribute categid is empty";
325 }
326 return checkMetaObject(datasubtag, MCRMetaClassification.class, false);
327 }
328 }
329
330 static class MCRMetaAdressCheck implements MCREditorMetadataValidator {
331 public String checkDataSubTag(Element datasubtag) {
332 if (datasubtag.getChildren().size() == 0)
333 return "adress is empty";
334 return checkMetaObjectWithLang(datasubtag, MCRMetaAddress.class);
335 }
336 }
337
338 /**
339 * @throws IOException
340 * @throws JDOMException
341 *
342 */
343 private void checkObject() throws JDOMException, IOException {
344 // add the namespaces (this is a workaround)
345 org.jdom.Element root = input.getRootElement();
346 root.addNamespaceDeclaration(XLINK_NAMESPACE);
347 root.addNamespaceDeclaration(XSI_NAMESPACE);
348 // set the schema
349 String mcr_schema = "datamodel-" + id.getTypeId() + ".xsd";
350 root.setAttribute("noNamespaceSchemaLocation", mcr_schema, XSI_NAMESPACE);
351 // check the label
352 String label = root.getAttributeValue("label");
353 if ((label == null) || ((label = label.trim()).length() == 0)) {
354 root.setAttribute("label", id.getId());
355 }
356 // remove the path elements from the incoming
357 org.jdom.Element pathes = root.getChild("pathes");
358 if (pathes != null) {
359 root.removeChildren("pathes");
360 }
361 org.jdom.Element structure = root.getChild("structure");
362 if (structure == null) {
363 root.addContent(new Element("structure"));
364 } else {
365 checkObjectStructure(structure);
366 }
367 Element metadata = root.getChild("metadata");
368 checkObjectMetadata(metadata);
369 org.jdom.Element service = root.getChild("service");
370 checkObjectService(root, service);
371 }
372
373 /**
374 * @param datatag
375 */
376 @SuppressWarnings("unchecked")
377 private boolean checkMetaTags(Element datatag) {
378 String mcrclass = datatag.getAttributeValue("class");
379 List datataglist = datatag.getChildren();
380 Iterator datatagIt = datataglist.iterator();
381
382 while (datatagIt.hasNext()) {
383 Element datasubtag = (Element) datatagIt.next();
384 MCREditorMetadataValidator validator = VALIDATOR_MAP.get(mcrclass);
385 String returns = null;
386 if (validator != null) {
387 returns = validator.checkDataSubTag(datasubtag);
388 } else {
389 LOGGER.warn("Tag <" + datatag.getName() + "> of type " + mcrclass
390 + " has no validator defined, fallback to default behaviour");
391 // try to create MCRMetaInterface instance
392 try {
393 Class<? extends MCRMetaInterface> metaClass = getClass(mcrclass);
394 // just checks if class would validate this element
395 returns = checkMetaObject(datasubtag, metaClass, true);
396 } catch (ClassNotFoundException e) {
397 throw new MCRException("Failure while trying fallback. Class not found: " + mcrclass, e);
398 }
399 }
400 if (returns != null) {
401 datatagIt.remove();
402 final String msg = datatag.getName() + ": " + returns;
403 errorlog.add(msg);
404 }
405 }
406 if (datatag.getChildren().size() == 0) {
407 return false;
408 }
409 return true;
410 }
411
412 /**
413 * @param service
414 * @throws IOException
415 * @throws JDOMException
416 */
417 @SuppressWarnings("unchecked")
418 private void checkObjectService(Element root, Element service) throws JDOMException, IOException {
419 if (service == null) {
420 service = new org.jdom.Element("service");
421 root.addContent(service);
422 }
423 List<Element> servicelist = service.getChildren();
424 boolean hasacls = false;
425 for (Element datatag : servicelist) {
426 checkMetaTags(datatag);
427 }
428 Collection<String> li = MCRAccessManager.getPermissionsForID(id.getId());
429 if ((li != null) && !li.isEmpty()) {
430 hasacls = true;
431 }
432 if (service.getChild("servacls") == null && !hasacls)
433 setDefaultObjectACLs(service);
434 }
435
436 /**
437 * The method add a default ACL-block.
438 *
439 * @param service
440 * @throws IOException
441 * @throws JDOMException
442 */
443 private void setDefaultObjectACLs(org.jdom.Element service) throws JDOMException, IOException {
444 if (!MCRConfiguration.instance().getBoolean("MCR.Access.AddObjectDefaultRule", true)) {
445 LOGGER.info("Adding object default acl rule is disabled.");
446 return;
447 }
448 String resourcetype = "/editor_default_acls_" + id.getTypeId() + ".xml";
449 String resourcebase = "/editor_default_acls_" + id.getBase() + ".xml";
450 // Read stylesheet and add user
451 InputStream aclxml = MCREditorOutValidator.class.getResourceAsStream(resourcebase);
452 if (aclxml == null) {
453 aclxml = MCREditorOutValidator.class.getResourceAsStream(resourcetype);
454 if (aclxml == null) {
455 LOGGER.warn("Can't find default object ACL file " + resourcebase.substring(1) + " or " + resourcetype.substring(1));
456 String resource = "/editor_default_acls.xml"; // fallback
457 aclxml = MCREditorOutValidator.class.getResourceAsStream(resource);
458 if (aclxml == null) {
459 return;
460 }
461 }
462 }
463 Document xml = SAX_BUILDER.build(aclxml);
464 Element acls = xml.getRootElement().getChild("servacls");
465 if (acls == null) {
466 return;
467 }
468 for (@SuppressWarnings("unchecked")
469 Iterator<Element> it = acls.getChildren().iterator(); it.hasNext();) {
470 Element acl = it.next();
471 Element condition = acl.getChild("condition");
472 if (condition == null) {
473 continue;
474 }
475 Element rootbool = condition.getChild("boolean");
476 if (rootbool == null) {
477 continue;
478 }
479 for (@SuppressWarnings("unchecked")
480 Iterator<Element> boolIt = rootbool.getChildren("boolean").iterator(); boolIt.hasNext();) {
481 Element orbool = boolIt.next();
482 for (@SuppressWarnings("unchecked")
483 Iterator<Element> condIt = orbool.getChildren("condition").iterator(); condIt.hasNext();) {
484 Element firstcond = condIt.next();
485 if (firstcond == null) {
486 continue;
487 }
488 String value = firstcond.getAttributeValue("value");
489 if (value == null)
490 continue;
491 if (value.equals("$CurrentUser")) {
492 String thisuser = MCRSessionMgr.getCurrentSession().getCurrentUserID();
493 firstcond.setAttribute("value", thisuser);
494 continue;
495 }
496 if (value.equals("$CurrentGroup")) {
497 String thisuser = MCRSessionMgr.getCurrentSession().getCurrentUserID();
498 String thisgroup = MCRUserMgr.instance().getPrimaryGroupIDOfUser(thisuser);
499 firstcond.setAttribute("value", thisgroup);
500 continue;
501 }
502 int i = value.indexOf("$CurrentIP");
503 if (i != -1) {
504 String thisip = MCRSessionMgr.getCurrentSession().getCurrentIP();
505 StringBuffer sb = new StringBuffer(64);
506 sb.append(value.substring(0, i)).append(thisip).append(value.substring(i + 10, value.length()));
507 firstcond.setAttribute("value", sb.toString());
508 continue;
509 }
510 }
511 }
512 }
513 service.addContent(acls.detach());
514 }
515
516 /**
517 * @param metadata
518 */
519 @SuppressWarnings("unchecked")
520 private void checkObjectMetadata(Element metadata) {
521 if (metadata.getAttribute("lang") != null) {
522 metadata.getAttribute("lang").setNamespace(XML_NAMESPACE);
523 }
524
525 List<Element> metadatalist = metadata.getChildren();
526 Iterator<Element> metaIt = metadatalist.iterator();
527
528 while (metaIt.hasNext()) {
529 Element datatag = (Element) metaIt.next();
530 if (!checkMetaTags(datatag)) {
531 // e.g. datatag is empty
532 LOGGER.debug("Removing element :" + datatag.getName());
533 metaIt.remove();
534 }
535 }
536 }
537
538 /**
539 * @param structure
540 */
541 @SuppressWarnings("unchecked")
542 private void checkObjectStructure(Element structure) {
543 List<Element> structurelist = structure.getChildren();
544 Iterator<Element> structIt = structurelist.iterator();
545
546 while (structIt.hasNext()) {
547 Element datatag = (Element) structIt.next();
548 if (!checkMetaTags(datatag)) {
549 // e.g. datatag is empty
550 structIt.remove();
551 }
552 }
553 }
554
555 /**
556 * The method add a default ACL-block.
557 *
558 * @param service
559 */
560 public static void setDefaultDerivateACLs(org.jdom.Element service) {
561 // Read stylesheet and add user
562 InputStream aclxml = MCREditorOutValidator.class.getResourceAsStream("/editor_default_acls_derivate.xml");
563 if (aclxml == null) {
564 LOGGER.warn("Can't find default derivate ACL file editor_default_acls_derivate.xml.");
565 return;
566 }
567 try {
568 org.jdom.Document xml = (SAX_BUILDER).build(aclxml);
569 org.jdom.Element acls = xml.getRootElement().getChild("servacls");
570 if (acls != null) {
571 service.addContent(acls.detach());
572 }
573 } catch (Exception e) {
574 LOGGER.warn("Error while parsing file editor_default_acls_derivate.xml.");
575 }
576 }
577
578 }