diff src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 3:47b53ae385d1

merging old code
author casties
date Fri, 29 Jun 2012 20:38:27 +0200
parents
children 3599b29c393f
line wrap: on
line diff
--- /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<Verifier> verifiers = new ArrayList<Verifier>();
+            // 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<String> xpointers = new ArrayList<String>();
+            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<String> 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<String> 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();
+
+    }
+
+}