changeset 16:794077e6288c

CLOSED - # 252: Tags for Annotations https://it-dev.mpiwg-berlin.mpg.de/tracs/mpdl-project-software/ticket/252
author casties
date Tue, 04 Sep 2012 20:02:59 +0200
parents 58357a4b86de
children e9dfac5b0566
files src/main/java/de/mpiwg/itgroup/annotations/Actor.java src/main/java/de/mpiwg/itgroup/annotations/Annotation.java src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java
diffstat 5 files changed, 205 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- 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);            
--- 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<String> 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<String> getTags() {
+        return tags;
+    }
+
+    /**
+     * @param tags the tags to set
+     */
+    public void setTags(Set<String> tags) {
+        this.tags = tags;
+    }
     
     
 }
--- 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<Index<Node>> 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<Index<Node>>(3);
+        nodeIndexes = new ArrayList<Index<Node>>(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<Node> 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<Relationship> 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<String> tags = new HashSet<String>();
+        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<String> newTags = annot.getTags();
+            // we ignore existing tags if tags == null
+            if (newTags != null) {
+                List<Relationship> oldHasTags = new ArrayList<Relationship>();
+                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<Node> idx = getNodeIndex(NodeTypes.TAG);
+        IndexHits<Node> 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;
--- 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;
     }
--- 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<String> fragments = new ArrayList<String>();
@@ -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<String> 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<String> tagset = new HashSet<String>();
+            JSONArray tags = jo.getJSONArray("tags");
+            for (int i = 0; i < tags.length(); ++i) {
+                tagset.add(tags.getString(i));
+            }
+            annot.setTags(tagset);
+        }
+
+        
         return annot;
     }