Mercurial > hg > AnnotationManagerN4J
annotate src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 84:6bf38b5e30a8
also stores polygon shape image annotations.
| author | casties |
|---|---|
| date | Fri, 23 Jan 2015 17:24:53 +0100 |
| parents | 4e2dc67997a0 |
| children | ed51eadc82c5 |
| 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 | |
| 467 fragment = String.format("wkt=POLYGON((%s))", coords); | |
| 61 | 468 } else { |
| 75 | 469 logger.severe("Unable to parse this shape: " + shape); |
| 61 | 470 } |
| 471 return fragment; | |
| 472 } | |
| 473 | |
| 5 | 474 protected String parseArea(JSONObject area) throws JSONException { |
| 4 | 475 String x = area.getString("x"); |
| 476 String y = area.getString("y"); | |
| 477 String width = "0"; | |
| 478 String height = "0"; | |
| 479 if (area.has("width")) { | |
| 480 width = area.getString("width"); | |
| 481 height = area.getString("height"); | |
| 482 } | |
| 5 | 483 String fragment = String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height); |
| 4 | 484 return fragment; |
| 485 } | |
| 486 | |
| 5 | 487 protected String parseRange(JSONObject range) throws JSONException { |
| 4 | 488 String start = range.getString("start"); |
| 489 String end = range.getString("end"); | |
| 490 String startOffset = range.getString("startOffset"); | |
| 491 String endOffset = range.getString("endOffset"); | |
| 5 | 492 String fragment = String.format( |
| 4 | 493 "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start, |
| 5 | 494 startOffset, end, endOffset); |
| 4 | 495 return fragment; |
| 496 } | |
| 497 | |
| 3 | 498 /** |
| 5 | 499 * Creates an Annotation object with data from JSON. |
| 3 | 500 * |
| 4 | 501 * uses the specification from the annotator project: {@link https |
| 502 * ://github.com/okfn/annotator/wiki/Annotation-format} | |
| 3 | 503 * |
| 4 | 504 * The username will be transformed to an URI if not given already as URI, |
| 505 * if not it will set to the MPIWG namespace defined in | |
| 3 | 506 * de.mpiwg.itgroup.annotationManager.Constants.NS |
| 507 * | |
| 508 * @param jo | |
| 509 * @return | |
| 510 * @throws JSONException | |
| 4 | 511 * @throws UnsupportedEncodingException |
| 3 | 512 */ |
| 4 | 513 public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException { |
| 3 | 514 return updateAnnotation(new Annotation(), jo, entity); |
| 515 } | |
| 516 | |
| 5 | 517 /** |
| 518 * Updates an Annotation object with data from JSON. | |
| 519 * | |
| 520 * uses the specification from the annotator project: {@link https | |
| 521 * ://github.com/okfn/annotator/wiki/Annotation-format} | |
| 522 * | |
| 523 * The username will be transformed to an URI if not given already as URI, | |
| 524 * if not it will set to the MPIWG namespace defined in | |
| 525 * de.mpiwg.itgroup.annotationManager.Constants.NS | |
| 526 * | |
| 527 * @param annot | |
| 528 * @param jo | |
| 529 * @return | |
| 530 * @throws JSONException | |
| 531 * @throws UnsupportedEncodingException | |
| 532 */ | |
| 4 | 533 public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException, |
| 534 UnsupportedEncodingException { | |
| 16 | 535 /* |
| 536 * target uri | |
| 537 */ | |
| 3 | 538 if (jo.has("uri")) { |
|
48
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
539 annot.setTarget(new Target(jo.getString("uri"))); |
| 3 | 540 } |
| 16 | 541 /* |
|
40
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
542 * resource uri |
|
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
543 */ |
|
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
544 if (jo.has("resource")) { |
|
48
0e00bf8e27fb
targets and resources of Annotation object are objects now.
casties
parents:
40
diff
changeset
|
545 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
|
546 } |
|
03e0f7574224
saving and loading resource targets should work now (no searching yet)
casties
parents:
22
diff
changeset
|
547 /* |
| 16 | 548 * annotation text |
| 549 */ | |
| 3 | 550 if (jo.has("text")) { |
| 4 | 551 annot.setBodyText(jo.getString("text")); |
| 3 | 552 } |
| 16 | 553 /* |
| 76 | 554 * annotation quote |
| 555 */ | |
| 556 if (jo.has("quote")) { | |
| 557 annot.setQuote(jo.getString("quote")); | |
| 558 } | |
| 559 /* | |
| 16 | 560 * check authentication |
| 561 */ | |
| 3 | 562 String authUser = checkAuthToken(entity); |
| 563 if (authUser == null) { | |
| 4 | 564 /* |
| 565 * // try http auth User httpUser = getHttpAuthUser(entity); if | |
| 566 * (httpUser == null) { | |
| 567 */ | |
| 568 setStatus(Status.CLIENT_ERROR_FORBIDDEN); | |
| 569 return null; | |
| 570 /* | |
| 571 * } authUser = httpUser.getIdentifier(); | |
| 572 */ | |
| 3 | 573 } |
| 16 | 574 /* |
| 575 * get or create creator object | |
| 576 */ | |
| 9 | 577 Actor creator = annot.getCreator(); |
| 578 if (creator == null) { | |
| 10 | 579 creator = new Person(); |
| 9 | 580 annot.setCreator(creator); |
| 581 } | |
| 3 | 582 // username not required, if no username given authuser will be used |
| 583 String username = null; | |
| 10 | 584 String userUri = creator.getUri(); |
| 3 | 585 if (jo.has("user")) { |
| 586 if (jo.get("user") instanceof String) { | |
| 587 // user is just a String | |
| 588 username = jo.getString("user"); | |
| 10 | 589 creator.setId(username); |
| 3 | 590 // TODO: what if username and authUser are different? |
| 591 } else { | |
| 592 // user is an object | |
| 593 JSONObject user = jo.getJSONObject("user"); | |
| 594 if (user.has("id")) { | |
| 10 | 595 String id = user.getString("id"); |
| 596 creator.setId(id); | |
| 597 username = id; | |
| 3 | 598 } |
| 599 if (user.has("uri")) { | |
| 600 userUri = user.getString("uri"); | |
| 601 } | |
| 602 } | |
| 603 } | |
| 604 if (username == null) { | |
| 605 username = authUser; | |
| 606 } | |
| 5 | 607 // try to get full name |
| 9 | 608 if (creator.getName() == null && username != null) { |
| 18 | 609 BaseRestlet restServer = (BaseRestlet) getApplication(); |
| 5 | 610 String fullName = restServer.getFullNameFromLdap(username); |
| 9 | 611 creator.setName(fullName); |
| 5 | 612 } |
| 613 // userUri should be a URI, if not it will set to the MPIWG namespace | |
| 3 | 614 if (userUri == null) { |
| 615 if (username.startsWith("http")) { | |
| 616 userUri = username; | |
| 617 } else { | |
| 58 | 618 userUri = BaseRestlet.PERSONS_URI_PREFIX + username; |
| 3 | 619 } |
| 620 } | |
| 621 // TODO: should we overwrite the creator? | |
| 9 | 622 if (creator.getUri() == null) { |
| 623 creator.setUri(userUri); | |
| 5 | 624 } |
| 16 | 625 /* |
| 626 * creation date | |
| 627 */ | |
| 5 | 628 if (annot.getCreated() == null) { |
| 629 // set creation date | |
| 630 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); | |
| 631 String ct = format.format(Calendar.getInstance().getTime()); | |
| 632 annot.setCreated(ct); | |
| 633 } | |
| 3 | 634 |
| 16 | 635 /* |
| 61 | 636 * create fragment from the first range/area |
| 16 | 637 */ |
| 52 | 638 try { |
| 639 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
|
640 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
|
641 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
|
642 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
|
643 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
|
644 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
|
645 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
|
646 } |
| 52 | 647 } |
| 648 } catch (JSONException e) { | |
| 649 // nothing to do | |
| 3 | 650 } |
| 52 | 651 try { |
| 61 | 652 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
|
653 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
|
654 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
|
655 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
|
656 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
|
657 annot.setTargetFragment(fragment); |
| 84 | 658 if (fragment.startsWith("wkt=")) { |
| 659 annot.setFragmentType(FragmentTypes.WKT); | |
| 660 } else { | |
| 661 annot.setFragmentType(FragmentTypes.AREA); | |
| 662 } | |
|
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
663 } |
| 61 | 664 } |
| 665 } catch (JSONException e) { | |
| 666 // nothing to do | |
| 667 } | |
| 668 // deprecated areas type | |
| 669 try { | |
| 52 | 670 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
|
671 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
|
672 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
|
673 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
|
674 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
|
675 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
|
676 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
|
677 } |
| 52 | 678 } |
| 679 } catch (JSONException e) { | |
| 680 // nothing to do | |
| 3 | 681 } |
|
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
682 // 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
|
683 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
|
684 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
|
685 } |
| 64 | 686 |
| 16 | 687 /* |
| 688 * permissions | |
| 689 */ | |
| 10 | 690 if (jo.has("permissions")) { |
| 691 JSONObject permissions = jo.getJSONObject("permissions"); | |
| 692 if (permissions.has("admin")) { | |
| 693 JSONArray perms = permissions.getJSONArray("admin"); | |
| 694 Actor actor = getActorFromPermissions(perms); | |
| 695 annot.setAdminPermission(actor); | |
| 696 } | |
| 697 if (permissions.has("delete")) { | |
| 698 JSONArray perms = permissions.getJSONArray("delete"); | |
| 699 Actor actor = getActorFromPermissions(perms); | |
| 700 annot.setDeletePermission(actor); | |
| 701 } | |
| 702 if (permissions.has("update")) { | |
| 703 JSONArray perms = permissions.getJSONArray("update"); | |
| 704 Actor actor = getActorFromPermissions(perms); | |
| 705 annot.setUpdatePermission(actor); | |
| 706 } | |
| 707 if (permissions.has("read")) { | |
| 708 JSONArray perms = permissions.getJSONArray("read"); | |
| 709 Actor actor = getActorFromPermissions(perms); | |
| 710 annot.setReadPermission(actor); | |
| 711 } | |
| 712 } | |
| 713 | |
| 16 | 714 /* |
| 715 * tags | |
| 716 */ | |
| 717 if (jo.has("tags")) { | |
| 718 HashSet<String> tagset = new HashSet<String>(); | |
| 719 JSONArray tags = jo.getJSONArray("tags"); | |
| 720 for (int i = 0; i < tags.length(); ++i) { | |
| 721 tagset.add(tags.getString(i)); | |
| 722 } | |
| 723 annot.setTags(tagset); | |
| 724 } | |
| 725 | |
| 4 | 726 return annot; |
| 3 | 727 } |
| 728 | |
| 61 | 729 @SuppressWarnings("unused") |
| 730 // i in for loop | |
| 10 | 731 protected Actor getActorFromPermissions(JSONArray perms) throws JSONException { |
| 732 Actor actor = null; | |
| 733 for (int i = 0; i < perms.length(); ++i) { | |
| 734 String perm = perms.getString(i); | |
| 735 if (perm.toLowerCase().startsWith("group:")) { | |
| 736 String groupId = perm.substring(6); | |
| 737 actor = new Group(groupId); | |
| 738 } else { | |
| 739 actor = new Person(perm); | |
| 740 } | |
| 741 // we just take the first one | |
| 742 break; | |
| 743 } | |
| 744 return actor; | |
| 745 } | |
| 746 | |
|
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
747 public static float getFloat(String s) { |
| 61 | 748 try { |
| 749 return Float.parseFloat(s); | |
| 750 } catch (NumberFormatException e) { | |
| 751 } | |
| 752 return 0f; | |
| 753 } | |
|
63
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
754 |
|
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
755 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
|
756 try { |
|
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
757 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
|
758 } 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
|
759 } |
|
9f8c9611848a
fixed bug with new rectangle shapes. added limit, offset and sortBy parameters to annotator/ and annotator/search.
casties
parents:
61
diff
changeset
|
760 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
|
761 } |
| 3 | 762 } |
