changeset 32:0731c4549065

UI for editing groups and persons works now. (still no authorisation!)
author casties
date Tue, 25 Sep 2012 21:59:21 +0200
parents 05b631a084d0
children 86bb29132ba6
files src/main/java/de/mpiwg/itgroup/annotations/Person.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/AnnotatorSearch.java src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/AnnotationsUiRestlet.java src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupResource.java src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupsResource.java src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/PersonResource.java src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/PersonsResource.java src/main/webapp/groups/index.html src/main/webapp/groups/new_group.html src/main/webapp/index.html
diffstat 12 files changed, 574 insertions(+), 139 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/de/mpiwg/itgroup/annotations/Person.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/Person.java	Tue Sep 25 21:59:21 2012 +0200
@@ -3,6 +3,8 @@
  */
 package de.mpiwg.itgroup.annotations;
 
+import de.mpiwg.itgroup.annotations.restlet.BaseRestlet;
+
 /**
  * @author casties
  *
@@ -54,4 +56,17 @@
         }
         return null;
     }
+
+    /**
+     * Sets the name from the id using getFullNameFromLdap of the Application.
+     *  
+     * @param application
+     * @return
+     */
+    public String updateName(BaseRestlet application) {
+        if (id != null) {
+            name = application.getFullNameFromLdap(id);
+        }
+        return name;
+    }
 }
--- a/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java	Tue Sep 25 21:59:21 2012 +0200
@@ -4,6 +4,7 @@
 package de.mpiwg.itgroup.annotations.neo4j;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Calendar;
 import java.util.HashSet;
 import java.util.List;
@@ -27,6 +28,8 @@
 import de.mpiwg.itgroup.annotations.Tag;
 
 /**
+ * Neo4J based Annotation store.
+ * 
  * @author casties
  * 
  */
@@ -40,6 +43,9 @@
         ANNOTATION, PERSON, TARGET, GROUP, TAG
     }
 
+    // types of nodes that should not be automatically deleted.
+    public Set<String> permanentNodeTypes = new HashSet<String>(Arrays.asList("PERSON", "GROUP", "TAG"));
+
     protected List<Index<Node>> nodeIndexes;
 
     public static enum RelationTypes implements RelationshipType {
@@ -100,8 +106,7 @@
     }
 
     /**
-     * Returns List of Groups.
-     * Key has to be indexed.
+     * Returns List of Groups. Key has to be indexed.
      * 
      * @param key
      * @param query
@@ -121,11 +126,9 @@
         }
         return groups;
     }
-    
-    
+
     /**
-     * Returns List of Tags.
-     * Key has to be indexed.
+     * Returns List of Tags. Key has to be indexed.
      * 
      * @param key
      * @param query
@@ -146,8 +149,7 @@
         return tags;
     }
 
- 
-   /**
+    /**
      * Returns List of Groups the person is member of.
      * 
      * @param person
@@ -212,7 +214,7 @@
         }
         return members;
     }
-    
+
     /**
      * Add Person newMember to Group group.
      * 
@@ -229,7 +231,7 @@
         }
         return addedMember;
     }
-    
+
     /**
      * Delete Person oldMember from Group group.
      * 
@@ -238,17 +240,24 @@
      */
     public void deleteGroupMember(Group group, Person member) {
         Node gn = getActorNode(group);
+        Node pn = getActorNode(member);
         Iterable<Relationship> rels = gn.getRelationships(RelationTypes.MEMBER_OF);
         for (Relationship rel : rels) {
             Node mn = rel.getStartNode();
-            if (mn.equals(member)) {
-                rel.delete();
+            if (mn.equals(pn)) {
+                Transaction tx = graphDb.beginTx();
+                try {
+                    rel.delete();
+                    tx.success();
+                } finally {
+                    tx.finish();
+                }
                 // there should be only one
                 break;
             }
-        }        
+        }
     }
-    
+
     /**
      * Returns the stored Actor matching the given one.
      * 
@@ -256,23 +265,79 @@
      * @return
      */
     public Actor getActor(Actor actor) {
-    	Node actorNode = getActorNode(actor);
-    	Actor storedActor = createActorFromNode(actorNode);
-    	return storedActor;
+        Node actorNode = getActorNode(actor);
+        Actor storedActor = createActorFromNode(actorNode);
+        return storedActor;
     }
