Mercurial > hg > AnnotationManagerN4J
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(); + + } + +}