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 }