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