Mercurial > hg > AnnotationManagerN4J
comparison 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 |
comparison
equal
deleted
inserted
replaced
2:f2d44c41eedf | 3:47b53ae385d1 |
---|---|
1 /** | |
2 * Base class for Annotator resource classes. | |
3 */ | |
4 package de.mpiwg.itgroup.annotations.restlet; | |
5 | |
6 import java.io.UnsupportedEncodingException; | |
7 import java.net.URLDecoder; | |
8 import java.net.URLEncoder; | |
9 import java.security.InvalidKeyException; | |
10 import java.security.SignatureException; | |
11 import java.util.ArrayList; | |
12 import java.util.List; | |
13 import java.util.regex.Matcher; | |
14 import java.util.regex.Pattern; | |
15 | |
16 import net.oauth.jsontoken.Checker; | |
17 import net.oauth.jsontoken.JsonToken; | |
18 import net.oauth.jsontoken.JsonTokenParser; | |
19 import net.oauth.jsontoken.SystemClock; | |
20 import net.oauth.jsontoken.crypto.HmacSHA256Verifier; | |
21 import net.oauth.jsontoken.crypto.Verifier; | |
22 | |
23 import org.apache.commons.codec.binary.Base64; | |
24 import org.apache.log4j.Logger; | |
25 import org.json.JSONArray; | |
26 import org.json.JSONException; | |
27 import org.json.JSONObject; | |
28 import org.restlet.data.ClientInfo; | |
29 import org.restlet.data.Form; | |
30 import org.restlet.data.Status; | |
31 import org.restlet.representation.Representation; | |
32 import org.restlet.resource.Options; | |
33 import org.restlet.resource.ServerResource; | |
34 import org.restlet.security.User; | |
35 | |
36 import de.mpiwg.itgroup.annotationManager.Constants.NS; | |
37 import de.mpiwg.itgroup.annotationManager.RDFHandling.Annotation; | |
38 | |
39 /** | |
40 * Base class for Annotator resource classes. | |
41 * | |
42 * @author dwinter, casties | |
43 * | |
44 */ | |
45 public abstract class AnnotatorResourceImpl extends ServerResource { | |
46 | |
47 protected Logger logger = Logger.getRootLogger(); | |
48 | |
49 protected String getAllowedMethodsForHeader() { | |
50 return "OPTIONS,GET,POST"; | |
51 } | |
52 | |
53 public String encodeJsonId(String id) { | |
54 try { | |
55 return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8")); | |
56 } catch (UnsupportedEncodingException e) { | |
57 return null; | |
58 } | |
59 } | |
60 | |
61 public String decodeJsonId(String id) { | |
62 try { | |
63 return new String(Base64.decodeBase64(id), "UTF-8"); | |
64 } catch (UnsupportedEncodingException e) { | |
65 return null; | |
66 } | |
67 } | |
68 | |
69 /** | |
70 * Handle options request to allow CORS for AJAX. | |
71 * | |
72 * @param entity | |
73 */ | |
74 @Options | |
75 public void doOptions(Representation entity) { | |
76 logger.debug("AnnotatorResourceImpl doOptions!"); | |
77 setCorsHeaders(); | |
78 } | |
79 | |
80 /** | |
81 * set headers to allow CORS for AJAX. | |
82 */ | |
83 protected void setCorsHeaders() { | |
84 Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers"); | |
85 if (responseHeaders == null) { | |
86 responseHeaders = new Form(); | |
87 getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders); | |
88 } | |
89 responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader()); | |
90 // echo back Origin and Request-Headers | |
91 Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); | |
92 String origin = requestHeaders.getFirstValue("Origin", true); | |
93 if (origin == null) { | |
94 responseHeaders.add("Access-Control-Allow-Origin", "*"); | |
95 } else { | |
96 responseHeaders.add("Access-Control-Allow-Origin", origin); | |
97 } | |
98 String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true); | |
99 if (allowHeaders != null) { | |
100 responseHeaders.add("Access-Control-Allow-Headers", allowHeaders); | |
101 } | |
102 responseHeaders.add("Access-Control-Allow-Credentials", "true"); | |
103 responseHeaders.add("Access-Control-Max-Age", "60"); | |
104 } | |
105 | |
106 /** | |
107 * returns if authentication information from headers is valid. | |
108 * | |
109 * @param entity | |
110 * @return | |
111 */ | |
112 public boolean isAuthenticated(Representation entity) { | |
113 return (checkAuthToken(entity) != null); | |
114 } | |
115 | |
116 /** | |
117 * checks Annotator Auth plugin authentication information from headers. returns userId if successful. | |
118 * | |
119 * @param entity | |
120 * @return | |
121 */ | |
122 public String checkAuthToken(Representation entity) { | |
123 Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); | |
124 String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true); | |
125 // decode token first to get consumer key | |
126 JsonToken token = new JsonTokenParser(null, null).deserialize(authToken); | |
127 String userId = token.getParamAsPrimitive("userId").getAsString(); | |
128 String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString(); | |
129 // get stored consumer secret for key | |
130 RestServer restServer = (RestServer) getApplication(); | |
131 String consumerSecret = restServer.getConsumerSecret(consumerKey); | |
132 logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret); | |
133 if (consumerSecret == null) { | |
134 return null; | |
135 } | |
136 // logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm())); | |
137 try { | |
138 List<Verifier> verifiers = new ArrayList<Verifier>(); | |
139 // we only do HS256 yet | |
140 verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8"))); | |
141 // verify token signature(should really be static...) | |
142 new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers); | |
143 } catch (SignatureException e) { | |
144 // TODO Auto-generated catch block | |
145 e.printStackTrace(); | |
146 } catch (InvalidKeyException e) { | |
147 // TODO Auto-generated catch block | |
148 e.printStackTrace(); | |
149 } catch (UnsupportedEncodingException e) { | |
150 // TODO Auto-generated catch block | |
151 e.printStackTrace(); | |
152 } | |
153 // must be ok then | |
154 logger.debug("auth OK! user=" + userId); | |
155 return userId; | |
156 } | |
157 | |
158 /** | |
159 * creates Annotator-JSON from an Annotation object. | |
160 * | |
161 * @param annot | |
162 * @return | |
163 */ | |
164 public JSONObject createAnnotatorJson(Annotation annot) { | |
165 boolean makeUserObject = true; | |
166 JSONObject jo = new JSONObject(); | |
167 try { | |
168 jo.put("text", annot.text); | |
169 jo.put("uri", annot.url); | |
170 | |
171 if (makeUserObject) { | |
172 // create user object | |
173 JSONObject userObject = new JSONObject(); | |
174 // save creator as uri | |
175 userObject.put("uri", annot.creator); | |
176 // make short user id | |
177 String userID = annot.creator; | |
178 if (userID.startsWith(NS.MPIWG_PERSONS_URL)) { | |
179 userID = userID.replace(NS.MPIWG_PERSONS_URL, ""); // entferne | |
180 // NAMESPACE | |
181 } | |
182 // save as id | |
183 userObject.put("id", userID); | |
184 // get full name | |
185 RestServer restServer = (RestServer) getApplication(); | |
186 String userName = restServer.getUserNameFromLdap(userID); | |
187 userObject.put("name", userName); | |
188 // save user object | |
189 jo.put("user", userObject); | |
190 } else { | |
191 // save user as string | |
192 jo.put("user", annot.creator); | |
193 } | |
194 | |
195 List<String> xpointers = new ArrayList<String>(); | |
196 if (annot.xpointers == null || annot.xpointers.size() == 0) | |
197 xpointers.add(annot.xpointer); | |
198 else { | |
199 for (String xpointerString : annot.xpointers) { | |
200 xpointers.add(xpointerString); | |
201 } | |
202 } | |
203 if (!xpointers.isEmpty()) { | |
204 // we only look at the first xpointer | |
205 String xt = getXpointerType(xpointers.get(0)); | |
206 if (xt == "range") { | |
207 jo.put("ranges", transformToRanges(xpointers)); | |
208 } else if (xt == "area") { | |
209 jo.put("areas", transformToAreas(xpointers)); | |
210 } | |
211 } | |
212 // encode Annotation URL (=id) in base64 | |
213 String annotUrl = annot.getAnnotationUri(); | |
214 String annotId = encodeJsonId(annotUrl); | |
215 jo.put("id", annotId); | |
216 return jo; | |
217 } catch (JSONException e) { | |
218 // TODO Auto-generated catch block | |
219 e.printStackTrace(); | |
220 } | |
221 return null; | |
222 } | |
223 | |
224 private String getXpointerType(String xpointer) { | |
225 if (xpointer.contains("#xpointer")) { | |
226 return "range"; | |
227 } else if (xpointer.contains("#xywh")) { | |
228 return "area"; | |
229 } | |
230 return null; | |
231 } | |
232 | |
233 private JSONArray transformToRanges(List<String> xpointers) { | |
234 | |
235 JSONArray ja = new JSONArray(); | |
236 | |
237 Pattern rg = Pattern | |
238 .compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)"); | |
239 Pattern rg1 = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)"); | |
240 | |
241 try { | |
242 for (String xpointer : xpointers) { | |
243 String decoded = URLDecoder.decode(xpointer, "utf-8"); | |
244 Matcher m = rg.matcher(decoded); | |
245 | |
246 if (m.find()) { | |
247 { | |
248 JSONObject jo = new JSONObject(); | |
249 jo.put("start", m.group(1)); | |
250 jo.put("startOffset", m.group(2)); | |
251 jo.put("end", m.group(3)); | |
252 jo.put("endOffset", m.group(4)); | |
253 ja.put(jo); | |
254 } | |
255 } | |
256 m = rg1.matcher(xpointer); | |
257 if (m.find()) { | |
258 JSONObject jo = new JSONObject(); | |
259 jo.put("start", m.group(1)); | |
260 jo.put("startOffset", m.group(2)); | |
261 | |
262 ja.put(jo); | |
263 } | |
264 } | |
265 } catch (JSONException e) { | |
266 // TODO Auto-generated catch block | |
267 e.printStackTrace(); | |
268 } catch (UnsupportedEncodingException e) { | |
269 // TODO Auto-generated catch block | |
270 e.printStackTrace(); | |
271 } | |
272 | |
273 return ja; | |
274 } | |
275 | |
276 private JSONArray transformToAreas(List<String> xpointers) { | |
277 | |
278 JSONArray ja = new JSONArray(); | |
279 | |
280 Pattern rg = Pattern.compile("#xywh=(\\w*:)([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)"); | |
281 | |
282 try { | |
283 for (String xpointer : xpointers) { | |
284 String decoded = URLDecoder.decode(xpointer, "utf-8"); | |
285 Matcher m = rg.matcher(decoded); | |
286 | |
287 if (m.find()) { | |
288 { | |
289 JSONObject jo = new JSONObject(); | |
290 String unit = m.group(1); | |
291 jo.put("x", m.group(2)); | |
292 jo.put("y", m.group(3)); | |
293 jo.put("width", m.group(4)); | |
294 jo.put("height", m.group(5)); | |
295 ja.put(jo); | |
296 } | |
297 } | |
298 } | |
299 } catch (JSONException e) { | |
300 // TODO Auto-generated catch block | |
301 e.printStackTrace(); | |
302 } catch (UnsupportedEncodingException e) { | |
303 // TODO Auto-generated catch block | |
304 e.printStackTrace(); | |
305 } | |
306 | |
307 return ja; | |
308 } | |
309 | |
310 /** | |
311 * creates an Annotation object with data from JSON. | |
312 * | |
313 * uses the specification from the annotator project: {@link https ://github.com/okfn/annotator/wiki/Annotation-format} | |
314 * | |
315 * 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 | |
316 * de.mpiwg.itgroup.annotationManager.Constants.NS | |
317 * | |
318 * @param jo | |
319 * @return | |
320 * @throws JSONException | |
321 */ | |
322 public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException { | |
323 return updateAnnotation(new Annotation(), jo, entity); | |
324 } | |
325 | |
326 public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException { | |
327 // annotated uri | |
328 String url = annot.url; | |
329 if (jo.has("uri")) { | |
330 url = jo.getString("uri"); | |
331 } | |
332 // annotation text | |
333 String text = annot.text; | |
334 if (jo.has("text")) { | |
335 text = jo.getString("text"); | |
336 } | |
337 // check authentication | |
338 String authUser = checkAuthToken(entity); | |
339 if (authUser == null) { | |
340 // try http auth | |
341 User httpUser = getHttpAuthUser(entity); | |
342 if (httpUser == null) { | |
343 setStatus(Status.CLIENT_ERROR_FORBIDDEN); | |
344 return null; | |
345 } | |
346 authUser = httpUser.getIdentifier(); | |
347 } | |
348 // username not required, if no username given authuser will be used | |
349 String username = null; | |
350 String userUri = annot.creator; | |
351 if (jo.has("user")) { | |
352 if (jo.get("user") instanceof String) { | |
353 // user is just a String | |
354 username = jo.getString("user"); | |
355 // TODO: what if username and authUser are different? | |
356 } else { | |
357 // user is an object | |
358 JSONObject user = jo.getJSONObject("user"); | |
359 if (user.has("id")) { | |
360 username = user.getString("id"); | |
361 } | |
362 if (user.has("uri")) { | |
363 userUri = user.getString("uri"); | |
364 } | |
365 } | |
366 } | |
367 if (username == null) { | |
368 username = authUser; | |
369 } | |
370 // username should be a URI, if not it will set to the MPIWG namespace | |
371 // defined in | |
372 // de.mpiwg.itgroup.annotationManager.Constants.NS | |
373 if (userUri == null) { | |
374 if (username.startsWith("http")) { | |
375 userUri = username; | |
376 } else { | |
377 userUri = NS.MPIWG_PERSONS_URL + username; | |
378 } | |
379 } | |
380 // TODO: should we overwrite the creator? | |
381 | |
382 // create xpointer from the first range/area | |
383 String xpointer = annot.xpointer; | |
384 if (jo.has("ranges")) { | |
385 JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0); | |
386 String start = ranges.getString("start"); | |
387 String end = ranges.getString("end"); | |
388 String startOffset = ranges.getString("startOffset"); | |
389 String endOffset = ranges.getString("endOffset"); | |
390 | |
391 try { | |
392 xpointer = url | |
393 + "#" | |
394 + URLEncoder.encode(String.format( | |
395 "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", | |
396 start, startOffset, end, endOffset), "utf-8"); | |
397 } catch (UnsupportedEncodingException e) { | |
398 e.printStackTrace(); | |
399 setStatus(Status.SERVER_ERROR_INTERNAL); | |
400 return null; | |
401 } | |
402 } | |
403 if (jo.has("areas")) { | |
404 JSONObject area = jo.getJSONArray("areas").getJSONObject(0); | |
405 String x = area.getString("x"); | |
406 String y = area.getString("y"); | |
407 String width = "0"; | |
408 String height = "0"; | |
409 if (area.has("width")) { | |
410 width = area.getString("width"); | |
411 height = area.getString("height"); | |
412 } | |
413 try { | |
414 xpointer = url + "#" + URLEncoder.encode(String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height), "utf-8"); | |
415 } catch (UnsupportedEncodingException e) { | |
416 e.printStackTrace(); | |
417 setStatus(Status.SERVER_ERROR_INTERNAL); | |
418 return null; | |
419 } | |
420 } | |
421 return new Annotation(xpointer, userUri, annot.time, text, annot.type); | |
422 } | |
423 | |
424 /** | |
425 * returns the logged in User. | |
426 * | |
427 * @param entity | |
428 * @return | |
429 */ | |
430 protected User getHttpAuthUser(Representation entity) { | |
431 RestServer restServer = (RestServer) getApplication(); | |
432 if (!restServer.authenticate(getRequest(), getResponse())) { | |
433 // Not authenticated | |
434 return null; | |
435 } | |
436 | |
437 ClientInfo ci = getRequest().getClientInfo(); | |
438 logger.debug(ci); | |
439 return getRequest().getClientInfo().getUser(); | |
440 | |
441 } | |
442 | |
443 } |