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