Mercurial > hg > AnnotationManagerN4J
annotate src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 72:4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
author | casties |
---|---|
date | Tue, 11 Mar 2014 17:43:00 +0100 |
parents | 2b1e6df5e21a |
children | 4b8c909cabf3 |
rev | line source |
---|---|
3 | 1 /** |
2 * Base class for Annotator resource classes. | |
3 */ | |
4 package de.mpiwg.itgroup.annotations.restlet; | |
5 | |
70 | 6 /* |
7 * #%L | |
8 * AnnotationManager | |
9 * %% | |
10 * Copyright (C) 2012 - 2014 MPIWG Berlin | |
11 * %% | |
12 * This program is free software: you can redistribute it and/or modify | |
13 * it under the terms of the GNU Lesser General Public License as | |
14 * published by the Free Software Foundation, either version 3 of the | |
15 * License, or (at your option) any later version. | |
16 * | |
17 * This program is distributed in the hope that it will be useful, | |
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 * GNU General Lesser Public License for more details. | |
21 * | |
22 * You should have received a copy of the GNU General Lesser Public | |
23 * License along with this program. If not, see | |
24 * <http://www.gnu.org/licenses/lgpl-3.0.html>. | |
25 * #L% | |
26 */ | |
27 | |
3 | 28 import java.io.UnsupportedEncodingException; |
29 import java.security.InvalidKeyException; | |
30 import java.security.SignatureException; | |
5 | 31 import java.text.SimpleDateFormat; |
3 | 32 import java.util.ArrayList; |
5 | 33 import java.util.Calendar; |
16 | 34 import java.util.HashSet; |
3 | 35 import java.util.List; |
16 | 36 import java.util.Set; |
3 | 37 import java.util.regex.Matcher; |
38 import java.util.regex.Pattern; | |
39 | |
40 import net.oauth.jsontoken.Checker; | |
41 import net.oauth.jsontoken.JsonToken; | |
42 import net.oauth.jsontoken.JsonTokenParser; | |
43 import net.oauth.jsontoken.SystemClock; | |
44 import net.oauth.jsontoken.crypto.HmacSHA256Verifier; | |
45 import net.oauth.jsontoken.crypto.Verifier; | |
46 | |
47 import org.apache.commons.codec.binary.Base64; | |
48 import org.apache.log4j.Logger; | |
49 import org.json.JSONArray; | |
50 import org.json.JSONException; | |
51 import org.json.JSONObject; | |
52 import org.restlet.data.Form; | |
53 import org.restlet.data.Status; | |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
54 import org.restlet.engine.header.Header; |
3 | 55 import org.restlet.representation.Representation; |
56 import org.restlet.resource.Options; | |
57 import org.restlet.resource.ServerResource; | |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
58 import org.restlet.util.Series; |
3 | 59 |
9 | 60 import de.mpiwg.itgroup.annotations.Actor; |
4 | 61 import de.mpiwg.itgroup.annotations.Annotation; |
62 import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes; | |
10 | 63 import de.mpiwg.itgroup.annotations.Group; |
64 import de.mpiwg.itgroup.annotations.Person; | |
48
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
65 import de.mpiwg.itgroup.annotations.Resource; |
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
66 import de.mpiwg.itgroup.annotations.Target; |
4 | 67 import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore; |
3 | 68 |
69 /** | |
70 * Base class for Annotator resource classes. | |
71 * | |
72 * @author dwinter, casties | |
73 * | |
74 */ | |
75 public abstract class AnnotatorResourceImpl extends ServerResource { | |
76 | |
4 | 77 protected static Logger logger = Logger.getLogger(AnnotatorResourceImpl.class); |
78 | |
79 private AnnotationStore store; | |
3 | 80 |
81 protected String getAllowedMethodsForHeader() { | |
82 return "OPTIONS,GET,POST"; | |
83 } | |
84 | |
4 | 85 protected AnnotationStore getAnnotationStore() { |
86 if (store == null) { | |
19 | 87 store = ((BaseRestlet) getApplication()).getAnnotationStore(); |
4 | 88 } |
89 return store; | |
90 } | |
91 | |
3 | 92 public String encodeJsonId(String id) { |
64 | 93 if (id == null) |
94 return null; | |
3 | 95 try { |
96 return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8")); | |
97 } catch (UnsupportedEncodingException e) { | |
98 return null; | |
99 } | |
100 } | |
101 | |
102 public String decodeJsonId(String id) { | |
64 | 103 if (id == null) |
104 return null; | |
3 | 105 try { |
106 return new String(Base64.decodeBase64(id), "UTF-8"); | |
107 } catch (UnsupportedEncodingException e) { | |
108 return null; | |
109 } | |
110 } | |
111 | |
112 /** | |
113 * Handle options request to allow CORS for AJAX. | |
114 * | |
115 * @param entity | |
116 */ | |
117 @Options | |
118 public void doOptions(Representation entity) { | |
119 logger.debug("AnnotatorResourceImpl doOptions!"); | |
120 setCorsHeaders(); | |
121 } | |
122 | |
123 /** | |
124 * set headers to allow CORS for AJAX. | |
125 */ | |
126 protected void setCorsHeaders() { | |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
127 @SuppressWarnings("unchecked") |
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
128 Series<Header> responseHeaders = (Series<Header>) getResponse().getAttributes().get("org.restlet.http.headers"); |
3 | 129 if (responseHeaders == null) { |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
130 responseHeaders = new Series<Header>(Header.class); |
3 | 131 getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders); |
132 } | |
133 responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader()); | |
134 // echo back Origin and Request-Headers | |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
135 @SuppressWarnings("unchecked") |
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
136 Series<Header> requestHeaders = (Series<Header>) getRequest().getAttributes().get("org.restlet.http.headers"); |
3 | 137 String origin = requestHeaders.getFirstValue("Origin", true); |
138 if (origin == null) { | |
139 responseHeaders.add("Access-Control-Allow-Origin", "*"); | |
140 } else { | |
141 responseHeaders.add("Access-Control-Allow-Origin", origin); | |
142 } | |
143 String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true); | |
144 if (allowHeaders != null) { | |
145 responseHeaders.add("Access-Control-Allow-Headers", allowHeaders); | |
146 } | |
147 responseHeaders.add("Access-Control-Allow-Credentials", "true"); | |
148 responseHeaders.add("Access-Control-Max-Age", "60"); | |
149 } | |
150 | |
151 /** | |
152 * returns if authentication information from headers is valid. | |
153 * | |
154 * @param entity | |
155 * @return | |
156 */ | |
157 public boolean isAuthenticated(Representation entity) { | |
158 return (checkAuthToken(entity) != null); | |
159 } | |
160 | |
161 /** | |
57
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
162 * Checks Annotator Auth plugin authentication information from headers. |
61 | 163 * Returns userId if successful. Returns "anonymous" in non-authorization |
164 * mode. | |
3 | 165 * |
166 * @param entity | |
167 * @return | |
168 */ | |
169 public String checkAuthToken(Representation entity) { | |
72
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
170 @SuppressWarnings("unchecked") |
4c2cea836bc0
restlet 2.1 works now. (it's the start() method, stupid!)
casties
parents:
70
diff
changeset
|
171 Series<Header> requestHeaders = (Series<Header>) getRequest().getAttributes().get("org.restlet.http.headers"); |
3 | 172 String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true); |
57
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
173 if (authToken == null) { |
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
174 if (!((BaseRestlet) getApplication()).isAuthorizationMode()) { |
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
175 return "anonymous"; |
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
176 } |
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
177 return null; |
4efb21cf0ce0
new non-authorized mode without tokens. enabled by default. configured with annotationmanager.authorization=false property.
casties
parents:
52
diff
changeset
|
178 } |
3 | 179 // decode token first to get consumer key |
180 JsonToken token = new JsonTokenParser(null, null).deserialize(authToken); | |
181 String userId = token.getParamAsPrimitive("userId").getAsString(); | |
182 String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString(); | |
183 // get stored consumer secret for key | |
18 | 184 BaseRestlet restServer = (BaseRestlet) getApplication(); |
3 | 185 String consumerSecret = restServer.getConsumerSecret(consumerKey); |
186 logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret); | |
187 if (consumerSecret == null) { | |
188 return null; | |
189 } | |
190 // logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm())); | |
191 try { | |
192 List<Verifier> verifiers = new ArrayList<Verifier>(); | |
193 // we only do HS256 yet | |
194 verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8"))); | |
195 // verify token signature(should really be static...) | |
196 new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers); | |
197 } catch (SignatureException e) { | |
198 // TODO Auto-generated catch block | |
199 e.printStackTrace(); | |
200 } catch (InvalidKeyException e) { | |
201 // TODO Auto-generated catch block | |
202 e.printStackTrace(); | |
203 } catch (UnsupportedEncodingException e) { | |
204 // TODO Auto-generated catch block | |
205 e.printStackTrace(); | |
206 } | |
207 // must be ok then | |
208 logger.debug("auth OK! user=" + userId); | |
209 return userId; | |
210 } | |
211 | |
212 /** | |
213 * creates Annotator-JSON from an Annotation object. | |
214 * | |
215 * @param annot | |
61 | 216 * @param forAnonymous |
217 * TODO | |
3 | 218 * @return |
219 */ | |
14
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
220 public JSONObject createAnnotatorJson(Annotation annot, boolean forAnonymous) { |
5 | 221 // return user as a JSON object (otherwise just as string) |
3 | 222 boolean makeUserObject = true; |
223 JSONObject jo = new JSONObject(); | |
224 try { | |
4 | 225 jo.put("text", annot.getBodyText()); |
226 jo.put("uri", annot.getTargetBaseUri()); | |
40
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
227 if (annot.getResourceUri() != null) { |
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
228 jo.put("resource", annot.getResourceUri()); |
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
229 } |
3 | 230 |
16 | 231 /* |
232 * user | |
233 */ | |
64 | 234 Actor creator = annot.getCreator(); |
235 if (creator != null) { | |
236 if (makeUserObject) { | |
237 // create user object | |
238 JSONObject userObject = new JSONObject(); | |
239 // save creator as uri | |
240 userObject.put("uri", creator.getUri()); | |
241 // make short user id | |
242 String userId = creator.getIdString(); | |
243 // set as id | |
244 userObject.put("id", userId); | |
245 // get full name | |
246 String userName = creator.getName(); | |
247 if (userName == null) { | |
248 BaseRestlet restServer = (BaseRestlet) getApplication(); | |
249 userName = restServer.getFullNameFromLdap(userId); | |
250 } | |
251 userObject.put("name", userName); | |
252 // save user object | |
253 jo.put("user", userObject); | |
254 } else { | |
255 // save user as string | |
256 jo.put("user", annot.getCreatorUri()); | |
5 | 257 } |
3 | 258 } |
259 | |
16 | 260 /* |
261 * ranges | |
262 */ | |
4 | 263 if (annot.getTargetFragment() != null) { |
3 | 264 // we only look at the first xpointer |
4 | 265 List<String> fragments = new ArrayList<String>(); |
266 fragments.add(annot.getTargetFragment()); | |
267 FragmentTypes xt = annot.getFragmentType(); | |
268 if (xt == FragmentTypes.XPOINTER) { | |
269 jo.put("ranges", transformToRanges(fragments)); | |
270 } else if (xt == FragmentTypes.AREA) { | |
61 | 271 jo.put("shapes", transformToShapes(fragments)); |
3 | 272 } |
273 } | |
61 | 274 |
16 | 275 /* |
276 * permissions | |
277 */ | |
10 | 278 JSONObject perms = new JSONObject(); |
279 jo.put("permissions", perms); | |
280 // admin | |
281 JSONArray adminPerms = new JSONArray(); | |
282 perms.put("admin", adminPerms); | |
283 Actor adminPerm = annot.getAdminPermission(); | |
284 if (adminPerm != null) { | |
285 adminPerms.put(adminPerm.getIdString()); | |
14
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
286 } else if (forAnonymous) { |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
287 // set something because its not allowed for anonymous |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
288 adminPerms.put("not-you"); |
10 | 289 } |
290 // delete | |
291 JSONArray deletePerms = new JSONArray(); | |
292 perms.put("delete", deletePerms); | |
293 Actor deletePerm = annot.getDeletePermission(); | |
294 if (deletePerm != null) { | |
295 deletePerms.put(deletePerm.getIdString()); | |
14
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
296 } else if (forAnonymous) { |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
297 // set something because its not allowed for anonymous |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
298 deletePerms.put("not-you"); |
10 | 299 } |
300 // update | |
301 JSONArray updatePerms = new JSONArray(); | |
302 perms.put("update", updatePerms); | |
303 Actor updatePerm = annot.getUpdatePermission(); | |
304 if (updatePerm != null) { | |
305 updatePerms.put(updatePerm.getIdString()); | |
14
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
306 } else if (forAnonymous) { |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
307 // set something because its not allowed for anonymous |
629e15b345aa
permissions mostly work. need more server-side checking.
casties
parents:
10
diff
changeset
|
308 updatePerms.put("not-you"); |
10 | 309 } |
310 // read | |
311 JSONArray readPerms = new JSONArray(); | |
312 perms.put("read", readPerms); | |
313 Actor readPerm = annot.getReadPermission(); | |
314 if (readPerm != null) { | |
315 readPerms.put(readPerm.getIdString()); | |
316 } | |
61 | 317 |
16 | 318 /* |
319 * tags | |
320 */ | |
61 | 321 Set<String> tagset = annot.getTags(); |
16 | 322 if (tagset != null) { |
323 JSONArray tags = new JSONArray(); | |
324 jo.put("tags", tags); | |
325 for (String tag : tagset) { | |
326 tags.put(tag); | |
327 } | |
328 } | |
61 | 329 |
16 | 330 /* |
331 * id | |
332 */ | |
3 | 333 // encode Annotation URL (=id) in base64 |
4 | 334 String annotUrl = annot.getUri(); |
3 | 335 String annotId = encodeJsonId(annotUrl); |
336 jo.put("id", annotId); | |
337 return jo; | |
338 } catch (JSONException e) { | |
61 | 339 logger.error("Unable to create AnnotatorJSON!", e); |
3 | 340 } |
341 return null; | |
342 } | |
343 | |
344 private JSONArray transformToRanges(List<String> xpointers) { | |
345 JSONArray ja = new JSONArray(); | |
346 Pattern rg = Pattern | |
4 | 347 .compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)"); |
348 Pattern rg1 = Pattern.compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)"); | |
3 | 349 try { |
350 for (String xpointer : xpointers) { | |
10 | 351 // String decoded = URLDecoder.decode(xpointer, "utf-8"); |
5 | 352 String decoded = xpointer; |
3 | 353 Matcher m = rg.matcher(decoded); |
354 if (m.find()) { | |
61 | 355 JSONObject jo = new JSONObject(); |
356 jo.put("start", m.group(1)); | |
357 jo.put("startOffset", m.group(2)); | |
358 jo.put("end", m.group(3)); | |
359 jo.put("endOffset", m.group(4)); | |
360 ja.put(jo); | |
3 | 361 } |
362 m = rg1.matcher(xpointer); | |
363 if (m.find()) { | |
364 JSONObject jo = new JSONObject(); | |
365 jo.put("start", m.group(1)); | |
366 jo.put("startOffset", m.group(2)); | |
367 ja.put(jo); | |
368 } | |
369 } | |
370 } catch (JSONException e) { | |
61 | 371 logger.error("Unable to transform to ranges!", e); |
3 | 372 } |
373 return ja; | |
374 } | |
375 | |
61 | 376 private JSONArray transformToShapes(List<String> xpointers) { |
3 | 377 JSONArray ja = new JSONArray(); |
61 | 378 Pattern rg = Pattern.compile("xywh=(\\w*):([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)"); |
3 | 379 try { |
380 for (String xpointer : xpointers) { | |
5 | 381 String decoded = xpointer; |
3 | 382 Matcher m = rg.matcher(decoded); |
383 if (m.find()) { | |
61 | 384 String units = m.group(1); |
385 float x = getFloat(m.group(2)); | |
386 float y = getFloat(m.group(3)); | |
387 float width = getFloat(m.group(4)); | |
388 float height = getFloat(m.group(5)); | |
389 JSONObject shape = new JSONObject(); | |
390 JSONObject geom = new JSONObject(); | |
391 geom.put("units", units); | |
392 geom.put("x", x); | |
393 geom.put("y", y); | |
394 if (width == 0 || height == 0) { | |
395 shape.put("type", "point"); | |
396 shape.put("geometry", geom); | |
397 } else { | |
398 shape.put("type", "rectangle"); | |
399 geom.put("width", width); | |
400 geom.put("height", height); | |
401 shape.put("geometry", geom); | |
3 | 402 } |
61 | 403 ja.put(shape); |
3 | 404 } |
405 } | |
406 } catch (JSONException e) { | |
61 | 407 logger.error("Unable to transform to shapes!", e); |
3 | 408 } |
409 return ja; | |
410 } | |
411 | |
61 | 412 protected String parseShape(JSONObject shape) throws JSONException { |
413 String fragment = null; | |
414 String type = shape.getString("type"); | |
415 JSONObject geom = shape.getJSONObject("geometry"); | |
416 if (type.equalsIgnoreCase("point")) { | |
417 String x = geom.getString("x"); | |
418 String y = geom.getString("y"); | |
419 fragment = String.format("xywh=fraction:%s,%s,0,0", x, y); | |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
420 } else if (type.equalsIgnoreCase("rectangle")) { |
61 | 421 String x = geom.getString("x"); |
422 String y = geom.getString("y"); | |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
423 String width = geom.getString("width"); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
424 String height = geom.getString("height"); |
61 | 425 fragment = String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height); |
426 } else { | |
427 logger.error("Unable to parse this shape: " + shape); | |
428 } | |
429 return fragment; | |
430 } | |
431 | |
5 | 432 protected String parseArea(JSONObject area) throws JSONException { |
4 | 433 String x = area.getString("x"); |
434 String y = area.getString("y"); | |
435 String width = "0"; | |
436 String height = "0"; | |
437 if (area.has("width")) { | |
438 width = area.getString("width"); | |
439 height = area.getString("height"); | |
440 } | |
5 | 441 String fragment = String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height); |
4 | 442 return fragment; |
443 } | |
444 | |
5 | 445 protected String parseRange(JSONObject range) throws JSONException { |
4 | 446 String start = range.getString("start"); |
447 String end = range.getString("end"); | |
448 String startOffset = range.getString("startOffset"); | |
449 String endOffset = range.getString("endOffset"); | |
5 | 450 String fragment = String.format( |
4 | 451 "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start, |
5 | 452 startOffset, end, endOffset); |
4 | 453 return fragment; |
454 } | |
455 | |
3 | 456 /** |
5 | 457 * Creates an Annotation object with data from JSON. |
3 | 458 * |
4 | 459 * uses the specification from the annotator project: {@link https |
460 * ://github.com/okfn/annotator/wiki/Annotation-format} | |
3 | 461 * |
4 | 462 * The username will be transformed to an URI if not given already as URI, |
463 * if not it will set to the MPIWG namespace defined in | |
3 | 464 * de.mpiwg.itgroup.annotationManager.Constants.NS |
465 * | |
466 * @param jo | |
467 * @return | |
468 * @throws JSONException | |
4 | 469 * @throws UnsupportedEncodingException |
3 | 470 */ |
4 | 471 public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException { |
3 | 472 return updateAnnotation(new Annotation(), jo, entity); |
473 } | |
474 | |
5 | 475 /** |
476 * Updates an Annotation object with data from JSON. | |
477 * | |
478 * uses the specification from the annotator project: {@link https | |
479 * ://github.com/okfn/annotator/wiki/Annotation-format} | |
480 * | |
481 * The username will be transformed to an URI if not given already as URI, | |
482 * if not it will set to the MPIWG namespace defined in | |
483 * de.mpiwg.itgroup.annotationManager.Constants.NS | |
484 * | |
485 * @param annot | |
486 * @param jo | |
487 * @return | |
488 * @throws JSONException | |
489 * @throws UnsupportedEncodingException | |
490 */ | |
4 | 491 public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException, |
492 UnsupportedEncodingException { | |
16 | 493 /* |
494 * target uri | |
495 */ | |
3 | 496 if (jo.has("uri")) { |
48
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
497 annot.setTarget(new Target(jo.getString("uri"))); |
3 | 498 } |
16 | 499 /* |
40
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
500 * resource uri |
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
501 */ |
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
502 if (jo.has("resource")) { |
48
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
503 annot.setResource(new Resource(jo.getString("resource"))); |
40
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
504 } |
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
505 /* |
16 | 506 * annotation text |
507 */ | |
3 | 508 if (jo.has("text")) { |
4 | 509 annot.setBodyText(jo.getString("text")); |
3 | 510 } |
16 | 511 /* |
512 * check authentication | |
513 */ | |
3 | 514 String authUser = checkAuthToken(entity); |
515 if (authUser == null) { | |
4 | 516 /* |
517 * // try http auth User httpUser = getHttpAuthUser(entity); if | |
518 * (httpUser == null) { | |
519 */ | |
520 setStatus(Status.CLIENT_ERROR_FORBIDDEN); | |
521 return null; | |
522 /* | |
523 * } authUser = httpUser.getIdentifier(); | |
524 */ | |
3 | 525 } |
16 | 526 /* |
527 * get or create creator object | |
528 */ | |
9 | 529 Actor creator = annot.getCreator(); |
530 if (creator == null) { | |
10 | 531 creator = new Person(); |
9 | 532 annot.setCreator(creator); |
533 } | |
3 | 534 // username not required, if no username given authuser will be used |
535 String username = null; | |
10 | 536 String userUri = creator.getUri(); |
3 | 537 if (jo.has("user")) { |
538 if (jo.get("user") instanceof String) { | |
539 // user is just a String | |
540 username = jo.getString("user"); | |
10 | 541 creator.setId(username); |
3 | 542 // TODO: what if username and authUser are different? |
543 } else { | |
544 // user is an object | |
545 JSONObject user = jo.getJSONObject("user"); | |
546 if (user.has("id")) { | |
10 | 547 String id = user.getString("id"); |
548 creator.setId(id); | |
549 username = id; | |
3 | 550 } |
551 if (user.has("uri")) { | |
552 userUri = user.getString("uri"); | |
553 } | |
554 } | |
555 } | |
556 if (username == null) { | |
557 username = authUser; | |
558 } | |
5 | 559 // try to get full name |
9 | 560 if (creator.getName() == null && username != null) { |
18 | 561 BaseRestlet restServer = (BaseRestlet) getApplication(); |
5 | 562 String fullName = restServer.getFullNameFromLdap(username); |
9 | 563 creator.setName(fullName); |
5 | 564 } |
565 // userUri should be a URI, if not it will set to the MPIWG namespace | |
3 | 566 if (userUri == null) { |
567 if (username.startsWith("http")) { | |
568 userUri = username; | |
569 } else { | |
58 | 570 userUri = BaseRestlet.PERSONS_URI_PREFIX + username; |
3 | 571 } |
572 } | |
573 // TODO: should we overwrite the creator? | |
9 | 574 if (creator.getUri() == null) { |
575 creator.setUri(userUri); | |
5 | 576 } |
16 | 577 /* |
578 * creation date | |
579 */ | |
5 | 580 if (annot.getCreated() == null) { |
581 // set creation date | |
582 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); | |
583 String ct = format.format(Calendar.getInstance().getTime()); | |
584 annot.setCreated(ct); | |
585 } | |
3 | 586 |
16 | 587 /* |
61 | 588 * create fragment from the first range/area |
16 | 589 */ |
52 | 590 try { |
591 if (jo.has("ranges")) { | |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
592 JSONArray ranges = jo.getJSONArray("ranges"); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
593 if (ranges.length() > 0) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
594 JSONObject range = ranges.getJSONObject(0); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
595 annot.setFragmentType(FragmentTypes.XPOINTER); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
596 String fragment = parseRange(range); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
597 annot.setTargetFragment(fragment); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
598 } |
52 | 599 } |
600 } catch (JSONException e) { | |
601 // nothing to do | |
3 | 602 } |
52 | 603 try { |
61 | 604 if (jo.has("shapes")) { |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
605 JSONArray shapes = jo.getJSONArray("shapes"); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
606 if (shapes.length() > 0) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
607 JSONObject shape = shapes.getJSONObject(0); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
608 annot.setFragmentType(FragmentTypes.AREA); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
609 String fragment = parseShape(shape); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
610 annot.setTargetFragment(fragment); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
611 } |
61 | 612 } |
613 } catch (JSONException e) { | |
614 // nothing to do | |
615 } | |
616 // deprecated areas type | |
617 try { | |
52 | 618 if (jo.has("areas")) { |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
619 JSONArray areas = jo.getJSONArray("areas"); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
620 if (areas.length() > 0) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
621 JSONObject area = areas.getJSONObject(0); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
622 annot.setFragmentType(FragmentTypes.AREA); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
623 String fragment = parseArea(area); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
624 annot.setTargetFragment(fragment); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
625 } |
52 | 626 } |
627 } catch (JSONException e) { | |
628 // nothing to do | |
3 | 629 } |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
630 // no fragment is an error |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
631 if (annot.getFragmentType() == null || annot.getTargetFragment() == null) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
632 throw new JSONException("Annotation has no valid target fragment!"); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
633 } |
64 | 634 |
16 | 635 /* |
636 * permissions | |
637 */ | |
10 | 638 if (jo.has("permissions")) { |
639 JSONObject permissions = jo.getJSONObject("permissions"); | |
640 if (permissions.has("admin")) { | |
641 JSONArray perms = permissions.getJSONArray("admin"); | |
642 Actor actor = getActorFromPermissions(perms); | |
643 annot.setAdminPermission(actor); | |
644 } | |
645 if (permissions.has("delete")) { | |
646 JSONArray perms = permissions.getJSONArray("delete"); | |
647 Actor actor = getActorFromPermissions(perms); | |
648 annot.setDeletePermission(actor); | |
649 } | |
650 if (permissions.has("update")) { | |
651 JSONArray perms = permissions.getJSONArray("update"); | |
652 Actor actor = getActorFromPermissions(perms); | |
653 annot.setUpdatePermission(actor); | |
654 } | |
655 if (permissions.has("read")) { | |
656 JSONArray perms = permissions.getJSONArray("read"); | |
657 Actor actor = getActorFromPermissions(perms); | |
658 annot.setReadPermission(actor); | |
659 } | |
660 } | |
661 | |
16 | 662 /* |
663 * tags | |
664 */ | |
665 if (jo.has("tags")) { | |
666 HashSet<String> tagset = new HashSet<String>(); | |
667 JSONArray tags = jo.getJSONArray("tags"); | |
668 for (int i = 0; i < tags.length(); ++i) { | |
669 tagset.add(tags.getString(i)); | |
670 } | |
671 annot.setTags(tagset); | |
672 } | |
673 | |
4 | 674 return annot; |
3 | 675 } |
676 | |
61 | 677 @SuppressWarnings("unused") |
678 // i in for loop | |
10 | 679 protected Actor getActorFromPermissions(JSONArray perms) throws JSONException { |
680 Actor actor = null; | |
681 for (int i = 0; i < perms.length(); ++i) { | |
682 String perm = perms.getString(i); | |
683 if (perm.toLowerCase().startsWith("group:")) { | |
684 String groupId = perm.substring(6); | |
685 actor = new Group(groupId); | |
686 } else { | |
687 actor = new Person(perm); | |
688 } | |
689 // we just take the first one | |
690 break; | |
691 } | |
692 return actor; | |
693 } | |
694 | |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
695 public static float getFloat(String s) { |
61 | 696 try { |
697 return Float.parseFloat(s); | |
698 } catch (NumberFormatException e) { | |
699 } | |
700 return 0f; | |
701 } | |
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
702 |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
703 public static int getInt(String s) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
704 try { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
705 return Integer.parseInt(s); |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
706 } catch (NumberFormatException e) { |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
707 } |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
708 return 0; |
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
709 } |
3 | 710 } |