changeset 31:7d8ebe8ac8a2

create reader and check script for XML dumps.
author casties
date Wed, 24 Aug 2016 19:12:24 +0200
parents 2f19cdf8e60b
children 9c54842f5e86
files src/main/java/org/mpi/openmind/repository/utils/OM4StreamWriter.java src/main/java/org/mpi/openmind/repository/utils/OM4XmlEventReader.java src/main/java/org/mpi/openmind/scripts/CheckXmlExport.java
diffstat 3 files changed, 814 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/mpi/openmind/repository/utils/OM4StreamWriter.java	Tue Aug 23 17:43:09 2016 +0200
+++ b/src/main/java/org/mpi/openmind/repository/utils/OM4StreamWriter.java	Wed Aug 24 19:12:24 2016 +0200
@@ -19,6 +19,15 @@
 import org.mpi.openmind.repository.bo.Relation;
 import org.mpi.openmind.repository.services.PersistenceService;
 
+/**
+ * Export all entities and relations and definitions to XML.
+ * 
+ * Saves (real) entities and relations (i.e. assertions) and definitions 
+ * (i.e. definition entities and relations) in separate files.
+ * 
+ * @author jurzua, casties
+ *
+ */
 public class OM4StreamWriter {
 
     private static Logger logger = Logger.getLogger(OM4StreamWriter.class);
@@ -31,7 +40,7 @@
      * @param s
      * @return
      */
-    public static String defaultString(Object s) {
+    private static String defaultString(Object s) {
         if (s == null) {
             return "null";
         } else {
@@ -69,11 +78,11 @@
 
             if (type.equals(Node.TYPE_ABOX)) {
                 writer.writeStartElement(XMLUtil.OPENMIND_DATA);
-                writer.writeAttribute("version", "3.0");
+                writer.writeAttribute("version", "4.3");
                 entitiesCount = ps.getEntityCount(null).intValue();
             } else {
                 writer.writeStartElement(XMLUtil.META_DATA);
-                writer.writeAttribute("version", "3.0");
+                writer.writeAttribute("version", "4.3");
                 entitiesCount = ps.getEntityCount(Node.TYPE_TBOX).intValue();
             }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/mpi/openmind/repository/utils/OM4XmlEventReader.java	Wed Aug 24 19:12:24 2016 +0200
@@ -0,0 +1,368 @@
+/**
+ * 
+ */
+package org.mpi.openmind.repository.utils;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Class that reads an OM4 XML dump into lists of simple objects.
+ * 
+ * The constructor takes an InputStream.
+ * 
+ * The read() method reads the contents of the file into the members
+ * .entities and .relations.
+ * 
+ * The contents are Lists of OmEntities and OmRelations also holding
+ * Lists of omAttributes. 
+ * 
+ * @author casties
+ *
+ */
+public class OM4XmlEventReader {
+
+    private static Logger logger = Logger.getLogger(OM4XmlEventReader.class);
+    
+    public OM4XmlEventReader(InputStream xmlStream) {
+        super();
+        this.xmlStream = xmlStream;
+    }
+
+    InputStream xmlStream;
+    
+    public int numEntities;
+    public List<OmEntity> entities;
+    private int entCnt = 0;
+
+    public int numRelations;
+    public List<OmRelation> relations;
+    private int relCnt = 0;
+
+    /**
+     * Simple class holding the representation of an OpenMind Attribute from XML.
+     *  
+     * @author casties
+     */
+    public class OmAttribute {
+        public Map<String, String> xmlAtts;
+        public String value;
+        
+        public String getId() {
+            return xmlAtts.get("id");
+        }
+    }
+
+    /**
+     * Simple class holding the representation of an OpenMind Entity from XML.
+     *  
+     * @author casties
+     */
+    public class OmEntity {
+        public Map<String, String> xmlAtts;
+        public String value;
+        public List<OmAttribute> attributes;
+
+        public String getId() {
+            return xmlAtts.get("id");
+        }
+    }
+
+    /**
+     * Simple class holding the representation of an OpenMind Relation from XML.
+     *  
+     * @author casties
+     */
+    public class OmRelation {
+        public Map<String, String> xmlAtts;
+        public String value;
+        public List<OmAttribute> attributes;
+
+        public String getId() {
+            return xmlAtts.get("id");
+        }
+    }
+
+    /**
+     * Reads the XML from xmlStream and populates entities and relations.
+     * 
+     * @throws XMLStreamException
+     */
+    public void read() throws XMLStreamException {
+        XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+        XMLEventReader reader = inputFactory.createXMLEventReader(xmlStream, "UTF-8");
+        try {
+            while (reader.hasNext()) {
+                XMLEvent e = reader.nextEvent();
+                if (e.isStartDocument()) {
+                    continue;
+                } else if (e.isStartElement()) {
+                    StartElement es = e.asStartElement();
+                    String lname = es.getName().getLocalPart();
+                    if (lname == XMLUtil.ENTITIES) {
+                        entities = processEntities(es, reader);
+                    } else if (lname == XMLUtil.RELATIONS) {
+                        relations = processRelations(es, reader);
+                    }
+                }
+            }
+        } finally {
+            reader.close();
+        }
+    }
+
+    /**
+     * Process the entities tag and its contents.
+     * 
+     * @param elem
+     * @param reader
+     * @return
+     * @throws XMLStreamException
+     */
+    private List<OmEntity> processEntities(StartElement elem, XMLEventReader reader) throws XMLStreamException {
+        logger.debug("loading entities...");
+        // get number attribute
+        Attribute numa = elem.getAttributeByName(new QName("number"));
+        if (numa != null) {
+            numEntities = Integer.parseInt(numa.getValue());
+        }
+        // start reading sub-elements
+        List<OmEntity> entities = new ArrayList<OmEntity>();
+        while (reader.hasNext()) {
+            XMLEvent e = reader.nextEvent();
+            if (e.isStartElement()) {
+                // start of next element
+                StartElement es = e.asStartElement();
+                String lname = es.getName().getLocalPart();
+                if (lname == XMLUtil.ENTITY) {
+                    // process entity tag
+                    entities.add(processEntity(es, reader));
+                }
+            } else if (e.isEndElement()) {
+                EndElement ee = e.asEndElement();
+                if (ee.getName().getLocalPart().equals(XMLUtil.ENTITIES)) {
+                    // end of this element
+                    break;
+                } else {
+                    logger.warn("Unexpected EndElement: "+ee);
+                }
+            }
+        }
+        return entities;
+    }
+
+    /**
+     * Process the entity tag and its contents.
+     * 
+     * @param elem
+     * @param reader
+     * @return
+     * @throws XMLStreamException
+     */
+    private OmEntity processEntity(StartElement elem, XMLEventReader reader) throws XMLStreamException {
+        //logger.debug("entity");
+        OmEntity ent = new OmEntity();
+        Map<String, String> xmlAtts = new HashMap<String, String>();
+        @SuppressWarnings("unchecked")
+        Iterator<Attribute> atts = elem.getAttributes();
+        while (atts.hasNext()) {
+            Attribute att = atts.next();
+            xmlAtts.put(att.getName().getLocalPart(), att.getValue());
+        }
+        ent.xmlAtts = xmlAtts;
+        // start reading sub-elements
+        ent.attributes = new ArrayList<OmAttribute>();
+        while (reader.hasNext()) {
+            XMLEvent e = reader.nextEvent();
+            if (e.isStartElement()) {
+                // start of next element
+                StartElement es = e.asStartElement();
+                String lname = es.getName().getLocalPart();
+                if (lname == XMLUtil.ATTRIBUTES) {
+                    // ignore attributes tag
+                    continue;
+                }
+                if (lname == XMLUtil.ATTRIBUTE) {
+                    // process attribute tag
+                    ent.attributes.add(processAttribute(es, reader));
+                }
+            } else if (e.isCharacters()) {
+                // text content
+                Characters ec = e.asCharacters();
+                if (ent.value == null) {
+                    ent.value = ec.getData();
+                } else {
+                    ent.value += ec.getData();
+                }
+            } else if (e.isEndElement()) {
+                EndElement ee = e.asEndElement();
+                if (ee.getName().getLocalPart().equals(XMLUtil.ENTITY)) {
+                    // end of this element
+                    break;
+                }
+            }
+        }
+        if (++entCnt % 500 == 0) {
+            logger.debug(""+entCnt+" entities read...");
+        }
+        return ent;
+    }
+
+    /**
+     * Process the relations tag and its contents.
+     * 
+     * @param elem
+     * @param reader
+     * @return
+     * @throws XMLStreamException
+     */
+    private List<OmRelation> processRelations(StartElement elem, XMLEventReader reader) throws XMLStreamException {
+        logger.debug("loading relations...");
+        // get number attribute
+        Attribute numa = elem.getAttributeByName(new QName("number"));
+        if (numa != null) {
+            numRelations = Integer.parseInt(numa.getValue());
+        }
+        // start reading sub-elements
+        List<OmRelation> rels = new ArrayList<OmRelation>();
+        while (reader.hasNext()) {
+            XMLEvent e = reader.nextEvent();
+            if (e.isStartElement()) {
+                // start of next element
+                StartElement es = e.asStartElement();
+                String lname = es.getName().getLocalPart();
+                if (lname == XMLUtil.RELATION) {
+                    // process entity tag
+                    rels.add(processRelation(es, reader));
+                }
+            } else if (e.isEndElement()) {
+                EndElement ee = e.asEndElement();
+                if (ee.getName().getLocalPart().equals(XMLUtil.RELATIONS)) {
+                    // end of this element
+                    break;
+                } else {
+                    logger.warn("Unexpected EndElement: "+ee);
+                }
+            }
+        }
+        return rels;
+    }
+
+
+    /**
+     * Process the relation tag and its contents.
+     * 
+     * @param elem
+     * @param reader
+     * @return
+     * @throws XMLStreamException
+     */
+    private OmRelation processRelation(StartElement elem, XMLEventReader reader) throws XMLStreamException {
+        //logger.debug("relation");
+        OmRelation rel = new OmRelation();
+        Map<String, String> xmlAtts = new HashMap<String, String>();
+        @SuppressWarnings("unchecked")
+        Iterator<Attribute> atts = elem.getAttributes();
+        while (atts.hasNext()) {
+            Attribute att = atts.next();
+            xmlAtts.put(att.getName().getLocalPart(), att.getValue());
+        }
+        rel.xmlAtts = xmlAtts;
+        // start reading sub-elements
+        rel.attributes = new ArrayList<OmAttribute>();
+        while (reader.hasNext()) {
+            XMLEvent e = reader.nextEvent();
+            if (e.isStartElement()) {
+                // start of next element
+                StartElement es = e.asStartElement();
+                String lname = es.getName().getLocalPart();
+                if (lname == XMLUtil.ATTRIBUTES) {
+                    // ignore attributes tag
+                    continue;
+                }
+                if (lname == XMLUtil.ATTRIBUTE) {
+                    // process attribute tag
+                    rel.attributes.add(processAttribute(es, reader));
+                }
+            } else if (e.isCharacters()) {
+                // text content
+                Characters ec = e.asCharacters();
+                if (rel.value == null) {
+                    rel.value = ec.getData();
+                } else {
+                    rel.value += ec.getData();
+                }
+            } else if (e.isEndElement()) {
+                EndElement ee = e.asEndElement();
+                if (ee.getName().getLocalPart().equals(XMLUtil.RELATION)) {
+                    // end of this element
+                    break;
+                }
+            }
+        }
+        if (++relCnt % 100 == 0) {
+            logger.debug(""+relCnt+" relations read...");
+        }
+        return rel;
+    }
+
+    /**
+     * Process the attribute tag and its contents.
+     * 
+     * @param elem
+     * @param reader
+     * @return
+     * @throws XMLStreamException
+     */
+    private OmAttribute processAttribute(StartElement elem, XMLEventReader reader) throws XMLStreamException {
+        //logger.debug("attribute");
+        OmAttribute oma = new OmAttribute();
+        Map<String, String> xmlAtts = new HashMap<String, String>();
+        @SuppressWarnings("unchecked")
+        Iterator<Attribute> atts = elem.getAttributes();
+        while (atts.hasNext()) {
+            Attribute att = atts.next();
+            xmlAtts.put(att.getName().getLocalPart(), att.getValue());
+        }
+        oma.xmlAtts = xmlAtts;
+        // start reading sub-elements
+        while (reader.hasNext()) {
+            XMLEvent e = reader.nextEvent();
+            if (e.isCharacters()) {
+                // text content
+                Characters ec = e.asCharacters();
+                if (oma.value == null) {
+                    oma.value = ec.getData();
+                } else {
+                    oma.value += ec.getData();
+                }
+            } else if (e.isEndElement()) {
+                EndElement ee = e.asEndElement();
+                if (ee.getName().getLocalPart().equals(XMLUtil.ATTRIBUTE)) {
+                    // end of this element
+                    break;
+                } else {
+                    logger.warn("Unexpected EndElement: "+ee);
+                }
+            }
+        }
+        return oma;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/mpi/openmind/scripts/CheckXmlExport.java	Wed Aug 24 19:12:24 2016 +0200
@@ -0,0 +1,434 @@
+package org.mpi.openmind.scripts;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.stream.XMLStreamException;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.mpi.openmind.cache.WrapperService;
+import org.mpi.openmind.configuration.ConfigurationService;
+import org.mpi.openmind.repository.bo.Attribute;
+import org.mpi.openmind.repository.bo.Entity;
+import org.mpi.openmind.repository.bo.Relation;
+import org.mpi.openmind.repository.services.PersistenceService;
+import org.mpi.openmind.repository.services.ServiceRegistry;
+import org.mpi.openmind.repository.utils.OM4XmlEventReader;
+import org.mpi.openmind.repository.utils.OM4XmlEventReader.OmAttribute;
+import org.mpi.openmind.repository.utils.OM4XmlEventReader.OmEntity;
+import org.mpi.openmind.repository.utils.OM4XmlEventReader.OmRelation;
+
+
+/**
+ * Class that reads an OM4 XML dump and compares the content to the content of the database.
+ * 
+ * Does not deal with definitions.
+ * 
+ * @author casties
+ */
+public class CheckXmlExport {
+    
+	private static Logger logger = Logger.getLogger(OM4XmlEventReader.class);
+    private static ConfigurationService configurationService;
+    private static PersistenceService persistenceService;
+
+	public static void execute(String fileName){
+	    logger.setLevel(Level.DEBUG);
+	    
+	    // set up OpenMind
+		ServiceRegistry services = new ServiceRegistry();
+        configurationService = services.getConfigurationService();
+        persistenceService = services.getPS();
+        
+        // read XML file
+        OM4XmlEventReader xmlReader = importXml(fileName);
+        
+        // check content of XML file
+		checkEntsAndRels(xmlReader, services.getWrapper());
+	}
+	
+    /**
+     * Reads the XML file and returns the OM4XmlEventReader with the content.
+     *  
+     * @param fileName
+     * @return
+     */
+    private static OM4XmlEventReader importXml(String fileName){
+	    // get path from configuration service
+        String schedulingPath = configurationService.getSchedulingPath();
+	    //String schedulingPath = "/tmp";
+        if (schedulingPath.charAt(schedulingPath.length() - 1) != '/'){
+            schedulingPath += "/";
+        }
+        // import XML
+        logger.info("Import entities from XML file: " + fileName);
+        File xmlFile = new File(schedulingPath + fileName);
+        try {
+            OM4XmlEventReader reader = new OM4XmlEventReader(new FileInputStream(xmlFile));
+            reader.read();
+            logger.info("supposed number of entities: "+reader.numEntities);
+            logger.info("number of entities read: "+reader.entities.size());
+            logger.info("supposed number of relations: "+reader.numRelations);
+            logger.info("number of relations read: "+reader.relations.size());
+            return reader;
+        } catch (XMLStreamException e) {
+            logger.error("Error reading XML", e);
+        } catch (FileNotFoundException e) {
+            logger.error("File not found: "+xmlFile);
+        }
+        return null;
+	}
+	
+    /**
+     * Compares the entities and relations in the content of the OM4XmlEventReader with
+     * the content of the database.
+     * 
+     * @param xmlReader
+     * @param wrapper
+     */
+    private static void checkEntsAndRels(OM4XmlEventReader xmlReader, WrapperService wrapper) {
+        /*
+         * create hashes by id as indices
+         */
+        logger.info("indexing entities...");
+        List<OmEntity> xmlEntList = xmlReader.entities;
+        List<OmRelation> xmlRelList = xmlReader.relations;
+        Map<Long, OmEntity> xmlEntMap = new HashMap<Long, OmEntity>(xmlEntList.size());
+        for (OmEntity e : xmlEntList) {
+            Long id = Long.parseLong(e.getId());
+            if (xmlEntMap.put(id, e) != null) {
+                logger.error("XML entity with duplicate id: "+id);
+            }
+        }
+        logger.info("indexing relations...");
+        Map<Long,OmRelation> xmlRelMap = new HashMap<Long, OmRelation>(xmlRelList.size());
+        for (OmRelation e : xmlRelList) {
+            Long id = Long.parseLong(e.getId());
+            if (xmlRelMap.put(id, e) != null) {
+                logger.error("XML relation with duplicate id: "+id);                
+            };
+        }
+
+        List<Relation> relations = new ArrayList<Relation>(xmlRelList.size());
+
+        /*
+         * load all entities from OM and compare with XML
+         */
+        int itemsPerPage = 500;
+        int entitiesCount = persistenceService.getEntityCount(null).intValue();
+        int numberOfPages = entitiesCount / itemsPerPage;
+        int entcnt = 0;
+        int entErrCnt = 0;
+        int relcnt = 0;
+        int relErrCnt = 0;
+        for (int currentPage = 0; currentPage <= numberOfPages; currentPage++) {
+            int startRecord = currentPage * itemsPerPage;
+            // get page of entities from OM
+            List<Entity> entities = persistenceService.getEntityPage(null, startRecord, itemsPerPage);
+
+            for (Entity ent : entities) {
+                if (ent.isLightweight()) {
+                    ent = persistenceService.getEntityContent(ent);
+                }
+
+                // save (source)relations to list
+                relations.addAll(ent.getSourceRelations());
+                
+                // get xml entity
+                Long id = ent.getId();
+                OmEntity xmlEnt = xmlEntMap.get(id);
+                if (xmlEnt == null) {
+                    logger.error("OM entity not found in XML: "+id);
+                    entErrCnt += 1;
+                } else if (!entEquals(ent, xmlEnt)) {
+                    logger.error("OM entity different from XML: "+id);
+                    entErrCnt += 1;
+                    //return;
+                }
+                
+                if ((++entcnt % 500) == 0) {
+                    logger.debug(""+entcnt+" entities processed...");
+                }
+            }
+        }
+        
+        /*
+         * go through all relations from OM
+         */
+        for (Relation rel : relations) {
+            // get xml entity
+            Long id = rel.getId();
+            OmRelation xmlRel = xmlRelMap.get(id);
+            if (xmlRel == null) {
+                logger.error("OM relation not found in XML: "+id);
+                relErrCnt += 1;
+            } else if (!relEquals(rel, xmlRel)) {
+                logger.error("OM relation different from XML: "+id);                    
+                relErrCnt += 1;
+            }
+            
+            if ((++relcnt % 500) == 0) {
+                logger.debug(""+relcnt+" relations processed...");
+            }
+           
+        }
+        
+        logger.info("Errors comparing entities: "+entErrCnt);
+        logger.info("Errors comparing relations: "+relErrCnt);
+        logger.info("Done.");
+    }
+
+
+	
+	/**
+	 * Compares a OM-Entity with an entity from an OM4XmlEventReader.
+	 * 
+	 * @param entity
+	 * @param xmlEnt
+	 * @return
+	 */
+	private static boolean entEquals(Entity entity, OmEntity xmlEnt) {
+        /*
+         * check XML attributes
+         */
+        if (!equalsString(entity.getObjectClass(), xmlEnt.xmlAtts.get("object-class"))) {
+            logger.error("Entities object-class not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+        if (!equalsString(entity.getRowId(), xmlEnt.xmlAtts.get("row-id"))) {
+            logger.error("Entities row-id not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+        if (!equalsString(entity.getVersion(), xmlEnt.xmlAtts.get("version"))) {
+            logger.error("Entities version not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+        if (!equalsString(entity.getModificationTime(), xmlEnt.xmlAtts.get("mtime"))) {
+            logger.error("Entities mtime not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+        if (!equalsString(entity.getUser(), xmlEnt.xmlAtts.get("user"))) {
+            logger.error("Entities user not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+        if (!equalsString(entity.getIsPublic(), (xmlEnt.xmlAtts.get("public") == null) ? "false" : xmlEnt.xmlAtts.get("public"))) {
+            logger.error("Entities public not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+
+        /*
+         * check OpenMind attributes of this entity
+         */
+        if (entity.getAttributes().size() > 0 && entity.getAttributes().size() == xmlEnt.attributes.size()) {
+            if (!attsEquals(entity.getAttributes(), xmlEnt.attributes)) {
+                logger.error("Entities attribute values not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+                return false; 
+            }
+        } else if (entity.getAttributes().size() != xmlEnt.attributes.size()) {
+            logger.error("Entities attribute numbers not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        }
+
+        /*
+         * check own value
+         */
+        if (!equalsString(entity.getOwnValue(), xmlEnt.value)) {
+            logger.error("Entities own-value not equal: om["+entity.getId()+"] xml["+xmlEnt.getId()+"]");
+            return false;
+        };
+
+        return true;
+    }
+
+	
+    /**
+     * Compares a OM-Relation with a relation from an OM4XmlEventReader.
+     * 
+     * @param relation
+     * @param xmlRel
+     * @return
+     */
+    private static boolean relEquals(Relation relation, OmRelation xmlRel) {
+        /*
+         * check XML attributes
+         */
+        if (!equalsString(relation.getObjectClass(), xmlRel.xmlAtts.get("object-class"))) {
+            logger.error("Relations object-class not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getRowId(), xmlRel.xmlAtts.get("row-id"))) {
+            logger.error("Relations row-id not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getSourceId(), xmlRel.xmlAtts.get("source-id"))) {
+            logger.error("Relations source-id not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getTargetId(), xmlRel.xmlAtts.get("target-id"))) {
+            logger.error("Relations target-id not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getVersion(), xmlRel.xmlAtts.get("version"))) {
+            logger.error("Relations version not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getModificationTime(), xmlRel.xmlAtts.get("mtime"))) {
+            logger.error("Relations mtime not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getUser(), xmlRel.xmlAtts.get("user"))) {
+            logger.error("Relations user not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+        if (!equalsString(relation.getIsPublic(), (xmlRel.xmlAtts.get("public") == null) ? "false" : xmlRel.xmlAtts.get("public"))) {
+            logger.error("Relations public not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+
+        /*
+         * check OpenMind attributes of this relation
+         */
+        if (relation.getAttributes().size() > 0 && relation.getAttributes().size() == xmlRel.attributes.size()) {
+            if (!attsEquals(relation.getAttributes(), xmlRel.attributes)) {
+                logger.error("Relations attribute values not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+                return false; 
+            }
+        } else if (relation.getAttributes().size() != xmlRel.attributes.size()) {
+            logger.error("Relations attribute numbers not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        }
+
+        /*
+         * check own value
+         */
+        if (!equalsString(relation.getOwnValue(), xmlRel.value)) {
+            logger.error("Relations own-value not equal: om["+relation.getId()+"] xml["+xmlRel.getId()+"]");
+            return false;
+        };
+
+        return true;
+    }
+
+
+    /**
+     * Compares a List of OM-Attributes with a List of attributes from an OM4XmlEventReader.
+     * 
+     * @param atts
+     * @param xmlAtts
+     * @return
+     */
+    private static boolean attsEquals(List<Attribute> atts, List<OmAttribute> xmlAtts) {
+        for (Attribute att : atts) {
+            // get the matching attribute from the XML file
+            OmAttribute xmlAtt = null;
+            for (OmAttribute xa : xmlAtts) {
+                if (equalsString(att.getId(), xa.getId())) {
+                    xmlAtt = xa;
+                    break;
+                }
+            }
+            if (xmlAtt == null) {
+                logger.error("Attributes id does not match: om["+att.getId()+"] xml[null]");
+                return false;
+            }
+            /*
+             * check the attributes of the attribute
+             */
+            if (!equalsString(att.getRowId(), xmlAtt.xmlAtts.get("row-id"))) {
+                logger.error("Attributes row-id not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getName(), xmlAtt.xmlAtts.get("name"))) {
+                logger.error("Attributes name not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getContentType(), xmlAtt.xmlAtts.get("content-type"))) {
+                logger.error("Attributes content-type not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getVersion(), xmlAtt.xmlAtts.get("version"))) {
+                logger.error("Attributes version not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getModificationTime(), xmlAtt.xmlAtts.get("mtime"))) {
+                logger.error("Attributes mtime not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getUser(), xmlAtt.xmlAtts.get("user"))) {
+                logger.error("Attributes user not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            if (!equalsString(att.getIsPublic(), (xmlAtt.xmlAtts.get("public") == null) ? "false" : xmlAtt.xmlAtts.get("public"))) {
+                logger.error("Attributes public not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+            /*
+             * check own value of the attribute
+             */
+            if (!equalsStringCR(att.getOwnValue(), xmlAtt.value)) {
+                logger.error("Attributes own-value not equal: om["+att.getId()+"] xml["+xmlAtt.getId()+"]");
+                return false;
+            };
+        }
+        return true;
+    }
+
+	
+    /**
+     * Compares an Object with a toString() method to a String.
+     * 
+     * Treats null, "" and "null" as same.
+     * 
+     * @param o
+     * @param s
+     * @return
+     */
+    private static boolean equalsString(Object o, String s) {
+	    if (o == null || o instanceof String) {
+	        // treat all forms of null equally
+	        if ((o == null || ((String) o).isEmpty() || ((String) o).equals("null") 
+	                && (s == null || s.isEmpty()) || s.equals("null"))) {
+	            return true;
+	        } else {
+	            return StringUtils.equals((String) o, s);
+	        }
+	    } else {
+	        // o is not String
+	        return StringUtils.equals(o.toString(), s);
+	    }
+	}
+	
+    /**
+     * Compares two Strings ignoring CR characters.
+     * 
+     * Treats null and "" as same.
+     * 
+     * @param o
+     * @param s
+     * @return
+     */
+    private static boolean equalsStringCR(String o, String s) {
+        if ((o == null || o.isEmpty()) && (s == null || s.isEmpty())) {
+            return true;
+        }
+        return StringUtils.equals(o.replace("\r", ""), s.replace("\r", ""));
+    }
+
+    public static void main(String[] args){
+        if (args.length > 0) {
+            String fn = args[0];
+            execute(fn);
+        } else {
+            System.out.println("Parameter/s not found! Should be: filename");
+            System.exit(1);
+        }
+		System.exit(0);
+	}
+}