view src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java @ 13:abe25edf2178

storing and retrieving permissions works now.
author casties
date Fri, 13 Jul 2012 17:22:05 +0200
parents 5928c5d9aae8
children 629e15b345aa
line wrap: on
line source

/**
 * 
 */
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.Group;
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<Index<Node>> nodeIndexes;

	public static enum RelationTypes implements RelationshipType {
		ANNOTATES, CREATED, PERMITS_ADMIN, PERMITS_DELETE, PERMITS_UPDATE, PERMITS_READ
	}

	public static String ANNOTATION_URI_BASE = "http://entities.mpiwg-berlin.mpg.de/annotations/";

	public AnnotationStore(GraphDatabaseService graphDb) {
		super();
		this.graphDb = graphDb;
		nodeIndexes = new ArrayList<Index<Node>>(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<Node> 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
		Relationship targetRel = getRelation(annotNode,
				RelationTypes.ANNOTATES, null);
		if (targetRel != null) {
			Node target = targetRel.getEndNode();
			annot.setTargetBaseUri((String) target.getProperty("uri", null));
		} else {
			logger.error("annotation " + annotNode + " has no target node!");
		}
		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
		Relationship creatorRel = getRelation(annotNode, RelationTypes.CREATED,
				null);
		if (creatorRel != null) {
			Node creatorNode = creatorRel.getStartNode();
			Actor creator = createActorFromNode(creatorNode);
			annot.setCreator(creator);
		} else {
			logger.error("annotation " + annotNode + " has no creator node!");
		}
		// get creation date
		annot.setCreated((String) annotNode.getProperty("created", null));
		// get permissions
		Relationship adminRel = getRelation(annotNode,
				RelationTypes.PERMITS_ADMIN, null);
		if (adminRel != null) {
			Node adminNode = adminRel.getEndNode();
			Actor admin = createActorFromNode(adminNode);
			annot.setAdminPermission(admin);
		}
		Relationship deleteRel = getRelation(annotNode,
				RelationTypes.PERMITS_DELETE, null);
		if (deleteRel != null) {
			Node deleteNode = deleteRel.getEndNode();
			Actor delete = createActorFromNode(deleteNode);
			annot.setDeletePermission(delete);
		}
		Relationship updateRel = getRelation(annotNode,
				RelationTypes.PERMITS_UPDATE, null);
		if (updateRel != null) {
			Node updateNode = updateRel.getEndNode();
			Actor update = createActorFromNode(updateNode);
			annot.setUpdatePermission(update);
		}
		Relationship readRel = getRelation(annotNode,
				RelationTypes.PERMITS_READ, null);
		if (readRel != null) {
			Node readNode = readRel.getEndNode();
			Actor read = createActorFromNode(readNode);
			annot.setReadPermission(read);
		}

		return annot;
	}

	/**
	 * Returns an Actor object from a node.
	 * 
	 * @param actorNode
	 * @return
	 */
	protected Actor createActorFromNode(Node actorNode) {
		String uri = (String) actorNode.getProperty("uri", null);
		String name = (String) actorNode.getProperty("name", null);
		String type = (String) actorNode.getProperty("TYPE", null);
		if (type != null && type.equals("PERSON")) {
			return new Person(uri, name);
		} else if (type != null && type.equals("GROUP")) {
			return new Group(uri, name);
		}
		return null;
	}

	/**
	 * 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);
			}

			/*
			 * Permissions for this annotation.
			 */
			setPermissionRelation(annotNode, RelationTypes.PERMITS_ADMIN,
					annot.getAdminPermission());
			setPermissionRelation(annotNode, RelationTypes.PERMITS_DELETE,
					annot.getDeletePermission());
			setPermissionRelation(annotNode, RelationTypes.PERMITS_UPDATE,
					annot.getUpdatePermission());
			setPermissionRelation(annotNode, RelationTypes.PERMITS_READ,
					annot.getReadPermission());

			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<Annotation> searchByUriUser(String targetUri, String userUri,
			String limit, String offset) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		if (targetUri != null) {
			// there should be only one
			Node target = getNodeIndex(NodeTypes.TARGET).get("uri", targetUri)
					.getSingle();
			if (target != null) {
				Iterable<Relationship> 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<Relationship> 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<Relationship> 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<Node> idx = getNodeIndex(NodeTypes.ANNOTATION);
		IndexHits<Node> 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<Node> idx = getNodeIndex(NodeTypes.TARGET);
		IndexHits<Node> 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
		String uri = actor.getUriString();
		String name = actor.getName();
		Index<Node> idx = getNodeIndex(NodeTypes.PERSON);
		IndexHits<Node> 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;
	}

	/**
	 * Create or update permissions relations for an annotation.
	 * 
	 * @param annotNode
	 * @param type
	 * @param annot
	 */
	protected void setPermissionRelation(Node annotNode, RelationTypes type,
			Actor actor) {
		Node newActorNode = null;
		if (actor != null) {
			newActorNode = getOrCreatePersonNode(actor);
		}
		Relationship rel = getRelation(annotNode, type, null);
		if (rel != null) {
			// relation exists
			Node oldActorNode = rel.getEndNode();
			if (!oldActorNode.equals(newActorNode)) {
				// new admin is different
				rel.delete();
				if (newActorNode != null) {
					rel = getOrCreateRelation(annotNode, type, newActorNode);
				}
			}
		} else {
			// no relation yet
			if (newActorNode != null) {
				rel = getOrCreateRelation(annotNode, type, newActorNode);
			}
		}
	}

	/**
	 * 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();
		}
	}

	protected Relationship getRelation(Node start, RelationTypes type,
			Direction direction) {
		Iterable<Relationship> rels;
		if (direction == null) {
			// ignore direction
			rels = start.getRelationships(type);
		} else {
			rels = start.getRelationships(type, direction);
		}
		for (Relationship rel : rels) {
			return rel;
		}
		return null;
	}

	/**
	 * 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);

	}

}