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