Mercurial > hg > AnnotationManager
diff src/de/mpiwg/itgroup/annotationManager/restlet/AddAndReadAnnotations.java @ 7:97f68ab3430f
support both read and search api of Annotator.
some cleanup of imports.
author | casties |
---|---|
date | Mon, 19 Mar 2012 12:01:39 +0100 |
parents | src/de/mpiwg/itgroup/annotationManager/restlet/AddAndSearchAnnotations.java@0be9d53a6967 |
children | 667d98fd28bd |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/de/mpiwg/itgroup/annotationManager/restlet/AddAndReadAnnotations.java Mon Mar 19 12:01:39 2012 +0100 @@ -0,0 +1,683 @@ +//TODO: handle XML-Post des Annoteaprotocolls http://www.w3.org/2001/Annotea/User/Protocol.html + +package de.mpiwg.itgroup.annotationManager.restlet; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.restlet.Context; +import org.restlet.data.ClientInfo; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +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.Get; +import org.restlet.resource.Options; +import org.restlet.resource.Post; +import org.restlet.resource.ServerResource; +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.Convert; +import de.mpiwg.itgroup.annotationManager.RDFHandling.Convert.Annotation; +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; + + +public class AddAndReadAnnotations extends ServerResource { + + private Logger logger = Logger.getRootLogger(); + + + + @Options + public void doOptions(Representation entity) { + Form responseHeaders = (Form) getResponse().getAttributes().get( + "org.restlet.http.headers"); + if (responseHeaders == null) { + responseHeaders = new Form(); + getResponse().getAttributes().put("org.restlet.http.headers", + responseHeaders); + } + 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-Methods", "POST,OPTIONS,GET"); + responseHeaders.add("Access-Control-Allow-Credentials", "true"); + responseHeaders.add("Access-Control-Max-Age", "60"); + } + + @Get("html") + public Representation doGetHTML(Representation entity){ + + doOptions(entity); + Form form = getRequest().getResourceRef().getQueryAsForm(); + String uri = form.getFirstValue("uri"); + String user = form.getFirstValue("user"); + + String limit=form.getFirstValue("limit"); + String offset=form.getFirstValue("offset"); + + try { + if (uri!=null){ + uri = URLDecoder.decode(uri, "utf-8"); + } + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + setStatus(Status.CLIENT_ERROR_NOT_ACCEPTABLE); + return null; + } + +// + RDFSearcher searcher = new RDFSearcher("file:///annotations"); //TODO should ge into config file + + String retString="<html><body><table>"; + String lineFormat="<tr><td><a href=\"%s\">%s</a></td>" + + "<td><a href=\"%s\">%s</a></td><td>%s</td><td>%s</td><td><a href=\"%s\">%s</a></td><td><a href=\"%s\">%s</a></td></div>"; + try { + + List<Convert.Annotation> annots=searcher.search(uri,user,limit,offset); + + for (Convert.Annotation annot:annots){ + + + RestServer restServer = (RestServer) getApplication(); + String userName=restServer.getUserNameFromLdap(annot.creator); + List<String> xpointer = new ArrayList<String>(); + + if (annot.xpointers==null || annot.xpointers.size()==0) + retString+=String.format(lineFormat, userName,userName,annot.url,annot.url,annot.time,annot.text,annot.xpointer,annot.xpointer,annot.annotationUri,annot.annotationUri); + else { + for(String xpointerString:annot.xpointers){ + retString+=String.format(lineFormat, userName,userName,annot.url,annot.url,annot.time,annot.text,xpointerString,xpointerString,annot.annotationUri,annot.annotationUri); + } + } + + } + } catch (TripleStoreHandlerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreHandler Error"); + return null; + } catch (TripleStoreSearchError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreSearch Error"); + return null; + } + + retString+="</table></body></html>"; + + logger.debug("sending:"); + logger.debug(retString); + return new StringRepresentation(retString,MediaType.TEXT_HTML); + } + + /** + * Erzeugt aus einer Annotation, das f�r den Annotator notwendige JSON-Format + * @param annot + * @return + */ + public JSONObject annot2AnnotatorJSON(Convert.Annotation annot){ + JSONObject jo = new JSONObject(); + try { + jo.put("text", annot.text); + jo.put("uri",annot.url); + + JSONObject userObject= new JSONObject(); + userObject.put("id",annot.creator); + + RestServer restServer = (RestServer) getApplication(); + + String userID= annot.creator; + if (userID.startsWith(NS.MPIWG_PERSONS)){ + userID=userID.replace(NS.MPIWG_PERSONS, ""); //entferne NAMESPACE + } + String userName=restServer.getUserNameFromLdap(userID); + userObject.put("name",userName); + + jo.put("user",userObject); + + List<String> xpointer = new ArrayList<String>(); + + if (annot.xpointers==null || annot.xpointers.size()==0) + xpointer.add(annot.xpointer); + else { + for(String xpointerString:annot.xpointers){ + xpointer.add(xpointerString); + } + } + jo.put("ranges", transformToRanges(xpointer)); + jo.put("id", annot.annotationUri); + return jo; + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + @Get("json") + public Representation doGetJSON(Representation entity){ + + doOptions(entity); + //TODO: Annotator read request does not use parameters + Form form = getRequest().getResourceRef().getQueryAsForm(); + String uri = form.getFirstValue("uri"); + String user = form.getFirstValue("user"); + + String limit=form.getFirstValue("limit"); + String offset=form.getFirstValue("offset"); + + +// + RDFSearcher searcher = new RDFSearcher("file:///annotations"); //TODO should ge into config file + + JSONArray ja; + try { + + List<Convert.Annotation> annots=searcher.search(uri,user,limit,offset); + + ja = new JSONArray(); + for (Convert.Annotation annot:annots){ +// JSONObject jo = new JSONObject(); +// jo.put("text", annot.text); +// jo.put("uri",annot.url); +// +// JSONObject userObject= new JSONObject(); +// userObject.put("id",annot.creator); +// +// RestServer restServer = (RestServer) getApplication(); +// +// String userID= annot.creator; +// if (userID.startsWith(NS.MPIWG_PERSONS)){ +// userID=userID.replace(NS.MPIWG_PERSONS, ""); //entferne NAMESPACE +// } +// String userName=restServer.getUserNameFromLdap(userID); +// userObject.put("name",userName); +// +// jo.put("user",userObject); +// +// List<String> xpointer = new ArrayList<String>(); +// +// if (annot.xpointers==null || annot.xpointers.size()==0) +// xpointer.add(annot.xpointer); +// else { +// for(String xpointerString:annot.xpointers){ +// xpointer.add(xpointerString); +// } +// } +// jo.put("ranges", transformToRanges(xpointer)); + JSONObject jo = annot2AnnotatorJSON(annot); + if (jo!=null){ + ja.put(annot2AnnotatorJSON(annot)); + } else { + setStatus(Status.SERVER_ERROR_INTERNAL,"JSon Error"); + return null; + } + } + } catch (TripleStoreHandlerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreHandler Error"); + return null; + } catch (TripleStoreSearchError e) { + // TODO Auto-generated catch block + e.printStackTrace(); + setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreSearch Error"); + return null; + } + + // annotator read request returns a list of annotation objects + logger.debug("sending:"); + logger.debug(ja); + return new JsonRepresentation(ja); + } + + 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; + + + + + + } + + + /** + * + * 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) { + + Annotation retVal = doPost(entity); + + +// JSONObject jo; +// try { +// jo = new JSONObject("{\"annotUrl\":\"" + retVal + "\"}"); +// } catch (JSONException e) { +// setStatus(Status.SERVER_ERROR_INTERNAL); +// return null; +// } + + if (retVal==null) + return null; + JSONObject jo = annot2AnnotatorJSON(retVal); + JsonRepresentation retRep = new JsonRepresentation(jo); + return retRep; + } + + @Post("html") + public Representation doPostHtml(Representation entity) { + Annotation retValAnnot = doPost(entity); + if (retValAnnot == null) { + return null; + } + + + String retVal=retValAnnot.annotationUri; + if (retVal == null) { + return null; + } + + String text = String.format( + "<html><body><a href=\"%s\">%s</a></body></html>", retVal + .replace(">", "").replace("<", ""), + retVal.replace(">", ">").replace("<", "<")); + Representation retRep = new StringRepresentation(text, + MediaType.TEXT_HTML); + return retRep; + } + + public Convert.Annotation doPost(Representation entity) { + + doOptions(entity); + Convert.Annotation annot; + // versuche basic authentifizierung und hole den Benutzer von dort. + + // User authUser;= handleBasicAuthentification(entity); + + if (entity.getMediaType().equals(MediaType.APPLICATION_JSON)) { + + JsonRepresentation jrep; + try { + jrep = new JsonRepresentation(entity); + } catch (IOException e1) { + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + + // try { + // logger.debug(jrep.getText()); + // } catch (IOException e1) { + // // TODO Auto-generated catch block + // e1.printStackTrace(); + // } + // + + try { + JSONObject jo = jrep.getJsonObject(); + if(jo==null){ + setStatus(Status.SERVER_ERROR_INTERNAL); + return null; + } + + String mode=null; + if(jo.has("mode")){ + mode = jo.getString("mode"); // hole modus + } + if (mode==null || mode.equals("")) + mode="annotea"; // default mode (annotea) TODO make this configurable + + if (mode.equals("annotator") ) { // annotator format + annot = handleAnnotatorSchema(jo, entity); + logger.debug("storing annotator object"); + logger.debug(jo); + } else if (mode.equals("annotea")){ + annot = handleAnnotea(jo, entity); + } else { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"mode "+mode+"not supported!"); + return null; + } + + } catch (JSONException e) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + return null; + } + + } else if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM)) { + annot = handleForm(entity); + + } else { + setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); + + return null; + } + + if (annot==null){ + return null; + } + if (annot.xpointer == null || annot.creator == null) { + setStatus(Status.CLIENT_ERROR_BAD_REQUEST); + + return null; + } + + + + try { + return new Convert("file:///annotations").storeAnnotation(annot); + } catch (TripleStoreStoreError e) { + e.printStackTrace(); + setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStore Error"); + return null; + } + } + + + /** + * + * @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 Convert.Annotation handleForm(Representation entity) { + Convert.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 = handleBasicAuthentification(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+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 Convert.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 Convert.Annotation(xpointer, username, null, text, + type, url); + return annot; + } + + @Post + public Representation doPostHtml2(Representation entity) { + return doPostHtml(entity); + } + + private User handleBasicAuthentification(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(); + + } + + /** + * using a minimal annotation format based on the annotea specification + * + * @param jo + * must contain xpointer, text,url,type and can contain a + * username, if not the username form the authentification will + * be used. + * @param authUser + * user object + * 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 + + * @return + * @throws JSONException + */ + public Annotation handleAnnotea(JSONObject jo, Representation entity) + throws JSONException { + + User authUser = handleBasicAuthentification(entity); + String username = jo.getString("username"); // not required, if no + // username given authuser + // will be used. + String xpointer = jo.getString("xpointer"); + String text = null; + if (jo.has("text")) + text = jo.getString("text"); + + String url = null; + if (jo.has("url")) + url = jo.getString("url"); + + String type = null; + if (jo.has("type")) + type = jo.getString("type"); + + if (username == null) + 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 + if (!username.startsWith("http")) + username=NS.MPIWG_PERSONS+username; + + return new Convert.Annotation(xpointer, username, null, text, type, url); + } + + /** + * uses the specification from the annotator project. + * + * @see{https://github.com/okfn/annotator/wiki/Annotation-format} The user + * object must + * contain an + * id and + * password or + * basic + * authentification + * is used. + * 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 + * @param authUser + * @return + * @throws JSONException + */ + public Convert.Annotation handleAnnotatorSchema(JSONObject jo, + Representation entity) throws JSONException { + Convert.Annotation annot; + String url = jo.getString("uri"); + String text = jo.getString("text"); + + String username = null; + if (jo.has("user")) { // not required, if no username given authuser + // will be used otherwise username and password + // has to be submitted + JSONObject user = jo.getJSONObject("user"); + if (user.has("id")) { + username = user.getString("id"); + if(!user.has("password")){ + User authUser = handleBasicAuthentification(entity); + if (authUser==null){ + setStatus(Status.CLIENT_ERROR_FORBIDDEN); + return null; + } + username = authUser.getIdentifier(); + } else { + String password = user.getString("password"); + if (!((RestServer) getApplication()).authenticate(username, + password, getRequest())) { + setStatus(Status.CLIENT_ERROR_FORBIDDEN); + return null; + } + } + } + + } else { + User authUser = handleBasicAuthentification(entity); + if (authUser == null) { + setStatus(Status.CLIENT_ERROR_FORBIDDEN); + return null; + } + username = authUser.getIdentifier(); + } + + String 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; + } + } else { + xpointer = url; + } + + //username should be a URI, if not it will set to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS + if (!username.startsWith("http")) + username=NS.MPIWG_PERSONS+username; + + return new Convert.Annotation(xpointer, username, null, text, null); + } + +}