# HG changeset patch # User casties # Date 1346781779 -7200 # Node ID 794077e6288c544ac02c3c5b334e01ecb45a95d0 # Parent 58357a4b86de38f16574963565f65c37a01d3db9 CLOSED - # 252: Tags for Annotations https://it-dev.mpiwg-berlin.mpg.de/tracs/mpdl-project-software/ticket/252 diff -r 58357a4b86de -r 794077e6288c src/main/java/de/mpiwg/itgroup/annotations/Actor.java --- a/src/main/java/de/mpiwg/itgroup/annotations/Actor.java Tue Aug 28 20:23:12 2012 +0200 +++ b/src/main/java/de/mpiwg/itgroup/annotations/Actor.java Tue Sep 04 20:02:59 2012 +0200 @@ -31,9 +31,8 @@ */ public boolean isEquivalentWith(Person person, AnnotationStore store) { if (person == null) return false; - if (person.equals(getIdString())) { - return true; - } + if (person.equals(this)) return true; + if (person.getIdString().equals(this.getIdString())) return true; if (isGroup() && store != null) { // check if person in group return store.isPersonInGroup(person, (Group) this); diff -r 58357a4b86de -r 794077e6288c src/main/java/de/mpiwg/itgroup/annotations/Annotation.java --- a/src/main/java/de/mpiwg/itgroup/annotations/Annotation.java Tue Aug 28 20:23:12 2012 +0200 +++ b/src/main/java/de/mpiwg/itgroup/annotations/Annotation.java Tue Sep 04 20:02:59 2012 +0200 @@ -3,6 +3,9 @@ */ package de.mpiwg.itgroup.annotations; +import java.util.List; +import java.util.Set; + import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore; /** @@ -81,7 +84,11 @@ * null means any user. */ protected Actor readPermission; - + + /** + * List of tags on this Annotation. + */ + protected Set tags; /** * Returns if the requested action is allowed on this annotation. @@ -317,6 +324,20 @@ public void setReadPermission(Actor readPermission) { this.readPermission = readPermission; } + + /** + * @return the tags + */ + public Set getTags() { + return tags; + } + + /** + * @param tags the tags to set + */ + public void setTags(Set tags) { + this.tags = tags; + } } diff -r 58357a4b86de -r 794077e6288c src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java --- a/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java Tue Aug 28 20:23:12 2012 +0200 +++ b/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java Tue Sep 04 20:02:59 2012 +0200 @@ -5,7 +5,9 @@ import java.util.ArrayList; import java.util.Calendar; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.log4j.Logger; import org.neo4j.graphdb.Direction; @@ -34,13 +36,13 @@ protected GraphDatabaseService graphDb; public static enum NodeTypes { - ANNOTATION, PERSON, TARGET, GROUP + ANNOTATION, PERSON, TARGET, GROUP, TAG } protected List> nodeIndexes; public static enum RelationTypes implements RelationshipType { - ANNOTATES, CREATED, PERMITS_ADMIN, PERMITS_DELETE, PERMITS_UPDATE, PERMITS_READ, MEMBER_OF + ANNOTATES, CREATED, PERMITS_ADMIN, PERMITS_DELETE, PERMITS_UPDATE, PERMITS_READ, MEMBER_OF, HAS_TAG } public static String ANNOTATION_URI_BASE = "http://entities.mpiwg-berlin.mpg.de/annotations/"; @@ -48,12 +50,13 @@ public AnnotationStore(GraphDatabaseService graphDb) { super(); this.graphDb = graphDb; - nodeIndexes = new ArrayList>(3); + nodeIndexes = new ArrayList>(5); // 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")); nodeIndexes.add(NodeTypes.GROUP.ordinal(), graphDb.index().forNodes("groups")); + nodeIndexes.add(NodeTypes.TAG.ordinal(), graphDb.index().forNodes("tags")); } protected Index getNodeIndex(NodeTypes type) { @@ -70,7 +73,6 @@ return person; } - /** * Returns List of Groups the person is member of. * @@ -85,14 +87,14 @@ Actor group = createActorFromNode(groupNode); // make sure we're getting a group if (!(group instanceof Group)) { - logger.error("target of MEMBER_OF is not GROUP! rel="+rel); + logger.error("target of MEMBER_OF is not GROUP! rel=" + rel); continue; } groups.add((Group) group); } return groups; } - + /** * Returns if person with uri is in Group group. * @@ -103,7 +105,7 @@ public boolean isPersonInGroup(Person person, Group group) { Node pn = getPersonNodeByUri(person.getUriString()); if (pn == null) return false; - // optimised version of getGroupsForPersonNode + // optimized version of getGroupsForPersonNode Iterable rels = pn.getRelationships(RelationTypes.MEMBER_OF); for (Relationship rel : rels) { Node gn = rel.getEndNode(); @@ -113,7 +115,7 @@ } return false; } - + /** * Returns the Annotation with the given id. * @@ -137,7 +139,9 @@ 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 + /* + * get annotation target from relation + */ Relationship targetRel = getRelation(annotNode, RelationTypes.ANNOTATES, null); if (targetRel != null) { Node target = targetRel.getEndNode(); @@ -150,7 +154,9 @@ if (ft != null) { annot.setFragmentType(FragmentTypes.valueOf(ft)); } - // get creator from relation + /* + * get creator from relation + */ Relationship creatorRel = getRelation(annotNode, RelationTypes.CREATED, null); if (creatorRel != null) { Node creatorNode = creatorRel.getStartNode(); @@ -159,9 +165,13 @@ } else { logger.error("annotation " + annotNode + " has no creator node!"); } - // get creation date + /* + * get creation date + */ annot.setCreated((String) annotNode.getProperty("created", null)); - // get permissions + /* + * get permissions + */ Relationship adminRel = getRelation(annotNode, RelationTypes.PERMITS_ADMIN, null); if (adminRel != null) { Node adminNode = adminRel.getEndNode(); @@ -186,6 +196,17 @@ Actor read = createActorFromNode(readNode); annot.setReadPermission(read); } + /* + * get tags + */ + Set tags = new HashSet(); + for (Relationship rel : annotNode.getRelationships(RelationTypes.HAS_TAG)) { + String tag = (String) rel.getEndNode().getProperty("name", null); + if (tag != null) { + tags.add(tag); + } + } + annot.setTags(tags); return annot; } @@ -285,6 +306,51 @@ setPermissionRelation(annotNode, RelationTypes.PERMITS_UPDATE, annot.getUpdatePermission()); setPermissionRelation(annotNode, RelationTypes.PERMITS_READ, annot.getReadPermission()); + /* + * Tags on this annotation. + */ + Set newTags = annot.getTags(); + // we ignore existing tags if tags == null + if (newTags != null) { + List oldHasTags = new ArrayList(); + for (Relationship rel : annotNode.getRelationships(RelationTypes.HAS_TAG)) { + oldHasTags.add(rel); + } + // adjust to new tags + if (newTags.isEmpty()) { + // remove old tags + if (!oldHasTags.isEmpty()) { + for (Relationship rel : oldHasTags) { + rel.delete(); + // TODO: should we delete orphan nodes too? + } + } + } else { + if (!oldHasTags.isEmpty()) { + // adjust old tags + for (Relationship rel : oldHasTags) { + String oldTag = (String) rel.getEndNode().getProperty("name", null); + if (newTags.contains(oldTag)) { + // tag exists + newTags.remove(oldTag); + } else { + // tag exists no longer + rel.delete(); + // TODO: should we delete orphan nodes too? + } + } + } + if (!newTags.isEmpty()) { + // still tags to add + for (String tag : newTags) { + // create new tag + Node tagNode = getOrCreateTagNode(tag); + getOrCreateRelation(annotNode, RelationTypes.HAS_TAG, tagNode); + } + } + + } + } tx.success(); } finally { tx.finish(); @@ -370,9 +436,19 @@ } } } + // TODO: if both uri and user are given we should intersect return annotations; } + /** + * Returns Relationship of type from Node start to Node end. Creates one if + * it doesn't exist. + * + * @param start + * @param type + * @param end + * @return + */ protected Relationship getOrCreateRelation(Node start, RelationshipType type, Node end) { if (start.hasRelationship()) { // there are relations @@ -457,7 +533,7 @@ if (actor.isGroup()) { person.setProperty("TYPE", NodeTypes.GROUP.name()); } else { - person.setProperty("TYPE", NodeTypes.PERSON.name()); + person.setProperty("TYPE", NodeTypes.PERSON.name()); } person.setProperty("uri", uri); idx.add(person, "uri", uri); @@ -475,6 +551,26 @@ return person; } + protected Node getOrCreateTagNode(String tagname) { + Index idx = getNodeIndex(NodeTypes.TAG); + IndexHits tags = idx.get("name", tagname); + Node tag = tags.getSingle(); + if (tag == null) { + // does not exist yet + Transaction tx = graphDb.beginTx(); + try { + tag = graphDb.createNode(); + tag.setProperty("TYPE", NodeTypes.TAG.name()); + tag.setProperty("name", tagname); + idx.add(tag, "name", tagname); + tx.success(); + } finally { + tx.finish(); + } + } + return tag; + } + /** * Create or update permissions relations for an annotation. * @@ -532,8 +628,9 @@ } } - /** returns the (first) Relationship of RelationTypes type from Node start. - * + /** + * returns the (first) Relationship of RelationTypes type from Node start. + * * @param start * @param type * @param direction @@ -548,6 +645,7 @@ rels = start.getRelationships(type, direction); } for (Relationship rel : rels) { + // just the first one return rel; } return null; diff -r 58357a4b86de -r 794077e6288c src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java --- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java Tue Aug 28 20:23:12 2012 +0200 +++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java Tue Sep 04 20:02:59 2012 +0200 @@ -54,9 +54,10 @@ Person authUser = Person.createPersonWithId(this.checkAuthToken(entity)); logger.debug("request authenticated=" + authUser); - Annotation annot = getAnnotationStore().getAnnotationById(id); + AnnotationStore store = getAnnotationStore(); + Annotation annot = store.getAnnotationById(id); if (annot != null) { - if (! annot.isActionAllowed("read", authUser, null)) { + if (! annot.isActionAllowed("read", authUser, store)) { setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Not Authorized!"); return null; } @@ -159,7 +160,7 @@ setStatus(Status.CLIENT_ERROR_NOT_FOUND); return null; } - if (! storedAnnot.isActionAllowed("update", authUser, null)) { + if (! storedAnnot.isActionAllowed("update", authUser, store)) { setStatus(Status.CLIENT_ERROR_FORBIDDEN); return null; } @@ -206,16 +207,17 @@ // do authentication Person authUser = Person.createPersonWithId(this.checkAuthToken(entity)); logger.debug("request authenticated=" + authUser); - Annotation annot = getAnnotationStore().getAnnotationById(id); + AnnotationStore store = getAnnotationStore(); + Annotation annot = store.getAnnotationById(id); if (annot != null) { - if (! annot.isActionAllowed("delete", authUser, null)) { + if (! annot.isActionAllowed("delete", authUser, store)) { setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Not Authorized!"); return null; } } // delete annotation - getAnnotationStore().deleteById(id); + store.deleteById(id); setStatus(Status.SUCCESS_NO_CONTENT); return null; } diff -r 58357a4b86de -r 794077e6288c src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java --- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java Tue Aug 28 20:23:12 2012 +0200 +++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java Tue Sep 04 20:02:59 2012 +0200 @@ -9,7 +9,9 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -189,6 +191,9 @@ jo.put("text", annot.getBodyText()); jo.put("uri", annot.getTargetBaseUri()); + /* + * user + */ if (makeUserObject) { // create user object JSONObject userObject = new JSONObject(); @@ -213,6 +218,9 @@ jo.put("user", annot.getCreatorUri()); } + /* + * ranges + */ if (annot.getTargetFragment() != null) { // we only look at the first xpointer List fragments = new ArrayList(); @@ -225,7 +233,9 @@ } } - // permissions + /* + * permissions + */ JSONObject perms = new JSONObject(); jo.put("permissions", perms); // admin @@ -266,6 +276,21 @@ readPerms.put(readPerm.getIdString()); } + /* + * tags + */ + Set tagset = annot.getTags(); + if (tagset != null) { + JSONArray tags = new JSONArray(); + jo.put("tags", tags); + for (String tag : tagset) { + tags.put(tag); + } + } + + /* + * id + */ // encode Annotation URL (=id) in base64 String annotUrl = annot.getUri(); String annotId = encodeJsonId(annotUrl); @@ -412,15 +437,21 @@ */ public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException { - // target uri + /* + * target uri + */ if (jo.has("uri")) { annot.setTargetBaseUri(jo.getString("uri")); } - // annotation text + /* + * annotation text + */ if (jo.has("text")) { annot.setBodyText(jo.getString("text")); } - // check authentication + /* + * check authentication + */ String authUser = checkAuthToken(entity); if (authUser == null) { /* @@ -433,7 +464,9 @@ * } authUser = httpUser.getIdentifier(); */ } - // get or create creator object + /* + * get or create creator object + */ Actor creator = annot.getCreator(); if (creator == null) { creator = new Person(); @@ -482,7 +515,9 @@ if (creator.getUri() == null) { creator.setUri(userUri); } - + /* + * creation date + */ if (annot.getCreated() == null) { // set creation date SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); @@ -490,7 +525,9 @@ annot.setCreated(ct); } - // create xpointer from the first range/area + /* + * create xpointer from the first range/area + */ if (jo.has("ranges")) { JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0); annot.setFragmentType(FragmentTypes.XPOINTER); @@ -504,7 +541,9 @@ annot.setTargetFragment(fragment); } - // permissions + /* + * permissions + */ if (jo.has("permissions")) { JSONObject permissions = jo.getJSONObject("permissions"); if (permissions.has("admin")) { @@ -529,6 +568,19 @@ } } + /* + * tags + */ + if (jo.has("tags")) { + HashSet tagset = new HashSet(); + JSONArray tags = jo.getJSONArray("tags"); + for (int i = 0; i < tags.length(); ++i) { + tagset.add(tags.getString(i)); + } + annot.setTags(tagset); + } + + return annot; }