-    
+
     /**
-     * Stores an Actor (Person or Group). Creates a new actor Node or returns an existing one.
+     * Stores an Actor (Person or Group). Creates a new actor Node or update an
+     * existing one.
      * 
      * @param actor
      * @return
      */
     public Actor storeActor(Actor actor) {
-    	Node actorNode = getOrCreateActorNode(actor);
-    	Actor storedActor = createActorFromNode(actorNode);
-    	return storedActor;
+        Node actorNode = getOrCreateActorNode(actor);
+        Transaction tx = graphDb.beginTx();
+        try {
+            // id
+            String id = actor.getId();
+            if (id != null) {
+                actorNode.setProperty("id", id);
+            }
+            // name
+            String name = actor.getName();
+            if (name != null) {
+                actorNode.setProperty("name", name);
+            }
+            // uri
+            String uri = actor.getUri();
+            if (uri != null) {
+                actorNode.setProperty("uri", uri);
+            }
+            tx.success();
+        } finally {
+            tx.finish();
+        }
+        Actor storedActor = createActorFromNode(actorNode);
+        return storedActor;
     }
-    
+
+    /**
+     * Deletes the given Actor.
+     * 
+     * @param actor
+     */
+    public void deleteActor(Actor actor) {
+        String uri = actor.getUriString();
+        Index<Node> idx;
+        if (actor.isGroup()) {
+            idx = getNodeIndex(NodeTypes.GROUP);
+        } else {
+            idx = getNodeIndex(NodeTypes.PERSON);
+        }
+        Node actorNode = idx.get("uri", uri).getSingle();
+        if (actorNode != null) {
+            // delete relations
+            Transaction tx = graphDb.beginTx();
+            try {
+                for (Relationship rel : actorNode.getRelationships()) {
+                    rel.delete();
+                }
+                if (!actorNode.hasRelationship()) {
+                    // this shouldn't happen
+                    deleteNode(actorNode);
+                } else {
+                    logger.error("deleteActor: unable to delete: Node still has relations.");
+                }
+                tx.success();
+            } finally {
+                tx.finish();
+            }
+        }
+    }
+
     /**
      * Returns the Annotation with the given id.
      * 
@@ -375,6 +440,7 @@
      * @return
      */
     protected Actor createActorFromNode(Node actorNode) {
+        if (actorNode == null) return null;
         String id = (String) actorNode.getProperty("id", null);
         String uri = (String) actorNode.getProperty("uri", null);
         String name = (String) actorNode.getProperty("name", null);
@@ -386,16 +452,16 @@
         }
         return null;
     }
-    
+
     public Tag createTagFromNode(Node tagNode) {
-    	String name = (String) tagNode.getProperty("name", null);
-    	String uri = (String) tagNode.getProperty("uri", null);
-    	String id = (String) tagNode.getProperty("id", null);
-    	
-    	return new Tag(id, uri, name);
-		
-	}
+        if (tagNode == null) return null;
+        String name = (String) tagNode.getProperty("name", null);
+        String uri = (String) tagNode.getProperty("uri", null);
+        String id = (String) tagNode.getProperty("id", null);
 
+        return new Tag(id, uri, name);
+
+    }
 
     /**
      * Store a new annotation in the store or update an existing one. Returns
@@ -511,7 +577,7 @@
                         // still tags to add
                         for (String tag : newTags) {
                             // create new tag
-                            Node tagNode = getOrCreateTagNode(new Tag(null,null,tag));
+                            Node tagNode = getOrCreateTagNode(new Tag(null, null, tag));
                             getOrCreateRelation(annotNode, RelationTypes.HAS_TAG, tagNode);
                         }
                     }
@@ -533,7 +599,7 @@
      * 
      * @param id
      */
