/** * */ package de.mpiwg.itgroup.annotations.neo4j; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import org.apache.log4j.Logger; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import de.mpiwg.itgroup.annotations.Actor; import de.mpiwg.itgroup.annotations.Annotation; import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes; import de.mpiwg.itgroup.annotations.Person; /** * @author casties * */ public class AnnotationStore { protected static Logger logger = Logger.getLogger(AnnotationStore.class); protected GraphDatabaseService graphDb; public static enum NodeTypes { ANNOTATION, PERSON, TARGET } protected List> nodeIndexes; public static enum RelationTypes implements RelationshipType { ANNOTATES, CREATED, PERMITS } public static String ANNOTATION_URI_BASE = "http://entities.mpiwg-berlin.mpg.de/annotations/"; public AnnotationStore(GraphDatabaseService graphDb) { super(); this.graphDb = graphDb; nodeIndexes = new ArrayList>(3); // List.set(enum.ordinal(), val) seems not to work. nodeIndexes.add(NodeTypes.ANNOTATION.ordinal(), graphDb.index().forNodes("annotations")); nodeIndexes.add(NodeTypes.PERSON.ordinal(), graphDb.index().forNodes("persons")); nodeIndexes.add(NodeTypes.TARGET.ordinal(), graphDb.index().forNodes("targets")); } protected Index getNodeIndex(NodeTypes type) { return nodeIndexes.get(type.ordinal()); } /** * Returns the Annotation with the given id. * * @param id * @return */ public Annotation getAnnotationById(String id) { Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle(); Annotation annot = createAnnotationFromNode(annotNode); return annot; } /** * Returns an Annotation object from an annotation-Node. * * @param annotNode * @return */ public Annotation createAnnotationFromNode(Node annotNode) { Annotation annot = new Annotation(); annot.setUri((String) annotNode.getProperty("id", null)); annot.setBodyText((String) annotNode.getProperty("bodyText", null)); annot.setBodyUri((String) annotNode.getProperty("bodyUri", null)); // get annotation target from relation Iterable targetRels = annotNode .getRelationships(RelationTypes.ANNOTATES); for (Relationship targetRel : targetRels) { Node target = targetRel.getEndNode(); annot.setTargetBaseUri((String) target.getProperty("uri", null)); // just the first one break; } annot.setTargetFragment((String) annotNode.getProperty( "targetFragment", null)); String ft = (String) annotNode.getProperty("fragmentType", null); if (ft != null) { annot.setFragmentType(FragmentTypes.valueOf(ft)); } // get creator from relation Iterable creatorRels = annotNode .getRelationships(RelationTypes.CREATED); for (Relationship creatorRel : creatorRels) { Node creatorNode = creatorRel.getStartNode(); String uri = (String) creatorNode.getProperty("uri", null); String name = (String) creatorNode.getProperty("name", null); Actor creator = new Person(uri, name); annot.setCreator(creator); // just the first one break; } annot.setCreated((String) annotNode.getProperty("created", null)); return annot; } /** * Store a new annotation in the store or update an existing one. Returns * the stored annotation. * * @param annot * @return */ public Annotation storeAnnotation(Annotation annot) { Node annotNode = null; Transaction tx = graphDb.beginTx(); try { /* * create or get the annotation */ String id = annot.getUri(); if (id == null) { id = createRessourceURI("annot:"); } annotNode = getOrCreateAnnotationNode(id); /* * the annotation body */ String bodyText = annot.getBodyText(); if (bodyText != null) { annotNode.setProperty("bodyText", bodyText); } String bodyUri = annot.getBodyUri(); if (bodyUri != null) { annotNode.setProperty("bodyUri", bodyUri); } /* * the annotation target */ String targetBaseUri = annot.getTargetBaseUri(); if (targetBaseUri != null) { Node target = getOrCreateTargetNode(targetBaseUri); getOrCreateRelation(annotNode, RelationTypes.ANNOTATES, target); } /* * The fragment part of the annotation target. */ String targetFragment = annot.getTargetFragment(); FragmentTypes fragmentType = annot.getFragmentType(); if (targetFragment != null) { annotNode.setProperty("targetFragment", targetFragment); annotNode.setProperty("fragmentType", fragmentType.name()); } /* * The creator of this annotation. */ Actor creator = annot.getCreator(); if (creator != null) { Node creatorNode = getOrCreatePersonNode(creator); getOrCreateRelation(creatorNode, RelationTypes.CREATED, annotNode); } /* * The creation date of this annotation. */ String created = annot.getCreated(); if (created != null) { annotNode.setProperty("created", created); } tx.success(); } finally { tx.finish(); } // re-read and return annotation Annotation storedAnnot = createAnnotationFromNode(annotNode); return storedAnnot; } /** * Deletes the annotation with the given id. * * @param id */ public void deleteById(String id) { Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle(); if (annotNode != null) { // delete related objects Transaction tx = graphDb.beginTx(); try { for (Relationship rel : annotNode.getRelationships()) { // delete relation and the related node if it has no other relations Node other = rel.getOtherNode(annotNode); rel.delete(); if (! other.hasRelationship()) { deleteNode(other); } } if (! annotNode.hasRelationship()) { deleteNode(annotNode); } else { logger.error("deleteById: unable to delete: Node still has relations."); } tx.success(); } finally { tx.finish(); } } } /** * Returns all annotations with the given uri and/or user. * * @param uri * @param userUri * @param limit * @param offset * @return */ public List searchByUriUser(String targetUri, String userUri, String limit, String offset) { List annotations = new ArrayList(); if (targetUri != null) { // there should be only one Node target = getNodeIndex(NodeTypes.TARGET).get("uri", targetUri).getSingle(); if (target != null) { Iterable relations = target .getRelationships(RelationTypes.ANNOTATES); for (Relationship relation : relations) { Node ann = relation.getStartNode(); if (ann.getProperty("TYPE", "").equals("ANNOTATION")) { Annotation annot = createAnnotationFromNode(ann); annotations.add(annot); } else { logger.error("ANNOTATES relation does not start with ANNOTATION: " + ann); } } } } if (userUri != null) { // there should be only one Node person = getNodeIndex(NodeTypes.PERSON).get("uri", userUri).getSingle(); if (person != null) { Iterable relations = person .getRelationships(RelationTypes.CREATED); for (Relationship relation : relations) { Node ann = relation.getEndNode(); if (ann.getProperty("TYPE", "").equals("ANNOTATION")) { Annotation annot = createAnnotationFromNode(ann); annotations.add(annot); } else { logger.error("CREATED relation does not end with ANNOTATION: " + ann); } } } } return annotations; } protected Relationship getOrCreateRelation(Node start, RelationshipType type, Node end) { if (start.hasRelationship()) { // there are relations Iterable rels = start.getRelationships(type, Direction.OUTGOING); for (Relationship rel : rels) { if (rel.getEndNode().equals(end)) { // relation exists return rel; } } } // create new one Relationship rel; Transaction tx = graphDb.beginTx(); try { rel = start.createRelationshipTo(end, type); tx.success(); } finally { tx.finish(); } return rel; } protected Node getOrCreateAnnotationNode(String id) { Index idx = getNodeIndex(NodeTypes.ANNOTATION); IndexHits annotations = idx.get("id", id); Node annotation = annotations.getSingle(); if (annotation == null) { // does not exist yet Transaction tx = graphDb.beginTx(); try { annotation = graphDb.createNode(); annotation.setProperty("TYPE", NodeTypes.ANNOTATION.name()); annotation.setProperty("id", id); idx.add(annotation, "id", id); tx.success(); } finally { tx.finish(); } } return annotation; } protected Node getOrCreateTargetNode(String uri) { Index idx = getNodeIndex(NodeTypes.TARGET); IndexHits targets = idx.get("uri", uri); Node target = targets.getSingle(); if (target == null) { // does not exist yet Transaction tx = graphDb.beginTx(); try { target = graphDb.createNode(); target.setProperty("TYPE", NodeTypes.TARGET.name()); target.setProperty("uri", uri); idx.add(target, "uri", uri); tx.success(); } finally { tx.finish(); } } return target; } protected Node getOrCreatePersonNode(Actor actor) { /* // Person is identified by URI Index idx = getNodeIndex(NodeTypes.PERSON); IndexHits persons = idx.get("uri", uri); Node person = persons.getSingle(); if (person == null) { // does not exist yet Transaction tx = graphDb.beginTx(); try { person = graphDb.createNode(); person.setProperty("TYPE", NodeTypes.PERSON.name()); person.setProperty("uri", uri); idx.add(person, "uri", uri); if (name != null) { person.setProperty("name", name); } tx.success(); } finally { tx.finish(); } } return person; */ return null; } /** * Unindexes and deletes given Node if it has no relations. * @param node */ protected void deleteNode(Node node) { Transaction tx = graphDb.beginTx(); try { if (node.hasRelationship()) { logger.error("deleteNode: unable to delete: Node still has relations."); } else { String ts = (String) node.getProperty("TYPE", null); try { NodeTypes type = NodeTypes.valueOf(ts); getNodeIndex(type).remove(node); } catch (Exception e) { logger.error("deleteNode: unable to get TYPE of node: "+node); } node.delete(); } tx.success(); } finally { tx.finish(); } } /** * Erzeuge eine urn aus der aktuellen Zeit in millis * * @return */ private String createRessourceURI(String prefix) { Calendar cal = Calendar.getInstance(); long time = cal.getTimeInMillis(); return String.format("%s%s%s", ANNOTATION_URI_BASE, prefix, time); } }