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