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