-    public void deleteById(String id) {
+    public void deleteAnnotationById(String id) {
         Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle();
         if (annotNode != null) {
             // delete related objects
@@ -541,10 +607,10 @@
             try {
                 for (Relationship rel : annotNode.getRelationships()) {
                     // delete relation and the related node if it has no other
-                    // relations
+                    // relations and is not permanent
                     Node other = rel.getOtherNode(annotNode);
                     rel.delete();
-                    if (!other.hasRelationship()) {
+                    if (!(other.hasRelationship() || permanentNodeTypes.contains(other.getProperty("TYPE", null)))) {
                         deleteNode(other);
                     }
                 }
@@ -569,7 +635,7 @@
      * @param offset
      * @return
      */
-    public List<Annotation> searchByUriUser(String targetUri, String userUri, String limit, String offset) {
+    public List<Annotation> searchAnnotationByUriUser(String targetUri, String userUri, String limit, String offset) {
         List<Annotation> annotations = new ArrayList<Annotation>();
         if (targetUri != null) {
             // there should be only one
@@ -692,7 +758,7 @@
         Node person = persons.getSingle();
         return person;
     }
-    
+
     protected Node getOrCreateActorNode(Actor actor) {
         // Person/Group is identified by URI or id
         String uri = actor.getUriString();
@@ -745,11 +811,11 @@
                 tag.setProperty("TYPE", NodeTypes.TAG.name());
                 tag.setProperty("name", tagname);
                 idx.add(tag, "name", tagname);
-                
+
                 tag.setProperty("id", inTag.getId());
                 tag.setProperty("uri", inTag.getUri());
                 idx.add(tag, "uri", inTag.getUri());
-                
+
                 tx.success();
             } finally {
                 tx.finish();
@@ -853,20 +919,19 @@
 
     }
 
-	public List<Annotation> getAnnotationsByTag(String tagUri) {
-		
-		ArrayList<Annotation> ret = new  ArrayList<Annotation>();
-		Node tag = getTagNodeByUri(tagUri);
-		
-		
-		Iterable<Relationship> rels = tag.getRelationships(Direction.INCOMING,RelationTypes.HAS_TAG);
-		
-		for (Relationship rel:rels){
-			Node node = rel.getStartNode();
-			ret.add(createAnnotationFromNode(node));
-			
-		}
-		return ret;
-	}
+    public List<Annotation> getAnnotationsByTag(String tagUri) {
+
+        ArrayList<Annotation> ret = new ArrayList<Annotation>();
+        Node tag = getTagNodeByUri(tagUri);
+
+        Iterable<Relationship> rels = tag.getRelationships(Direction.INCOMING, RelationTypes.HAS_TAG);
+
+        for (Relationship rel : rels) {
+            Node node = rel.getStartNode();
+            ret.add(createAnnotationFromNode(node));
+
+        }
+        return ret;
+    }
 
 }
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java	Tue Sep 25 21:59:21 2012 +0200
@@ -221,7 +221,7 @@
         }
         
         // delete annotation
-        store.deleteById(id);
+        store.deleteAnnotationById(id);
         setStatus(Status.SUCCESS_NO_CONTENT);
         return null;
     }
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorSearch.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorSearch.java	Tue Sep 25 21:59:21 2012 +0200
@@ -56,7 +56,7 @@
         // do search
         logger.debug(String.format("searching for uri=%s user=%s", uri, user));
         AnnotationStore store = getAnnotationStore();
-        List<Annotation> annots = store.searchByUriUser(uri, user, limit, offset);
+        List<Annotation> annots = store.searchAnnotationByUriUser(uri, user, limit, offset);
         for (Annotation annot : annots) {
             // check permission
             if (!annot.isActionAllowed("read", authUser, store)) continue;
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/AnnotationsUiRestlet.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/AnnotationsUiRestlet.java	Tue Sep 25 21:59:21 2012 +0200
@@ -16,7 +16,7 @@
  */
 public class AnnotationsUiRestlet extends BaseRestlet {
 
-    public final String version = "AnnotationManagerN4J/AnnotationStore 0.1";
+    public final String version = "AnnotationManagerN4J/AnnotationsUI 0.2";
 
     public static Logger logger = Logger.getLogger(AnnotationsUiRestlet.class);
 
@@ -41,6 +41,10 @@
         router.attach("/groups/{id}", GroupResource.class);
         router.attach("/groups/{id}/", GroupResource.class);
         router.attach("/groups/{id}/members", GroupMembersResource.class);
+        router.attach("/persons", PersonsResource.class);
+        router.attach("/persons/", PersonsResource.class);
+        router.attach("/persons/{id}", PersonResource.class);
+        router.attach("/persons/{id}/", PersonResource.class);
 
         router.attach("/", InfoResource.class);
         // authenticator.setNext(router);
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupResource.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupResource.java	Tue Sep 25 21:59:21 2012 +0200
@@ -4,12 +4,15 @@
 package de.mpiwg.itgroup.annotations.restlet.annotations_ui;
 
 import org.apache.log4j.Logger;
+import org.restlet.data.Form;
 import org.restlet.data.MediaType;
 import org.restlet.data.Reference;
 import org.restlet.data.Status;
 import org.restlet.representation.Representation;
 import org.restlet.representation.StringRepresentation;
+import org.restlet.resource.Delete;
 import org.restlet.resource.Get;
+import org.restlet.resource.Put;
 import org.restlet.resource.ResourceException;
 import org.restlet.resource.ServerResource;
 
@@ -28,11 +31,11 @@
     public static Logger logger = Logger.getLogger(GroupResource.class);
 
     protected AnnotationStore store;
-    
+
     protected String requestId;
-    
+
     protected Group group;
-    
+
     @Override
     protected void doInit() throws ResourceException {
         super.doInit();
@@ -57,26 +60,105 @@
      */
     @Get("html")
     public Representation doGetHTML(Representation entity) {
-        if (requestId == null || requestId.isEmpty()) {
+        if (group == null) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+            return null;
+        }
+        String result = null;
+        // get form parameter
+        Form f = this.getQuery();
+        String form = f.getFirstValue("form");
+        if (form != null && form.equals("edit")) {
+            // output edit form
+            result = "<html><body>\n";
+            result += String.format("<h1>Edit group %s</h1>\n", group.getId());
+            result += String.format("<p><a href=\"%s\">All groups</a></p>", this.getReference().getParentRef());
+            // tunnel PUT method through POST
+            result += String.format("<form method=\"post\" action=\"%s?method=PUT\">\n", this.getReference().getHierarchicalPart());
+            result += "<table>";
+            result += String.format("<tr><td><b>name</b></td><td><input type=\"text\" name=\"name\" value=\"%s\"/></td></tr>\n",
+                    group.getName());
+            result += String.format("<tr><td><b>uri</b></td><td><input type=\"text\" name=\"uri\" value=\"%s\"/></td></tr>\n",
+                    group.getUriString());
+            result += "</table>\n";
+            result += "<p><input type=\"submit\"/></p>";
+            result += "</table>\n</form>\n</body>\n</html>";
+        } else {
+            // output group content
+            result = "<html><body>\n<h1>Group</h1>\n";
+            result += String.format("<p><a href=\"%s\">All groups</a></p>", this.getReference().getParentRef());
+            result += "<table>";
+            result += String.format("<tr><td><b>id</b></td><td>%s</td></tr>\n", group.getId());
+            result += String.format("<tr><td><b>name</b></td><td>%s</td></tr>\n", group.getName());
+            result += String.format("<tr><td><b>uri</b></td><td>%s</td></tr>\n", group.getUri());
+            result += String.format("<tr><td><b>members</b></td><td><a href=\"%s\">view members</a></td></tr>\n", this
+                    .getReference().addSegment("members"));
+            result += "</table>\n";
+            result += "<p><a href=\"?form=edit\">Edit group</a></p>\n";
+            // tunnel POST as DELETE
+            result += String.format(
+                    "<form method=\"post\" action=\"%s?method=DELETE\"><input type=\"submit\" value=\"Delete group\"/></form>\n",
+                    this.getReference().getHierarchicalPart());
+            result += "</body>\n</html>";
+        }
+        return new StringRepresentation(result, MediaType.TEXT_HTML);
+    }
+
+    /**
+     * PUT updates the group.
+     * 
+     * @param entity
+     * @return
+     */
+    @Put
+    public Representation doPut(Representation entity) {
+        logger.debug("GroupResource.doPut!");
+        if (group == null) {
             // invalid id
             setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
             return null;
         }
-        String result = null;
-        Reference groupsUrl = this.getReference().getParentRef();
-        result = "<html><body>\n<h1>Group</h1>\n";
-        result += String.format("<p><a href=\"%s\">All groups</a></p>", groupsUrl);
-        result += "<table>";
-        result += String.format("<tr><td><b>id</b></td><td>%s</td></tr>\n", group.getId());
-        result += String.format("<tr><td><b>name</b></td><td>%s</td></tr>\n", group.getName());
-        result += String.format("<tr><td><b>uri</b></td><td>%s</td></tr>\n", group.getUri());
-        result += String.format("<tr><td><b>members</b></td><td><a href=\"%s\">view members</a></td></tr>\n", this.getReference()
-                .addSegment("members"));
-        result += "</table>\n</body>\n</html>";
-
-        logger.debug("sending:");
-        logger.debug(result);
-        return new StringRepresentation(result, MediaType.TEXT_HTML);
+        // TODO: do authentication
+        Form form = new Form(entity);
+        String name = form.getFirstValue("name");
+        String uri = form.getFirstValue("uri");
+        if (name != null && !name.isEmpty()) {
+            group.setName(name);
+        }
+        if (uri != null && !uri.isEmpty()) {
+            group.setUri(uri);
+        }
+        store.storeActor(group);
+        // return 303: see other
+        setStatus(Status.REDIRECTION_SEE_OTHER);
+        // go GET same URL
+        Reference url = this.getReference();
+        this.getResponse().setLocationRef(url);
+        return null;
     }
 
+    /**
+     * DELETE deletes the group.
+     * 
+     * @param entity
+     * @return
+     */
+    @Delete
+    public Representation doDelete(Representation entity) {
+        logger.debug("GroupResource.doDelete!");
+        if (group == null) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+            return null;
+        }
+        // TODO: do authentication
+        store.deleteActor(group);
+        // return 303: see other
+        setStatus(Status.REDIRECTION_SEE_OTHER);
+        // go GET parent URL
+        Reference url = this.getReference().getParentRef();
+        this.getResponse().setLocationRef(url);
+        return null;
+    }
 }
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupsResource.java	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/GroupsResource.java	Tue Sep 25 21:59:21 2012 +0200
@@ -14,6 +14,7 @@
 import org.restlet.representation.StringRepresentation;
 import org.restlet.resource.Get;
 import org.restlet.resource.Post;
+import org.restlet.resource.ResourceException;
 import org.restlet.resource.ServerResource;
 
 import de.mpiwg.itgroup.annotations.Actor;
@@ -33,6 +34,15 @@
 
     private AnnotationStore store;
 
+    @Override
+    protected void doInit() throws ResourceException {
+        super.doInit();
+        // get store instance
+        if (store == null) {
+            store = ((BaseRestlet) getApplication()).getAnnotationStore();
+        }
+    }
+
     /**
      * GET with HTML content type. Lists all groups.
      * 
@@ -42,20 +52,37 @@
     @Get("html")
     public Representation doGetHTML(Representation entity) {
         String result = null;
-        store = getAnnotationStore();
-        // list all groups
-        result = "<html><body>\n<h1>Groups</h1>\n<table>";
-        result += "<tr><th>id</th><th>name</th><th>uri</th></tr>";
-        List<Group> groups = store.getGroups("uri", "*");
-        for (Group group : groups) {
-            Reference groupUrl = this.getReference();
-            groupUrl.addSegment(group.getId());
-            result += String.format("<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%s</td></tr>\n", groupUrl, group.getId(),
-                    group.getName(), group.getUri());
+        // get form parameter
+        Form f = this.getQuery();
+        String form = f.getFirstValue("form");
+        if (form != null && form.equals("new_group")) {
+            // output new group form
+            result = "<html><body>\n";
+            result += "<h1>New group</h1>\n";
+            result += String.format("<p><a href=\"%s\">All groups</a></p>", this.getReference());
+            result += String.format("<form method=\"post\" action=\"%s\">\n", this.getReference().getHierarchicalPart());
+            result += "<table>";
+            result += "<tr><td><b>id</b></td><td><input type=\"text\" name=\"id\"/></td></tr>\n";
+            result += "<tr><td><b>name</b></td><td><input type=\"text\" name=\"name\"/></td></tr>\n";
+            result += "</table>\n";
+            result += "<p><input type=\"submit\"/></p>\n";
+            result += "</form>\n</body>\n</html>";
+        } else {
+            // list all groups
+            result = "<html><body>\n<h1>Groups</h1>\n";
+            result += "<table>\n";
+            result += "<tr><th>id</th><th>name</th><th>uri</th></tr>";
+            List<Group> groups = store.getGroups("uri", "*");
+            for (Group group : groups) {
+                Reference groupUrl = this.getReference().clone();
+                groupUrl.addSegment(group.getId());
+                result += String.format("<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%s</td></tr>\n", groupUrl, group.getId(),
+                        group.getName(), group.getUri());
+            }
+            result += "</table>\n";
+            result += "<p><a href=\"?form=new_group\">Add new group</a></p>\n";
+            result += "</body>\n</html>";
         }
-        result += "</table>\n</body>\n</html>";
-        logger.debug("sending:");
-        logger.debug(result);
         return new StringRepresentation(result, MediaType.TEXT_HTML);
     }
 
@@ -78,7 +105,6 @@
         }
         String gid = makeGroupId(id);
         Group newGroup = new Group(gid, null, name);
-        store = getAnnotationStore();
         Actor storedGroup = store.storeActor(newGroup);
         gid = storedGroup.getId();
         // return 303: see other
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/PersonResource.java	Tue Sep 25 21:59:21 2012 +0200
@@ -0,0 +1,162 @@
+/**
+ * 
+ */
+package de.mpiwg.itgroup.annotations.restlet.annotations_ui;
+
+import org.apache.log4j.Logger;
+import org.restlet.data.Form;
+import org.restlet.data.MediaType;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Put;
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+import de.mpiwg.itgroup.annotations.Person;
+import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore;
+import de.mpiwg.itgroup.annotations.restlet.BaseRestlet;
+
+/**
+ * Resource class for a single person.
+ * 
+ * @author casties
+ * 
+ */
+public class PersonResource extends ServerResource {
+
+    public static Logger logger = Logger.getLogger(PersonResource.class);
+
+    protected AnnotationStore store;
+
+    protected String requestId;
+
+    protected Person person;
+
+    @Override
+    protected void doInit() throws ResourceException {
+        super.doInit();
+        // id from URI /annotations/persons/{id}
+        requestId = (String) getRequest().getAttributes().get("id");
+        logger.debug("group-id=" + requestId);
+        // get store instance
+        if (store == null) {
+            store = ((BaseRestlet) getApplication()).getAnnotationStore();
+        }
+        // get group from store
+        if (requestId != null) {
+            person = (Person) store.getActor(new Person(requestId));
+        }
+    }
+
+    /**
+     * GET with HTML content type. Shows the person.
+     * 
+     * @param entity
+     * @return
+     */
+    @Get("html")
+    public Representation doGetHTML(Representation entity) {
+        if (person == null) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+            return null;
+        }
+        String result = null;
+        // get form parameter
+        Form f = this.getQuery();
+        String form = f.getFirstValue("form");
+        if (form != null && form.equals("edit")) {
+            // output edit form
+            result = "<html><body>\n";
+            result += String.format("<h1>Edit person %s</h1>\n", person.getId());
+            result += String.format("<p><a href=\"%s\">All persons</a></p>", this.getReference().getParentRef());
+            // tunnel PUT method through POST
+            result += String.format("<form method=\"post\" action=\"%s?method=PUT\">\n", this.getReference().getHierarchicalPart());
+            result += "<table>";
+            result += String.format("<tr><td><b>name</b></td><td><input type=\"text\" name=\"name\" value=\"%s\"/></td></tr>\n",
+                    person.getName());
+            result += String.format("<tr><td><b>uri</b></td><td><input type=\"text\" name=\"uri\" value=\"%s\"/></td></tr>\n",
+                    person.getUriString());
+            result += "</table>\n";
+            result += "<p><input type=\"submit\"/></p>";
+            result += "</table>\n</form>\n</body>\n</html>";
+        } else {
+            // output person content
+            result = "<html><body>\n<h1>Person</h1>\n";
+            result += String.format("<p><a href=\"%s\">All persons</a></p>", this.getReference().getParentRef());
+            result += "<table>";
+            result += String.format("<tr><td><b>id</b></td><td>%s</td></tr>\n", person.getId());
+            result += String.format("<tr><td><b>name</b></td><td>%s</td></tr>\n", person.getName());
+            result += String.format("<tr><td><b>uri</b></td><td>%s</td></tr>\n", person.getUri());
+            result += "</table>\n";
+            result += "<p><a href=\"?form=edit\">Edit person</a></p>\n";
+            // tunnel POST as DELETE
+            result += String.format(
+                    "<form method=\"post\" action=\"%s?method=DELETE\"><input type=\"submit\" value=\"Delete person\"/></form>\n",
+                    this.getReference().getHierarchicalPart());
+            result += "</body>\n</html>";
+        }
+        return new StringRepresentation(result, MediaType.TEXT_HTML);
+    }
+
+    /**
+     * PUT updates the person.
+     * 
+     * @param entity
+     * @return
+     */
+    @Put
+    public Representation doPut(Representation entity) {
+        logger.debug("PersonResource.doPut!");
+        if (person == null) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+            return null;
+        }
+        // TODO: do authentication
+        Form form = new Form(entity);
+        String name = form.getFirstValue("name");
+        String uri = form.getFirstValue("uri");
+        if (name != null && !name.isEmpty()) {
+            person.setName(name);
+        }
+        if (uri != null && !uri.isEmpty()) {
+            person.setUri(uri);
+        }
+        store.storeActor(person);
+        // return 303: see other
+        setStatus(Status.REDIRECTION_SEE_OTHER);
+        // go GET same URL
+        Reference url = this.getReference();
+        this.getResponse().setLocationRef(url);
+        return null;
+    }
+
+    /**
+     * DELETE deletes the person.
+     * 
+     * @param entity
+     * @return
+     */
+    @Delete
+    public Representation doDelete(Representation entity) {
+        logger.debug("PersonResource.doDelete!");
+        if (person == null) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+            return null;
+        }
+        // TODO: do authentication
+        store.deleteActor(person);
+        // return 303: see other
+        setStatus(Status.REDIRECTION_SEE_OTHER);
+        // go GET parent URL
+        Reference url = this.getReference().getParentRef();
+        this.getResponse().setLocationRef(url);
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/annotations_ui/PersonsResource.java	Tue Sep 25 21:59:21 2012 +0200
@@ -0,0 +1,123 @@
+/**
+ * 
+ */
+package de.mpiwg.itgroup.annotations.restlet.annotations_ui;
+
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.restlet.data.Form;
+import org.restlet.data.MediaType;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+import de.mpiwg.itgroup.annotations.Actor;
+import de.mpiwg.itgroup.annotations.Person;
+import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore;
+import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore.NodeTypes;
+import de.mpiwg.itgroup.annotations.restlet.BaseRestlet;
+
+/**
+ * Resource class for the list of annotation users (Person class).
+ * 
+ * @author casties
+ * 
+ */
+public class PersonsResource extends ServerResource {
+
+    public static Logger logger = Logger.getLogger(PersonsResource.class);
+
+    private AnnotationStore store;
+
+    @Override
+    protected void doInit() throws ResourceException {
+        super.doInit();
+        // get store instance
+        if (store == null) {
+            store = ((BaseRestlet) getApplication()).getAnnotationStore();
+        }
+    }
+
+    /**
+     * GET with HTML content type. Lists all persons.
+     * 
+     * @param entity
+     * @return
+     */
+    @Get("html")
+    public Representation doGetHTML(Representation entity) {
+        String result = null;
+        // get form parameter
+        Form f = this.getQuery();
+        String form = f.getFirstValue("form");
+        if (form != null && form.equals("new_person")) {
+            // output new group form
+            result = "<html><body>\n";
+            result += "<h1>New person</h1>\n";
+            result += String.format("<p><a href=\"%s\">All persons</a></p>", this.getReference());
+            result += String.format("<form method=\"post\" action=\"%s\">\n", this.getReference().getHierarchicalPart());
+            result += "<table>";
+            result += "<tr><td><b>id</b></td><td><input type=\"text\" name=\"id\"/></td></tr>\n";
+            result += "<tr><td><b>name</b></td><td><input type=\"text\" name=\"name\"/></td></tr>\n";
+            result += "</table>\n";
+            result += "<p><input type=\"submit\"/></p>\n";
+            result += "</form>\n</body>\n</html>";
+        } else {
+            // list all groups
+            result = "<html><body>\n<h1>Persons</h1>\n<table>";
+            result += "<tr><th>id</th><th>name</th><th>uri</th></tr>";
+            List<Actor> persons = store.getActors("uri", "*", NodeTypes.PERSON);
+            for (Actor person : persons) {
+                Reference url = this.getReference().clone();
+                url.addSegment(person.getId());
+                result += String.format("<tr><td><a href=\"%s\">%s</a></td><td>%s</td><td>%s</td></tr>\n", url,
+                        person.getIdString(), person.getName(), person.getUri());
+            }
+            result += "</table>\n";
+            result += "<p><a href=\"?form=new_person\">Add new person</a></p>\n";
+            result += "</body>\n</html>";
+        }
+        return new StringRepresentation(result, MediaType.TEXT_HTML);
+    }
+
+    /**
+     * POST creates a new Group.
+     * 
+     * @return
+     */
+    @Post
+    public Representation doPost(Representation entity) {
+        logger.debug("PersonsResource doPost!");
+        // TODO: do authentication
+        Form form = new Form(entity);
+        String id = form.getFirstValue("id");
+        if (id == null || id.isEmpty() || id.matches("\\W")) {
+            // invalid id
+            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+            return null;
+        }
+        String name = form.getFirstValue("name");
+        if (name == null || name.isEmpty()) {
+            name = ((BaseRestlet) getApplication()).getFullNameFromLdap(id);
+        }
+        String uri = form.getFirstValue("uri");
+        if (uri != null && uri.isEmpty()) uri = null;
+        Person newPerson = new Person(id, uri, name);
+        Actor storedPerson = store.storeActor(newPerson);
+        id = storedPerson.getId();
+        // return 303: see other
+        setStatus(Status.REDIRECTION_SEE_OTHER);
+        // go GET URL for this person
+        Reference url = this.getReference().clone();
+        url.addSegment(id);
+        this.getResponse().setLocationRef(url);
+        return null;
+    }
+
+}
--- a/src/main/webapp/groups/index.html	Tue Sep 25 16:08:11 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-
-<title>Annotation Server Groups</title>
-
-</head>
-
-<body>
-	<h1>Annotation groups</h1>
-	<ul>
-		<li><a href="../annotations/groups">List groups</a></li>
-		<li><a href="new_group.html">Add new group</a></li>
-	</ul>
-
-</body>
-</html>
--- a/src/main/webapp/groups/new_group.html	Tue Sep 25 16:08:11 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-
-<title>Annotation Server Groups</title>
-
-</head>
-
-<body>
-  <h1>New group</h1>
-  <form action="../annotations/groups" method="post">
-    <table>
-      <tr>
-        <td>ID (short name)</td>
-        <td><input name="id" type="text" /></td>
-      </tr>
-      <tr>
-        <td>Name</td>
-        <td><input name="name" type="text" /></td>
-      </tr>
-    </table>
-    <input type="submit"/>
-  </form>
-</body>
-</html>
--- a/src/main/webapp/index.html	Tue Sep 25 16:08:11 2012 +0200
+++ b/src/main/webapp/index.html	Tue Sep 25 21:59:21 2012 +0200
@@ -11,8 +11,14 @@
 
 <body>
 	<h1>Annotation Server</h1>
+	<h2>View</h2>
+  <ul>
+    <li><a href="tags">View tags</a></li>
+  </ul>
+  <h2>Admin</h2>
 	<ul>
-		<li><a href="groups">View and edit groups</a></li>
+		<li><a href="annotations/groups">View and edit groups</a></li>
+    <li><a href="annotations/persons">View and edit persons</a></li>
 	</ul>
 </body>
 </html>