comparison 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
comparison
equal deleted inserted replaced
6:5bb7cc86069c 7:97f68ab3430f
1 //TODO: handle XML-Post des Annoteaprotocolls http://www.w3.org/2001/Annotea/User/Protocol.html
2
3 package de.mpiwg.itgroup.annotationManager.restlet;
4
5 import java.io.IOException;
6 import java.io.UnsupportedEncodingException;
7 import java.net.URLDecoder;
8 import java.net.URLEncoder;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13
14 import org.apache.log4j.Logger;
15 import org.json.JSONArray;
16 import org.json.JSONException;
17 import org.json.JSONObject;
18 import org.restlet.Context;
19 import org.restlet.data.ClientInfo;
20 import org.restlet.data.Form;
21 import org.restlet.data.MediaType;
22 import org.restlet.data.Status;
23 import org.restlet.ext.json.JsonRepresentation;
24 import org.restlet.representation.Representation;
25 import org.restlet.representation.StringRepresentation;
26 import org.restlet.resource.Get;
27 import org.restlet.resource.Options;
28 import org.restlet.resource.Post;
29 import org.restlet.resource.ServerResource;
30 import org.restlet.security.User;
31
32 import de.mpiwg.itgroup.annotationManager.Constants.NS;
33 import de.mpiwg.itgroup.annotationManager.Errors.TripleStoreSearchError;
34 import de.mpiwg.itgroup.annotationManager.Errors.TripleStoreStoreError;
35 import de.mpiwg.itgroup.annotationManager.RDFHandling.Convert;
36 import de.mpiwg.itgroup.annotationManager.RDFHandling.Convert.Annotation;
37 import de.mpiwg.itgroup.annotationManager.RDFHandling.RDFSearcher;
38 import de.mpiwg.itgroup.annotationManager.drupal.AnnotationHandler;
39 import de.mpiwg.itgroup.annotationManager.drupal.UnknowUserException;
40 import de.mpiwg.itgroup.triplestoremanager.exceptions.TripleStoreHandlerException;
41
42
43 public class AddAndReadAnnotations extends ServerResource {
44
45 private Logger logger = Logger.getRootLogger();
46
47
48
49 @Options
50 public void doOptions(Representation entity) {
51 Form responseHeaders = (Form) getResponse().getAttributes().get(
52 "org.restlet.http.headers");
53 if (responseHeaders == null) {
54 responseHeaders = new Form();
55 getResponse().getAttributes().put("org.restlet.http.headers",
56 responseHeaders);
57 }
58 Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
59 String origin = requestHeaders.getFirstValue("Origin", true);
60 if (origin == null) {
61 responseHeaders.add("Access-Control-Allow-Origin", "*");
62 } else {
63 responseHeaders.add("Access-Control-Allow-Origin", origin);
64 }
65 String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true);
66 if (allowHeaders != null) {
67 responseHeaders.add("Access-Control-Allow-Headers", allowHeaders);
68 }
69 responseHeaders.add("Access-Control-Allow-Methods", "POST,OPTIONS,GET");
70 responseHeaders.add("Access-Control-Allow-Credentials", "true");
71 responseHeaders.add("Access-Control-Max-Age", "60");
72 }
73
74 @Get("html")
75 public Representation doGetHTML(Representation entity){
76
77 doOptions(entity);
78 Form form = getRequest().getResourceRef().getQueryAsForm();
79 String uri = form.getFirstValue("uri");
80 String user = form.getFirstValue("user");
81
82 String limit=form.getFirstValue("limit");
83 String offset=form.getFirstValue("offset");
84
85 try {
86 if (uri!=null){
87 uri = URLDecoder.decode(uri, "utf-8");
88 }
89 } catch (UnsupportedEncodingException e1) {
90 e1.printStackTrace();
91 setStatus(Status.CLIENT_ERROR_NOT_ACCEPTABLE);
92 return null;
93 }
94
95 //
96 RDFSearcher searcher = new RDFSearcher("file:///annotations"); //TODO should ge into config file
97
98 String retString="<html><body><table>";
99 String lineFormat="<tr><td><a href=\"%s\">%s</a></td>" +
100 "<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>";
101 try {
102
103 List<Convert.Annotation> annots=searcher.search(uri,user,limit,offset);
104
105 for (Convert.Annotation annot:annots){
106
107
108 RestServer restServer = (RestServer) getApplication();
109 String userName=restServer.getUserNameFromLdap(annot.creator);
110 List<String> xpointer = new ArrayList<String>();
111
112 if (annot.xpointers==null || annot.xpointers.size()==0)
113 retString+=String.format(lineFormat, userName,userName,annot.url,annot.url,annot.time,annot.text,annot.xpointer,annot.xpointer,annot.annotationUri,annot.annotationUri);
114 else {
115 for(String xpointerString:annot.xpointers){
116 retString+=String.format(lineFormat, userName,userName,annot.url,annot.url,annot.time,annot.text,xpointerString,xpointerString,annot.annotationUri,annot.annotationUri);
117 }
118 }
119
120 }
121 } catch (TripleStoreHandlerException e) {
122 // TODO Auto-generated catch block
123 e.printStackTrace();
124 setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreHandler Error");
125 return null;
126 } catch (TripleStoreSearchError e) {
127 // TODO Auto-generated catch block
128 e.printStackTrace();
129 setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreSearch Error");
130 return null;
131 }
132
133 retString+="</table></body></html>";
134
135 logger.debug("sending:");
136 logger.debug(retString);
137 return new StringRepresentation(retString,MediaType.TEXT_HTML);
138 }
139
140 /**
141 * Erzeugt aus einer Annotation, das f�r den Annotator notwendige JSON-Format
142 * @param annot
143 * @return
144 */
145 public JSONObject annot2AnnotatorJSON(Convert.Annotation annot){
146 JSONObject jo = new JSONObject();
147 try {
148 jo.put("text", annot.text);
149 jo.put("uri",annot.url);
150
151 JSONObject userObject= new JSONObject();
152 userObject.put("id",annot.creator);
153
154 RestServer restServer = (RestServer) getApplication();
155
156 String userID= annot.creator;
157 if (userID.startsWith(NS.MPIWG_PERSONS)){
158 userID=userID.replace(NS.MPIWG_PERSONS, ""); //entferne NAMESPACE
159 }
160 String userName=restServer.getUserNameFromLdap(userID);
161 userObject.put("name",userName);
162
163 jo.put("user",userObject);
164
165 List<String> xpointer = new ArrayList<String>();
166
167 if (annot.xpointers==null || annot.xpointers.size()==0)
168 xpointer.add(annot.xpointer);
169 else {
170 for(String xpointerString:annot.xpointers){
171 xpointer.add(xpointerString);
172 }
173 }
174 jo.put("ranges", transformToRanges(xpointer));
175 jo.put("id", annot.annotationUri);
176 return jo;
177 } catch (JSONException e) {
178 // TODO Auto-generated catch block
179 e.printStackTrace();
180 return null;
181 }
182 }
183
184 @Get("json")
185 public Representation doGetJSON(Representation entity){
186
187 doOptions(entity);
188 //TODO: Annotator read request does not use parameters
189 Form form = getRequest().getResourceRef().getQueryAsForm();
190 String uri = form.getFirstValue("uri");
191 String user = form.getFirstValue("user");
192
193 String limit=form.getFirstValue("limit");
194 String offset=form.getFirstValue("offset");
195
196
197 //
198 RDFSearcher searcher = new RDFSearcher("file:///annotations"); //TODO should ge into config file
199
200 JSONArray ja;
201 try {
202
203 List<Convert.Annotation> annots=searcher.search(uri,user,limit,offset);
204
205 ja = new JSONArray();
206 for (Convert.Annotation annot:annots){
207 // JSONObject jo = new JSONObject();
208 // jo.put("text", annot.text);
209 // jo.put("uri",annot.url);
210 //
211 // JSONObject userObject= new JSONObject();
212 // userObject.put("id",annot.creator);
213 //
214 // RestServer restServer = (RestServer) getApplication();
215 //
216 // String userID= annot.creator;
217 // if (userID.startsWith(NS.MPIWG_PERSONS)){
218 // userID=userID.replace(NS.MPIWG_PERSONS, ""); //entferne NAMESPACE
219 // }
220 // String userName=restServer.getUserNameFromLdap(userID);
221 // userObject.put("name",userName);
222 //
223 // jo.put("user",userObject);
224 //
225 // List<String> xpointer = new ArrayList<String>();
226 //
227 // if (annot.xpointers==null || annot.xpointers.size()==0)
228 // xpointer.add(annot.xpointer);
229 // else {
230 // for(String xpointerString:annot.xpointers){
231 // xpointer.add(xpointerString);
232 // }
233 // }
234 // jo.put("ranges", transformToRanges(xpointer));
235 JSONObject jo = annot2AnnotatorJSON(annot);
236 if (jo!=null){
237 ja.put(annot2AnnotatorJSON(annot));
238 } else {
239 setStatus(Status.SERVER_ERROR_INTERNAL,"JSon Error");
240 return null;
241 }
242 }
243 } catch (TripleStoreHandlerException e) {
244 // TODO Auto-generated catch block
245 e.printStackTrace();
246 setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreHandler Error");
247 return null;
248 } catch (TripleStoreSearchError e) {
249 // TODO Auto-generated catch block
250 e.printStackTrace();
251 setStatus(Status.SERVER_ERROR_INTERNAL,"TripleStoreSearch Error");
252 return null;
253 }
254
255 // annotator read request returns a list of annotation objects
256 logger.debug("sending:");
257 logger.debug(ja);
258 return new JsonRepresentation(ja);
259 }
260
261 private JSONArray transformToRanges(List<String> xpointers) {
262
263 JSONArray ja = new JSONArray();
264
265 Pattern rg = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)");
266 Pattern rg1 = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)");
267
268
269
270 try {
271 for(String xpointer:xpointers){
272 String decoded =URLDecoder.decode(xpointer,"utf-8");
273 Matcher m=rg.matcher(decoded);
274
275 if (m.find()){
276 {
277 JSONObject jo = new JSONObject();
278 jo.put("start", m.group(1));
279 jo.put("startOffset", m.group(2));
280 jo.put("end", m.group(3));
281 jo.put("endOffset", m.group(4));
282 ja.put(jo);
283 }
284 }
285 m=rg1.matcher(xpointer);
286 if (m.find()){
287 JSONObject jo = new JSONObject();
288 jo.put("start", m.group(1));
289 jo.put("startOffset", m.group(2));
290
291 ja.put(jo);
292 }
293
294
295 }
296 } catch (JSONException e) {
297 // TODO Auto-generated catch block
298 e.printStackTrace();
299 } catch (UnsupportedEncodingException e) {
300 // TODO Auto-generated catch block
301 e.printStackTrace();
302 }
303
304
305 return ja;
306
307
308
309
310
311 }
312
313
314 /**
315 *
316 * json hash: username: name des users xpointer: xpointer auf den Ausschnitt
317 * (incl. der URL des Dokumentes) text: text der annotation annoturl: url
318 * auf eine Annotation falls extern
319 *
320 * @return
321 */
322
323 @Post("json")
324 public Representation doPostJson(Representation entity) {
325
326 Annotation retVal = doPost(entity);
327
328
329 // JSONObject jo;
330 // try {
331 // jo = new JSONObject("{\"annotUrl\":\"" + retVal + "\"}");
332 // } catch (JSONException e) {
333 // setStatus(Status.SERVER_ERROR_INTERNAL);
334 // return null;
335 // }
336
337 if (retVal==null)
338 return null;
339 JSONObject jo = annot2AnnotatorJSON(retVal);
340 JsonRepresentation retRep = new JsonRepresentation(jo);
341 return retRep;
342 }
343
344 @Post("html")
345 public Representation doPostHtml(Representation entity) {
346 Annotation retValAnnot = doPost(entity);
347 if (retValAnnot == null) {
348 return null;
349 }
350
351
352 String retVal=retValAnnot.annotationUri;
353 if (retVal == null) {
354 return null;
355 }
356
357 String text = String.format(
358 "<html><body><a href=\"%s\">%s</a></body></html>", retVal
359 .replace(">", "").replace("<", ""),
360 retVal.replace(">", "&gt;").replace("<", "&lt;"));
361 Representation retRep = new StringRepresentation(text,
362 MediaType.TEXT_HTML);
363 return retRep;
364 }
365
366 public Convert.Annotation doPost(Representation entity) {
367
368 doOptions(entity);
369 Convert.Annotation annot;
370 // versuche basic authentifizierung und hole den Benutzer von dort.
371
372 // User authUser;= handleBasicAuthentification(entity);
373
374 if (entity.getMediaType().equals(MediaType.APPLICATION_JSON)) {
375
376 JsonRepresentation jrep;
377 try {
378 jrep = new JsonRepresentation(entity);
379 } catch (IOException e1) {
380 setStatus(Status.SERVER_ERROR_INTERNAL);
381 return null;
382 }
383
384 // try {
385 // logger.debug(jrep.getText());
386 // } catch (IOException e1) {
387 // // TODO Auto-generated catch block
388 // e1.printStackTrace();
389 // }
390 //
391
392 try {
393 JSONObject jo = jrep.getJsonObject();
394 if(jo==null){
395 setStatus(Status.SERVER_ERROR_INTERNAL);
396 return null;
397 }
398
399 String mode=null;
400 if(jo.has("mode")){
401 mode = jo.getString("mode"); // hole modus
402 }
403 if (mode==null || mode.equals(""))
404 mode="annotea"; // default mode (annotea) TODO make this configurable
405
406 if (mode.equals("annotator") ) { // annotator format
407 annot = handleAnnotatorSchema(jo, entity);
408 logger.debug("storing annotator object");
409 logger.debug(jo);
410 } else if (mode.equals("annotea")){
411 annot = handleAnnotea(jo, entity);
412 } else {
413 setStatus(Status.CLIENT_ERROR_BAD_REQUEST,"mode "+mode+"not supported!");
414 return null;
415 }
416
417 } catch (JSONException e) {
418 setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
419 return null;
420 }
421
422 } else if (entity.getMediaType().equals(MediaType.APPLICATION_WWW_FORM)) {
423 annot = handleForm(entity);
424
425 } else {
426 setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE);
427
428 return null;
429 }
430
431 if (annot==null){
432 return null;
433 }
434 if (annot.xpointer == null || annot.creator == null) {
435 setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
436
437 return null;
438 }
439
440
441
442 try {
443 return new Convert("file:///annotations").storeAnnotation(annot);
444 } catch (TripleStoreStoreError e) {
445 e.printStackTrace();
446 setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStore Error");
447 return null;
448 }
449 }
450
451
452 /**
453 *
454 * @param entity should contain a form with the parameters "username", "password", "xpointer","text","uri","type"
455 *
456 * username,password is optional, if not given BasicAuthentification is used.
457 *
458 *
459 *
460 * 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
461 *
462 * @return
463 */
464 protected Convert.Annotation handleForm(Representation entity) {
465 Convert.Annotation annot;
466 Form form = new Form(entity);
467 String username = form.getValues("username");
468 String mode = form.getValues("mode");
469 String password = form.getValues("password");
470 String xpointer = form.getValues("xpointer");
471 String text = form.getValues("text");
472 String title = form.getValues("title");
473 String url = form.getValues("url");
474 String type = form.getValues("type");
475 RestServer restServer = (RestServer) getApplication();
476
477 // falls user and password nicht null sind:
478 User userFromForm = null;
479 if (username != null && password != null) {
480 if (restServer.authenticate(username, password, getRequest())) {
481 userFromForm = new User(username);
482 }
483 }
484 User authUser = null;
485
486 if (userFromForm == null) {
487 authUser = handleBasicAuthentification(entity);
488 }
489
490 // weder BasicAuth noch FormAuth
491 if (authUser == null && userFromForm == null) {
492 setStatus(Status.CLIENT_ERROR_FORBIDDEN);
493 return null;
494 }
495
496 if (userFromForm != null) {
497 username = userFromForm.getIdentifier();
498 } else {
499 username = authUser.getIdentifier();
500 }
501
502 //username should be a URI, if not it will set to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS
503 String usernameOrig=username;
504 if (!username.startsWith("http"))
505 username=NS.MPIWG_PERSONS+username;
506
507 if (mode.equals("complexAnnotation")){// Annotation mit text in externer ressource
508
509 Context context = getContext();
510 String drupalPath = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.drupalServer");
511
512
513 AnnotationHandler ah = new AnnotationHandler(drupalPath);
514 JSONObject newAnnot;
515 try {
516 newAnnot = ah.createAnnotation(title, text, usernameOrig, password);
517 } catch (UnknowUserException e1) {
518 setStatus(Status.CLIENT_ERROR_FORBIDDEN);
519 e1.printStackTrace();
520 return null;
521 }
522 try {
523 annot= new Convert.Annotation(xpointer, username, null, text, type, newAnnot.getString("node_uri"));
524 } catch (JSONException e) {
525 // TODO Auto-generated catch block
526 e.printStackTrace();
527 setStatus(Status.SERVER_ERROR_INTERNAL);
528 return null;
529 }
530 } else
531 annot = new Convert.Annotation(xpointer, username, null, text,
532 type, url);
533 return annot;
534 }
535
536 @Post
537 public Representation doPostHtml2(Representation entity) {
538 return doPostHtml(entity);
539 }
540
541 private User handleBasicAuthentification(Representation entity) {
542 RestServer restServer = (RestServer) getApplication();
543 if (!restServer.authenticate(getRequest(), getResponse())) {
544 // Not authenticated
545 return null;
546 }
547
548 ClientInfo ci = getRequest().getClientInfo();
549 logger.debug(ci);
550 return getRequest().getClientInfo().getUser();
551
552 }
553
554 /**
555 * using a minimal annotation format based on the annotea specification
556 *
557 * @param jo
558 * must contain xpointer, text,url,type and can contain a
559 * username, if not the username form the authentification will
560 * be used.
561 * @param authUser
562 * user object
563 * 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
564
565 * @return
566 * @throws JSONException
567 */
568 public Annotation handleAnnotea(JSONObject jo, Representation entity)
569 throws JSONException {
570
571 User authUser = handleBasicAuthentification(entity);
572 String username = jo.getString("username"); // not required, if no
573 // username given authuser
574 // will be used.
575 String xpointer = jo.getString("xpointer");
576 String text = null;
577 if (jo.has("text"))
578 text = jo.getString("text");
579
580 String url = null;
581 if (jo.has("url"))
582 url = jo.getString("url");
583
584 String type = null;
585 if (jo.has("type"))
586 type = jo.getString("type");
587
588 if (username == null)
589 username = authUser.getIdentifier();
590
591 //username should be a URI, if not it will set to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS
592 if (!username.startsWith("http"))
593 username=NS.MPIWG_PERSONS+username;
594
595 return new Convert.Annotation(xpointer, username, null, text, type, url);
596 }
597
598 /**
599 * uses the specification from the annotator project.
600 *
601 * @see{https://github.com/okfn/annotator/wiki/Annotation-format} The user
602 * object must
603 * contain an
604 * id and
605 * password or
606 * basic
607 * authentification
608 * is used.
609 * 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
610 * @param jo
611 * @param authUser
612 * @return
613 * @throws JSONException
614 */
615 public Convert.Annotation handleAnnotatorSchema(JSONObject jo,
616 Representation entity) throws JSONException {
617 Convert.Annotation annot;
618 String url = jo.getString("uri");
619 String text = jo.getString("text");
620
621 String username = null;
622 if (jo.has("user")) { // not required, if no username given authuser
623 // will be used otherwise username and password
624 // has to be submitted
625 JSONObject user = jo.getJSONObject("user");
626 if (user.has("id")) {
627 username = user.getString("id");
628 if(!user.has("password")){
629 User authUser = handleBasicAuthentification(entity);
630 if (authUser==null){
631 setStatus(Status.CLIENT_ERROR_FORBIDDEN);
632 return null;
633 }
634 username = authUser.getIdentifier();
635 } else {
636 String password = user.getString("password");
637 if (!((RestServer) getApplication()).authenticate(username,
638 password, getRequest())) {
639 setStatus(Status.CLIENT_ERROR_FORBIDDEN);
640 return null;
641 }
642 }
643 }
644
645 } else {
646 User authUser = handleBasicAuthentification(entity);
647 if (authUser == null) {
648 setStatus(Status.CLIENT_ERROR_FORBIDDEN);
649 return null;
650 }
651 username = authUser.getIdentifier();
652 }
653
654 String xpointer;
655 if (jo.has("ranges")) {
656 JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0);
657 String start = ranges.getString("start");
658 String end = ranges.getString("end");
659 String startOffset = ranges.getString("startOffset");
660 String endOffset = ranges.getString("endOffset");
661
662 try {
663 xpointer = url+"#"+
664 URLEncoder.encode(String.format(
665 "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))",
666 start, startOffset, end, endOffset),"utf-8");
667 } catch (UnsupportedEncodingException e) {
668 e.printStackTrace();
669 setStatus(Status.SERVER_ERROR_INTERNAL);
670 return null;
671 }
672 } else {
673 xpointer = url;
674 }
675
676 //username should be a URI, if not it will set to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS
677 if (!username.startsWith("http"))
678 username=NS.MPIWG_PERSONS+username;
679
680 return new Convert.Annotation(xpointer, username, null, text, null);
681 }
682
683 }