# HG changeset patch
# User casties
# Date 1340995107 -7200
# Node ID 47b53ae385d1f9e3491a07bbab97d9f35ca494ec
# Parent f2d44c41eedf54c1f42b8a8b3ad2b9cdc7da5033
merging old code
diff -r f2d44c41eedf -r 47b53ae385d1 .hgignore
--- a/.hgignore Thu Jun 28 19:34:32 2012 +0200
+++ b/.hgignore Fri Jun 29 20:38:27 2012 +0200
@@ -6,4 +6,6 @@
syntax: regexp
^\.classpath$
syntax: regexp
-^\.project$
\ No newline at end of file
+^\.project$
+syntax: regexp
+^\.shell_history$
\ No newline at end of file
diff -r f2d44c41eedf -r 47b53ae385d1 pom.xml
--- a/pom.xml Thu Jun 28 19:34:32 2012 +0200
+++ b/pom.xml Fri Jun 29 20:38:27 2012 +0200
@@ -59,11 +59,27 @@
org.restlet.ext.json
${restlet-version}
+
+ com.googlecode.jsontoken
+ jsontoken
+ 1.1-SNAPSHOT
+
+
+ servlet-api
+ javax.servlet
+
+
+
log4j
log4j
1.2.14
+
+ commons-codec
+ commons-codec
+ 1.4
+
@@ -79,4 +95,5 @@
+ war
\ No newline at end of file
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorAnnotations.java Fri Jun 29 20:38:27 2012 +0200
@@ -0,0 +1,398 @@
+/**
+ * Implements the "annotations" uri of the Annotator API. see
+ *
+ */
+package de.mpiwg.itgroup.annotations.restlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.restlet.Context;
+import org.restlet.data.Form;
+import org.restlet.data.MediaType;
+import org.restlet.data.Reference;
+import org.restlet.data.Status;
+import org.restlet.ext.json.JsonRepresentation;
+import org.restlet.representation.Representation;
+import org.restlet.representation.StringRepresentation;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.Put;
+import org.restlet.security.User;
+
+import de.mpiwg.itgroup.annotationManager.Constants.NS;
+import de.mpiwg.itgroup.annotationManager.Errors.TripleStoreSearchError;
+import de.mpiwg.itgroup.annotationManager.Errors.TripleStoreStoreError;
+import de.mpiwg.itgroup.annotationManager.RDFHandling.Annotation;
+import de.mpiwg.itgroup.annotationManager.RDFHandling.Convert;
+import de.mpiwg.itgroup.annotationManager.RDFHandling.RDFSearcher;
+import de.mpiwg.itgroup.annotationManager.drupal.AnnotationHandler;
+import de.mpiwg.itgroup.annotationManager.drupal.UnknowUserException;
+import de.mpiwg.itgroup.triplestoremanager.exceptions.TripleStoreHandlerException;
+
+/**
+ * Implements the "annotations" uri of the Annotator API. see
+ *
+ * @author dwinter, casties
+ *
+ */
+public class AnnotatorAnnotations extends AnnotatorResourceImpl {
+
+ protected String getAllowedMethodsForHeader() {
+ return "OPTIONS,GET,POST,PUT,DELETE";
+ }
+
+ /**
+ * GET with JSON content-type.
+ *
+ * @param entity
+ * @return
+ */
+ @Get("json")
+ public Representation doGetJSON(Representation entity) {
+ logger.debug("AnnotatorAnnotations doGetJSON!");
+ setCorsHeaders();
+ // id from URI /annotations/{id}
+ String jsonId = (String) getRequest().getAttributes().get("id");
+ String id = decodeJsonId(jsonId);
+ logger.debug("annotation-id=" + id);
+
+ // TODO: what to return without id - list of all annotations?
+
+ // TODO: what to do with authentication?
+ boolean authenticated = isAuthenticated(entity);
+ logger.debug("request authenticated=" + authenticated);
+
+ RDFSearcher searcher = new RDFSearcher(NS.MPIWG_ANNOT_CTX); // TODO should ge into config file
+
+ try {
+ List annots = searcher.searchById(id);
+ if (annots.size() == 1) {
+ // there should be only one
+ JSONObject result = createAnnotatorJson(annots.get(0));
+ logger.debug("sending:");
+ logger.debug(result);
+ return new JsonRepresentation(result);
+ } else {
+ JSONArray results;
+ results = new JSONArray();
+ for (Annotation annot : annots) {
+ JSONObject jo = createAnnotatorJson(annot);
+ if (jo != null) {
+ results.put(createAnnotatorJson(annot));
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL, "JSon Error");
+ return null;
+ }
+ }
+ // annotator read request returns a list of annotation objects
+ logger.debug("sending:");
+ logger.debug(results);
+ return new JsonRepresentation(results);
+ }
+ } catch (TripleStoreHandlerException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreHandler Error");
+ return null;
+ } catch (TripleStoreSearchError e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreSearch Error");
+ return null;
+ }
+ }
+
+ /**
+ * POST with JSON content-type.
+ *
+ * json hash: username: name des users xpointer: xpointer auf den Ausschnitt (incl. der URL des Dokumentes) text: text der
+ * annotation annoturl: url auf eine Annotation falls extern
+ *
+ * @return
+ */
+ @Post("json")
+ public Representation doPostJson(Representation entity) {
+ logger.debug("AnnotatorAnnotations doPostJSON!");
+ // set headers
+ setCorsHeaders();
+ Annotation annot = null;
+ try {
+ JsonRepresentation jrep = new JsonRepresentation(entity);
+ JSONObject jo = jrep.getJsonObject();
+ if (jo == null) {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ }
+ // make sure id is not set for POST
+ jo.remove("id");
+ // get Annotation object from posted JSON
+ annot = createAnnotation(jo, entity);
+ } catch (IOException e1) {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ } catch (JSONException e) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ return null;
+ }
+ if (annot == null || annot.xpointer == null || annot.creator == null) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ return null;
+ }
+ Annotation storedAnnot;
+ try {
+ // store Annotation
+ storedAnnot = new Convert(NS.MPIWG_ANNOT_CTX).storeAnnotation(annot);
+ } catch (TripleStoreStoreError e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStore Error");
+ return null;
+ }
+ /* according to https://github.com/okfn/annotator/wiki/Storage
+ * we should return 303: see other.
+ * For now we return the annotation.
+ */
+ JSONObject jo = createAnnotatorJson(storedAnnot);
+ JsonRepresentation retRep = new JsonRepresentation(jo);
+ return retRep;
+ }
+
+ /**
+ * POST with HTML content-type.
+ *
+ * @param entity
+ * @return
+ */
+ @Post("html")
+ public Representation doPostHtml(Representation entity) {
+ logger.debug("AnnotatorAnnotations doPostHtml!");
+ Annotation annot;
+ annot = handleForm(entity);
+ if (annot.xpointer == null || annot.creator == null) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+
+ return null;
+ }
+
+ Annotation retValAnnot;
+ try {
+ retValAnnot = new Convert(NS.MPIWG_ANNOT_CTX).storeAnnotation(annot);
+ } catch (TripleStoreStoreError e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStore Error");
+ return null;
+ }
+ if (retValAnnot == null) {
+ return null;
+ }
+ String retVal = retValAnnot.getAnnotationUri();
+ if (retVal == null) {
+ return null;
+ }
+
+ String text = String.format("%s", retVal.replace(">", "").replace("<", ""),
+ retVal.replace(">", ">").replace("<", "<"));
+ Representation retRep = new StringRepresentation(text, MediaType.TEXT_HTML);
+ return retRep;
+ }
+
+ /**
+ *
+ * @param entity
+ * should contain a form with the parameters "username", "password", "xpointer","text","uri","type"
+ *
+ * username,password is optional, if not given BasicAuthentification is used.
+ *
+ * If username given as a URI, the username will be transformed to an URI, username will be added to the MPIWG
+ * namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS
+ *
+ * @return
+ */
+ protected Annotation handleForm(Representation entity) {
+ Annotation annot;
+ Form form = new Form(entity);
+ String username = form.getValues("username");
+ String mode = form.getValues("mode");
+ String password = form.getValues("password");
+ String xpointer = form.getValues("xpointer");
+ String text = form.getValues("text");
+ String title = form.getValues("title");
+ String url = form.getValues("url");
+ String type = form.getValues("type");
+ RestServer restServer = (RestServer) getApplication();
+
+ // falls user and password nicht null sind:
+ User userFromForm = null;
+ if (username != null && password != null) {
+ if (restServer.authenticate(username, password, getRequest())) {
+ userFromForm = new User(username);
+ }
+ }
+ User authUser = null;
+
+ if (userFromForm == null) {
+ authUser = getHttpAuthUser(entity);
+ }
+
+ // weder BasicAuth noch FormAuth
+ if (authUser == null && userFromForm == null) {
+ setStatus(Status.CLIENT_ERROR_FORBIDDEN);
+ return null;
+ }
+
+ if (userFromForm != null) {
+ username = userFromForm.getIdentifier();
+ } else {
+ username = authUser.getIdentifier();
+ }
+
+ // username should be a URI, if not it will set to the MPIWG namespace defined in
+ // de.mpiwg.itgroup.annotationManager.Constants.NS
+ String usernameOrig = username;
+ if (!username.startsWith("http"))
+ username = NS.MPIWG_PERSONS_URL + username;
+
+ if (mode.equals("complexAnnotation")) {// Annotation mit text in externer ressource
+
+ Context context = getContext();
+ String drupalPath = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.drupalServer");
+
+ AnnotationHandler ah = new AnnotationHandler(drupalPath);
+ JSONObject newAnnot;
+ try {
+ newAnnot = ah.createAnnotation(title, text, usernameOrig, password);
+ } catch (UnknowUserException e1) {
+ setStatus(Status.CLIENT_ERROR_FORBIDDEN);
+ e1.printStackTrace();
+ return null;
+ }
+ try {
+ annot = new Annotation(xpointer, username, null, text, type, newAnnot.getString("node_uri"));
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ }
+ } else
+ annot = new Annotation(xpointer, username, null, text, type, url);
+ return annot;
+ }
+
+
+ /**
+ * PUT with JSON content-type.
+ *
+ * @param entity
+ * @return
+ */
+ @Put("json")
+ public Representation doPutJSON(Representation entity) {
+ logger.debug("AnnotatorAnnotations doPutJSON!");
+ setCorsHeaders();
+ // id from URI /annotations/{id}
+ String jsonId = (String) getRequest().getAttributes().get("id");
+ String id = decodeJsonId(jsonId);
+ logger.debug("annotation-id=" + id);
+
+ // TODO: what to do with authentication? we should check the owner
+ boolean authenticated = isAuthenticated(entity);
+ logger.debug("request authenticated=" + authenticated);
+ if (!authenticated) {
+ setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Not Authorized!");
+ return null;
+ }
+
+ Annotation annot = null;
+ try {
+ JsonRepresentation jrep = new JsonRepresentation(entity);
+ JSONObject jo = jrep.getJsonObject();
+ if (jo == null) {
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ return null;
+ }
+ RDFSearcher searcher = new RDFSearcher(NS.MPIWG_ANNOT_CTX); // TODO should ge into config file
+ // get stored Annotation
+ List annots = searcher.searchById(id);
+ if (annots.size() < 1) {
+ setStatus(Status.CLIENT_ERROR_NOT_FOUND);
+ return null;
+ }
+ Annotation storedAnnot = annots.get(0);
+ // delete
+ searcher.deleteById(id);
+ // update from posted JSON
+ annot = updateAnnotation(storedAnnot, jo, entity);
+ // store Annotation
+ storedAnnot = new Convert(NS.MPIWG_ANNOT_CTX).storeAnnotation(annot);
+ /* according to https://github.com/okfn/annotator/wiki/Storage
+ * we should return 303: see other.
+ * but the client doesn't like it
+ setStatus(Status.REDIRECTION_SEE_OTHER);
+ // go to same URL as this one
+ Reference thisUrl = this.getReference();
+ this.getResponse().setLocationRef(thisUrl); */
+ // return new annotation
+ jo = createAnnotatorJson(storedAnnot);
+ JsonRepresentation retRep = new JsonRepresentation(jo);
+ return retRep;
+ } catch (TripleStoreHandlerException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreHandler Error");
+ } catch (TripleStoreSearchError e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreSearch Error");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
+ } catch (IOException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "Other Error");
+ }
+ return null;
+ }
+
+ /**
+ * DELETE with JSON content-type.
+ *
+ * @param entity
+ * @return
+ */
+ @Delete("json")
+ public Representation doDeleteJSON(Representation entity) {
+ logger.debug("AnnotatorAnnotations doDeleteJSON!");
+ setCorsHeaders();
+ // id from URI /annotations/{id}
+ String jsonId = (String) getRequest().getAttributes().get("id");
+ String id = decodeJsonId(jsonId);
+ logger.debug("annotation-id=" + id);
+
+ // TODO: what to do with authentication? we should check the owner
+ boolean authenticated = isAuthenticated(entity);
+ logger.debug("request authenticated=" + authenticated);
+ if (!authenticated) {
+ setStatus(Status.CLIENT_ERROR_FORBIDDEN, "Not Authorized!");
+ return null;
+ }
+
+ RDFSearcher searcher = new RDFSearcher(NS.MPIWG_ANNOT_CTX); // TODO should ge into config file
+
+ try {
+ // delete annotation
+ searcher.deleteById(id);
+ setStatus(Status.SUCCESS_NO_CONTENT);
+ } catch (TripleStoreHandlerException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreHandler Error");
+ } catch (TripleStoreSearchError e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStoreSearch Error");
+ }
+ return null;
+ }
+
+
+}
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java Fri Jun 29 20:38:27 2012 +0200
@@ -0,0 +1,443 @@
+/**
+ * Base class for Annotator resource classes.
+ */
+package de.mpiwg.itgroup.annotations.restlet;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.security.InvalidKeyException;
+import java.security.SignatureException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import net.oauth.jsontoken.Checker;
+import net.oauth.jsontoken.JsonToken;
+import net.oauth.jsontoken.JsonTokenParser;
+import net.oauth.jsontoken.SystemClock;
+import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
+import net.oauth.jsontoken.crypto.Verifier;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.log4j.Logger;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.restlet.data.ClientInfo;
+import org.restlet.data.Form;
+import org.restlet.data.Status;
+import org.restlet.representation.Representation;
+import org.restlet.resource.Options;
+import org.restlet.resource.ServerResource;
+import org.restlet.security.User;
+
+import de.mpiwg.itgroup.annotationManager.Constants.NS;
+import de.mpiwg.itgroup.annotationManager.RDFHandling.Annotation;
+
+/**
+ * Base class for Annotator resource classes.
+ *
+ * @author dwinter, casties
+ *
+ */
+public abstract class AnnotatorResourceImpl extends ServerResource {
+
+ protected Logger logger = Logger.getRootLogger();
+
+ protected String getAllowedMethodsForHeader() {
+ return "OPTIONS,GET,POST";
+ }
+
+ public String encodeJsonId(String id) {
+ try {
+ return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ public String decodeJsonId(String id) {
+ try {
+ return new String(Base64.decodeBase64(id), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Handle options request to allow CORS for AJAX.
+ *
+ * @param entity
+ */
+ @Options
+ public void doOptions(Representation entity) {
+ logger.debug("AnnotatorResourceImpl doOptions!");
+ setCorsHeaders();
+ }
+
+ /**
+ * set headers to allow CORS for AJAX.
+ */
+ protected void setCorsHeaders() {
+ Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers");
+ if (responseHeaders == null) {
+ responseHeaders = new Form();
+ getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
+ }
+ responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader());
+ // echo back Origin and Request-Headers
+ Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
+ String origin = requestHeaders.getFirstValue("Origin", true);
+ if (origin == null) {
+ responseHeaders.add("Access-Control-Allow-Origin", "*");
+ } else {
+ responseHeaders.add("Access-Control-Allow-Origin", origin);
+ }
+ String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true);
+ if (allowHeaders != null) {
+ responseHeaders.add("Access-Control-Allow-Headers", allowHeaders);
+ }
+ responseHeaders.add("Access-Control-Allow-Credentials", "true");
+ responseHeaders.add("Access-Control-Max-Age", "60");
+ }
+
+ /**
+ * returns if authentication information from headers is valid.
+ *
+ * @param entity
+ * @return
+ */
+ public boolean isAuthenticated(Representation entity) {
+ return (checkAuthToken(entity) != null);
+ }
+
+ /**
+ * checks Annotator Auth plugin authentication information from headers. returns userId if successful.
+ *
+ * @param entity
+ * @return
+ */
+ public String checkAuthToken(Representation entity) {
+ Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
+ String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true);
+ // decode token first to get consumer key
+ JsonToken token = new JsonTokenParser(null, null).deserialize(authToken);
+ String userId = token.getParamAsPrimitive("userId").getAsString();
+ String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString();
+ // get stored consumer secret for key
+ RestServer restServer = (RestServer) getApplication();
+ String consumerSecret = restServer.getConsumerSecret(consumerKey);
+ logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret);
+ if (consumerSecret == null) {
+ return null;
+ }
+ // logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm()));
+ try {
+ List verifiers = new ArrayList();
+ // we only do HS256 yet
+ verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8")));
+ // verify token signature(should really be static...)
+ new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers);
+ } catch (SignatureException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (InvalidKeyException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // must be ok then
+ logger.debug("auth OK! user=" + userId);
+ return userId;
+ }
+
+ /**
+ * creates Annotator-JSON from an Annotation object.
+ *
+ * @param annot
+ * @return
+ */
+ public JSONObject createAnnotatorJson(Annotation annot) {
+ boolean makeUserObject = true;
+ JSONObject jo = new JSONObject();
+ try {
+ jo.put("text", annot.text);
+ jo.put("uri", annot.url);
+
+ if (makeUserObject) {
+ // create user object
+ JSONObject userObject = new JSONObject();
+ // save creator as uri
+ userObject.put("uri", annot.creator);
+ // make short user id
+ String userID = annot.creator;
+ if (userID.startsWith(NS.MPIWG_PERSONS_URL)) {
+ userID = userID.replace(NS.MPIWG_PERSONS_URL, ""); // entferne
+ // NAMESPACE
+ }
+ // save as id
+ userObject.put("id", userID);
+ // get full name
+ RestServer restServer = (RestServer) getApplication();
+ String userName = restServer.getUserNameFromLdap(userID);
+ userObject.put("name", userName);
+ // save user object
+ jo.put("user", userObject);
+ } else {
+ // save user as string
+ jo.put("user", annot.creator);
+ }
+
+ List xpointers = new ArrayList();
+ if (annot.xpointers == null || annot.xpointers.size() == 0)
+ xpointers.add(annot.xpointer);
+ else {
+ for (String xpointerString : annot.xpointers) {
+ xpointers.add(xpointerString);
+ }
+ }
+ if (!xpointers.isEmpty()) {
+ // we only look at the first xpointer
+ String xt = getXpointerType(xpointers.get(0));
+ if (xt == "range") {
+ jo.put("ranges", transformToRanges(xpointers));
+ } else if (xt == "area") {
+ jo.put("areas", transformToAreas(xpointers));
+ }
+ }
+ // encode Annotation URL (=id) in base64
+ String annotUrl = annot.getAnnotationUri();
+ String annotId = encodeJsonId(annotUrl);
+ jo.put("id", annotId);
+ return jo;
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private String getXpointerType(String xpointer) {
+ if (xpointer.contains("#xpointer")) {
+ return "range";
+ } else if (xpointer.contains("#xywh")) {
+ return "area";
+ }
+ return null;
+ }
+
+ private JSONArray transformToRanges(List xpointers) {
+
+ JSONArray ja = new JSONArray();
+
+ Pattern rg = Pattern
+ .compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)");
+ Pattern rg1 = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)");
+
+ try {
+ for (String xpointer : xpointers) {
+ String decoded = URLDecoder.decode(xpointer, "utf-8");
+ Matcher m = rg.matcher(decoded);
+
+ if (m.find()) {
+ {
+ JSONObject jo = new JSONObject();
+ jo.put("start", m.group(1));
+ jo.put("startOffset", m.group(2));
+ jo.put("end", m.group(3));
+ jo.put("endOffset", m.group(4));
+ ja.put(jo);
+ }
+ }
+ m = rg1.matcher(xpointer);
+ if (m.find()) {
+ JSONObject jo = new JSONObject();
+ jo.put("start", m.group(1));
+ jo.put("startOffset", m.group(2));
+
+ ja.put(jo);
+ }
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return ja;
+ }
+
+ private JSONArray transformToAreas(List xpointers) {
+
+ JSONArray ja = new JSONArray();
+
+ Pattern rg = Pattern.compile("#xywh=(\\w*:)([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)");
+
+ try {
+ for (String xpointer : xpointers) {
+ String decoded = URLDecoder.decode(xpointer, "utf-8");
+ Matcher m = rg.matcher(decoded);
+
+ if (m.find()) {
+ {
+ JSONObject jo = new JSONObject();
+ String unit = m.group(1);
+ jo.put("x", m.group(2));
+ jo.put("y", m.group(3));
+ jo.put("width", m.group(4));
+ jo.put("height", m.group(5));
+ ja.put(jo);
+ }
+ }
+ }
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (UnsupportedEncodingException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return ja;
+ }
+
+ /**
+ * creates an Annotation object with data from JSON.
+ *
+ * uses the specification from the annotator project: {@link https ://github.com/okfn/annotator/wiki/Annotation-format}
+ *
+ * The username will be transformed to an URI if not given already as URI, if not it will set to the MPIWG namespace defined in
+ * de.mpiwg.itgroup.annotationManager.Constants.NS
+ *
+ * @param jo
+ * @return
+ * @throws JSONException
+ */
+ public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException {
+ return updateAnnotation(new Annotation(), jo, entity);
+ }
+
+ public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException {
+ // annotated uri
+ String url = annot.url;
+ if (jo.has("uri")) {
+ url = jo.getString("uri");
+ }
+ // annotation text
+ String text = annot.text;
+ if (jo.has("text")) {
+ text = jo.getString("text");
+ }
+ // check authentication
+ String authUser = checkAuthToken(entity);
+ if (authUser == null) {
+ // try http auth
+ User httpUser = getHttpAuthUser(entity);
+ if (httpUser == null) {
+ setStatus(Status.CLIENT_ERROR_FORBIDDEN);
+ return null;
+ }
+ authUser = httpUser.getIdentifier();
+ }
+ // username not required, if no username given authuser will be used
+ String username = null;
+ String userUri = annot.creator;
+ if (jo.has("user")) {
+ if (jo.get("user") instanceof String) {
+ // user is just a String
+ username = jo.getString("user");
+ // TODO: what if username and authUser are different?
+ } else {
+ // user is an object
+ JSONObject user = jo.getJSONObject("user");
+ if (user.has("id")) {
+ username = user.getString("id");
+ }
+ if (user.has("uri")) {
+ userUri = user.getString("uri");
+ }
+ }
+ }
+ if (username == null) {
+ username = authUser;
+ }
+ // username should be a URI, if not it will set to the MPIWG namespace
+ // defined in
+ // de.mpiwg.itgroup.annotationManager.Constants.NS
+ if (userUri == null) {
+ if (username.startsWith("http")) {
+ userUri = username;
+ } else {
+ userUri = NS.MPIWG_PERSONS_URL + username;
+ }
+ }
+ // TODO: should we overwrite the creator?
+
+ // create xpointer from the first range/area
+ String xpointer = annot.xpointer;
+ if (jo.has("ranges")) {
+ JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0);
+ String start = ranges.getString("start");
+ String end = ranges.getString("end");
+ String startOffset = ranges.getString("startOffset");
+ String endOffset = ranges.getString("endOffset");
+
+ try {
+ xpointer = url
+ + "#"
+ + URLEncoder.encode(String.format(
+ "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))",
+ start, startOffset, end, endOffset), "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ }
+ }
+ if (jo.has("areas")) {
+ JSONObject area = jo.getJSONArray("areas").getJSONObject(0);
+ String x = area.getString("x");
+ String y = area.getString("y");
+ String width = "0";
+ String height = "0";
+ if (area.has("width")) {
+ width = area.getString("width");
+ height = area.getString("height");
+ }
+ try {
+ xpointer = url + "#" + URLEncoder.encode(String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height), "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ return null;
+ }
+ }
+ return new Annotation(xpointer, userUri, annot.time, text, annot.type);
+ }
+
+ /**
+ * returns the logged in User.
+ *
+ * @param entity
+ * @return
+ */
+ protected User getHttpAuthUser(Representation entity) {
+ RestServer restServer = (RestServer) getApplication();
+ if (!restServer.authenticate(getRequest(), getResponse())) {
+ // Not authenticated
+ return null;
+ }
+
+ ClientInfo ci = getRequest().getClientInfo();
+ logger.debug(ci);
+ return getRequest().getClientInfo().getUser();
+
+ }
+
+}
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/java/de/mpiwg/itgroup/annotations/restlet/RestServer.java
--- a/src/main/java/de/mpiwg/itgroup/annotations/restlet/RestServer.java Thu Jun 28 19:34:32 2012 +0200
+++ b/src/main/java/de/mpiwg/itgroup/annotations/restlet/RestServer.java Fri Jun 29 20:38:27 2012 +0200
@@ -19,6 +19,10 @@
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.neo4j.graphdb.factory.GraphDatabaseFactory;
+import org.neo4j.kernel.AbstractGraphDatabase;
+import org.neo4j.server.WrappingNeoServerBootstrapper;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.Restlet;
@@ -30,14 +34,18 @@
public class RestServer extends Application {
public static Logger logger = Logger.getRootLogger();
-
+
private ChallengeAuthenticator authenticator;
/**
* Properties holding consumer keys and secrets
*/
private Properties consumerKeys;
- public final String CONSUMER_KEYS_PATH = "WEB-INF/consumerkeys.property";
+ public String CONSUMER_KEYS_PATH = "WEB-INF/consumerkeys.property";
+
+ private GraphDatabaseService graphDb;
+ public static final String GRAPHDB_KEY = "annotationmanager.graphdb";
+ public String DB_PATH = "WEB-INF/neo4j-annotation-db";
/**
* constructor
@@ -46,50 +54,58 @@
*/
public RestServer(Context parentContext) {
super(parentContext);
-
- Logger rl = Logger.getRootLogger();
+ // TODO: is this the right place to run the log4j configurator?
BasicConfigurator.configure();
- // read consumerKeys from webapp
- consumerKeys = new Properties();
- ServletContext sc = (ServletContext) getContext().getServerDispatcher()
- .getContext().getAttributes()
+
+ ServletContext sc = (ServletContext) getContext().getServerDispatcher().getContext().getAttributes()
.get("org.restlet.ext.servlet.ServletContext");
if (sc != null) {
- InputStream ps = sc.getResourceAsStream(CONSUMER_KEYS_PATH);
- if (ps == null) {
- // try as file
- File pf = new File(sc.getRealPath(CONSUMER_KEYS_PATH));
- if (pf != null) {
- rl.debug("trying file for consumer keys: "+pf);
- try {
- ps = new FileInputStream(pf);
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
+ // look for database service in context
+ graphDb = (GraphDatabaseService) sc.getAttribute(GRAPHDB_KEY);
+ if (graphDb == null) {
+ /*
+ * open database
+ */
+ String dbFn = getResourcePath(sc, DB_PATH);
+ if (dbFn != null) {
+ logger.debug("opening DB " + dbFn);
+ graphDb = new GraphDatabaseFactory().newEmbeddedDatabase(dbFn);
+ // store in context
+ sc.setAttribute(GRAPHDB_KEY, graphDb);
+ WrappingNeoServerBootstrapper srv = new WrappingNeoServerBootstrapper((AbstractGraphDatabase) graphDb);
+ logger.debug("Starting DB admin server...");
+ srv.start();
+ } else {
+ logger.error("Unable to get resource " + DB_PATH);
}
}
+ /*
+ * read consumerKeys from webapp
+ */
+ consumerKeys = new Properties();
+ InputStream ps = getResourceAsStream(sc, CONSUMER_KEYS_PATH);
if (ps != null) {
- rl.debug("loading consumer keys from "+CONSUMER_KEYS_PATH);
+ logger.debug("loading consumer keys from " + CONSUMER_KEYS_PATH);
try {
consumerKeys.load(ps);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
- rl.debug("consumer keys: "+consumerKeys);
+ logger.debug("consumer keys: " + consumerKeys);
} else {
- rl.error("Unable to get resource "+CONSUMER_KEYS_PATH);
+ logger.error("Unable to get resource " + CONSUMER_KEYS_PATH);
}
} else {
- rl.error("Unable to get ServletContext!");
+ logger.error("Unable to get ServletContext!");
}
-
+
}
-
+
/**
- * returns consumer secret for consumer key.
- * returns null if consumer key doesn't exist.
+ * returns consumer secret for consumer key. returns null if consumer key
+ * doesn't exist.
+ *
* @param consumerKey
* @return
*/
@@ -97,7 +113,6 @@
return consumerKeys.getProperty(consumerKey);
}
-
/*
* (non-Javadoc)
*
@@ -105,26 +120,21 @@
*/
@Override
public Restlet createInboundRoot() {
- //this.authenticator = createAuthenticator();
-
- // String target = "{rh}/{rf}/XX";
- // Redirector redirector = new
- // Redirector(getContext().createChildContext(), target,
- // Redirector.MODE_CLIENT_SEE_OTHER);
+ // this.authenticator = createAuthenticator();
Router router = new Router(getContext());
- /*
+
router.attach("/annotator/annotations", AnnotatorAnnotations.class);
router.attach("/annotator/annotations/{id}", AnnotatorAnnotations.class);
- router.attach("/annotator/search", AnnotatorSearch.class);
-
- // router.attach("",redirector);
- router.attach("/annotator", ExtendedAnnotationInput.class);
- */
+ // router.attach("/annotator/search", AnnotatorSearch.class);
+
+ // router.attach("",redirector); router.attach("/annotator",
+ // ExtendedAnnotationInput.class);
+
router.attach("/", AnnotatorInfo.class);
- //authenticator.setNext(router);
- //return authenticator;
-
+ // authenticator.setNext(router);
+ // return authenticator;
+
return router;
}
@@ -141,7 +151,8 @@
String sp = "com.sun.jndi.ldap.LdapCtxFactory";
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, sp);
- String ldapUrl = "ldap://ldapreplik.mpiwg-berlin.mpg.de/dc=mpiwg-berlin,dc=mpg,dc=de"; // TODO should go into config file
+ // TODO: should go into config file
+ String ldapUrl = "ldap://ldapreplik.mpiwg-berlin.mpg.de/dc=mpiwg-berlin,dc=mpg,dc=de";
env.put(javax.naming.Context.PROVIDER_URL, ldapUrl);
DirContext dctx;
@@ -163,8 +174,7 @@
String filter = "(uid=" + creator + ")";
try {
- NamingEnumeration results = dctx.search(base, filter,
- sc);
+ NamingEnumeration results = dctx.search(base, filter, sc);
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
javax.naming.directory.Attributes attrs = sr.getAttributes();
@@ -186,4 +196,48 @@
return retString;
}
+ /**
+ * returns resource from path (in webapp) as InputStream.
+ *
+ * @param sc
+ * @param path
+ * @return
+ */
+ protected InputStream getResourceAsStream(ServletContext sc, String path) {
+ InputStream ps = sc.getResourceAsStream(path);
+ if (ps == null) {
+ // try as file
+ File pf = new File(sc.getRealPath(path));
+ if (pf != null) {
+ logger.debug("trying file for: " + pf);
+ try {
+ ps = new FileInputStream(pf);
+ } catch (FileNotFoundException e) {
+ logger.error(e);
+ }
+ }
+ }
+ return ps;
+ }
+
+ /**
+ * get a real file name for a web app file pathname.
+ *
+ * If filename starts with "/" its treated as absolute else the path is
+ * appended to the base directory of the web-app.
+ *
+ * @param filename
+ * @param sc
+ * @return
+ */
+ public static String getResourcePath(ServletContext sc, String filename) {
+ File f = new File(filename);
+ // is the filename absolute?
+ if (!f.isAbsolute()) {
+ // relative path -> use getRealPath to resolve in webapp
+ filename = sc.getRealPath(filename);
+ }
+ return filename;
+ }
+
}
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/resources/de/itgroup/annotations/annotator-info.html
--- a/src/main/resources/de/itgroup/annotations/annotator-info.html Thu Jun 28 19:34:32 2012 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-
-
-Annotation manager
-Annotations overview
-
-
\ No newline at end of file
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/resources/de/mpiwg/itgroup/annotations/annotator-info.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/de/mpiwg/itgroup/annotations/annotator-info.html Fri Jun 29 20:38:27 2012 +0200
@@ -0,0 +1,6 @@
+
+
+Annotation manager
+Annotations overview
+
+
\ No newline at end of file
diff -r f2d44c41eedf -r 47b53ae385d1 src/main/webapp/WEB-INF/web.xml
--- a/src/main/webapp/WEB-INF/web.xml Thu Jun 28 19:34:32 2012 +0200
+++ b/src/main/webapp/WEB-INF/web.xml Fri Jun 29 20:38:27 2012 +0200
@@ -4,7 +4,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
- RESTfulJavaWebServices-Restlet
+ AnnotationManager-Restlet
org.restlet.application