source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 4:3599b29c393f

Last change on this file since 4:3599b29c393f was 4:3599b29c393f, checked in by casties, 12 years ago

store seems to work now :-)

File size: 15.7 KB
Line 
1/**
2 * Base class for Annotator resource classes.
3 */
4package de.mpiwg.itgroup.annotations.restlet;
5
6import java.io.UnsupportedEncodingException;
7import java.net.URLDecoder;
8import java.net.URLEncoder;
9import java.security.InvalidKeyException;
10import java.security.SignatureException;
11import java.util.ArrayList;
12import java.util.List;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import javax.servlet.ServletContext;
17
18import net.oauth.jsontoken.Checker;
19import net.oauth.jsontoken.JsonToken;
20import net.oauth.jsontoken.JsonTokenParser;
21import net.oauth.jsontoken.SystemClock;
22import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
23import net.oauth.jsontoken.crypto.Verifier;
24
25import org.apache.commons.codec.binary.Base64;
26import org.apache.log4j.Logger;
27import org.json.JSONArray;
28import org.json.JSONException;
29import org.json.JSONObject;
30import org.restlet.data.Form;
31import org.restlet.data.Status;
32import org.restlet.representation.Representation;
33import org.restlet.resource.Options;
34import org.restlet.resource.ServerResource;
35
36import de.mpiwg.itgroup.annotations.Annotation;
37import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes;
38import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore;
39import de.mpiwg.itgroup.annotations.old.NS;
40
41/**
42 * Base class for Annotator resource classes.
43 *
44 * @author dwinter, casties
45 *
46 */
47public abstract class AnnotatorResourceImpl extends ServerResource {
48
49    protected static Logger logger = Logger.getLogger(AnnotatorResourceImpl.class);
50
51    private AnnotationStore store;
52
53    protected String getAllowedMethodsForHeader() {
54        return "OPTIONS,GET,POST";
55    }
56
57    protected AnnotationStore getAnnotationStore() {
58        if (store == null) {
59            ServletContext sc = (ServletContext) getContext().getServerDispatcher().getContext().getAttributes()
60                    .get("org.restlet.ext.servlet.ServletContext");
61            logger.debug("Getting AnnotationStore from Context");
62            store = (AnnotationStore) sc.getAttribute(RestServer.ANNSTORE_KEY);
63        }
64        return store;
65    }
66
67    public String encodeJsonId(String id) {
68        try {
69            return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8"));
70        } catch (UnsupportedEncodingException e) {
71            return null;
72        }
73    }
74
75    public String decodeJsonId(String id) {
76        try {
77            return new String(Base64.decodeBase64(id), "UTF-8");
78        } catch (UnsupportedEncodingException e) {
79            return null;
80        }
81    }
82
83    /**
84     * Handle options request to allow CORS for AJAX.
85     *
86     * @param entity
87     */
88    @Options
89    public void doOptions(Representation entity) {
90        logger.debug("AnnotatorResourceImpl doOptions!");
91        setCorsHeaders();
92    }
93
94    /**
95     * set headers to allow CORS for AJAX.
96     */
97    protected void setCorsHeaders() {
98        Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers");
99        if (responseHeaders == null) {
100            responseHeaders = new Form();
101            getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
102        }
103        responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader());
104        // echo back Origin and Request-Headers
105        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
106        String origin = requestHeaders.getFirstValue("Origin", true);
107        if (origin == null) {
108            responseHeaders.add("Access-Control-Allow-Origin", "*");
109        } else {
110            responseHeaders.add("Access-Control-Allow-Origin", origin);
111        }
112        String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true);
113        if (allowHeaders != null) {
114            responseHeaders.add("Access-Control-Allow-Headers", allowHeaders);
115        }
116        responseHeaders.add("Access-Control-Allow-Credentials", "true");
117        responseHeaders.add("Access-Control-Max-Age", "60");
118    }
119
120    /**
121     * returns if authentication information from headers is valid.
122     *
123     * @param entity
124     * @return
125     */
126    public boolean isAuthenticated(Representation entity) {
127        return (checkAuthToken(entity) != null);
128    }
129
130    /**
131     * checks Annotator Auth plugin authentication information from headers.
132     * returns userId if successful.
133     *
134     * @param entity
135     * @return
136     */
137    public String checkAuthToken(Representation entity) {
138        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
139        String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true);
140        // decode token first to get consumer key
141        JsonToken token = new JsonTokenParser(null, null).deserialize(authToken);
142        String userId = token.getParamAsPrimitive("userId").getAsString();
143        String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString();
144        // get stored consumer secret for key
145        RestServer restServer = (RestServer) getApplication();
146        String consumerSecret = restServer.getConsumerSecret(consumerKey);
147        logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret);
148        if (consumerSecret == null) {
149            return null;
150        }
151        // logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm()));
152        try {
153            List<Verifier> verifiers = new ArrayList<Verifier>();
154            // we only do HS256 yet
155            verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8")));
156            // verify token signature(should really be static...)
157            new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers);
158        } catch (SignatureException e) {
159            // TODO Auto-generated catch block
160            e.printStackTrace();
161        } catch (InvalidKeyException e) {
162            // TODO Auto-generated catch block
163            e.printStackTrace();
164        } catch (UnsupportedEncodingException e) {
165            // TODO Auto-generated catch block
166            e.printStackTrace();
167        }
168        // must be ok then
169        logger.debug("auth OK! user=" + userId);
170        return userId;
171    }
172
173    /**
174     * creates Annotator-JSON from an Annotation object.
175     *
176     * @param annot
177     * @return
178     */
179    public JSONObject createAnnotatorJson(Annotation annot) {
180        boolean makeUserObject = true;
181        JSONObject jo = new JSONObject();
182        try {
183            jo.put("text", annot.getBodyText());
184            jo.put("uri", annot.getTargetBaseUri());
185
186            if (makeUserObject) {
187                // create user object
188                JSONObject userObject = new JSONObject();
189                // save creator as uri
190                userObject.put("uri", annot.getCreatorUri());
191                // make short user id
192                String userId = annot.getCreatorUri();
193                if (userId != null && userId.startsWith(NS.MPIWG_PERSONS_URL)) {
194                    userId = userId.replace(NS.MPIWG_PERSONS_URL, ""); // entferne
195                                                                       // NAMESPACE
196                }
197                // save as id
198                userObject.put("id", userId);
199                // get full name
200                RestServer restServer = (RestServer) getApplication();
201                String userName = restServer.getUserNameFromLdap(userId);
202                userObject.put("name", userName);
203                // save user object
204                jo.put("user", userObject);
205            } else {
206                // save user as string
207                jo.put("user", annot.getCreatorUri());
208            }
209
210            if (annot.getTargetFragment() != null) {
211                // we only look at the first xpointer
212                List<String> fragments = new ArrayList<String>();
213                fragments.add(annot.getTargetFragment());
214                FragmentTypes xt = annot.getFragmentType();
215                if (xt == FragmentTypes.XPOINTER) {
216                    jo.put("ranges", transformToRanges(fragments));
217                } else if (xt == FragmentTypes.AREA) {
218                    jo.put("areas", transformToAreas(fragments));
219                }
220            }
221            // encode Annotation URL (=id) in base64
222            String annotUrl = annot.getUri();
223            String annotId = encodeJsonId(annotUrl);
224            jo.put("id", annotId);
225            return jo;
226        } catch (JSONException e) {
227            // TODO Auto-generated catch block
228            e.printStackTrace();
229        }
230        return null;
231    }
232
233    private JSONArray transformToRanges(List<String> xpointers) {
234
235        JSONArray ja = new JSONArray();
236
237        Pattern rg = Pattern
238                .compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)");
239        Pattern rg1 = Pattern.compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)");
240
241        try {
242            for (String xpointer : xpointers) {
243                String decoded = URLDecoder.decode(xpointer, "utf-8");
244                Matcher m = rg.matcher(decoded);
245
246                if (m.find()) {
247                    {
248                        JSONObject jo = new JSONObject();
249                        jo.put("start", m.group(1));
250                        jo.put("startOffset", m.group(2));
251                        jo.put("end", m.group(3));
252                        jo.put("endOffset", m.group(4));
253                        ja.put(jo);
254                    }
255                }
256                m = rg1.matcher(xpointer);
257                if (m.find()) {
258                    JSONObject jo = new JSONObject();
259                    jo.put("start", m.group(1));
260                    jo.put("startOffset", m.group(2));
261
262                    ja.put(jo);
263                }
264            }
265        } catch (JSONException e) {
266            // TODO Auto-generated catch block
267            e.printStackTrace();
268        } catch (UnsupportedEncodingException e) {
269            // TODO Auto-generated catch block
270            e.printStackTrace();
271        }
272
273        return ja;
274    }
275
276    private JSONArray transformToAreas(List<String> xpointers) {
277
278        JSONArray ja = new JSONArray();
279
280        Pattern rg = Pattern.compile("xywh=(\\w*:)([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)");
281
282        try {
283            for (String xpointer : xpointers) {
284                String decoded = URLDecoder.decode(xpointer, "utf-8");
285                Matcher m = rg.matcher(decoded);
286
287                if (m.find()) {
288                    {
289                        JSONObject jo = new JSONObject();
290                        String unit = m.group(1);
291                        jo.put("x", m.group(2));
292                        jo.put("y", m.group(3));
293                        jo.put("width", m.group(4));
294                        jo.put("height", m.group(5));
295                        ja.put(jo);
296                    }
297                }
298            }
299        } catch (JSONException e) {
300            // TODO Auto-generated catch block
301            e.printStackTrace();
302        } catch (UnsupportedEncodingException e) {
303            // TODO Auto-generated catch block
304            e.printStackTrace();
305        }
306
307        return ja;
308    }
309
310    protected String parseArea(JSONObject area) throws JSONException, UnsupportedEncodingException {
311        String x = area.getString("x");
312        String y = area.getString("y");
313        String width = "0";
314        String height = "0";
315        if (area.has("width")) {
316            width = area.getString("width");
317            height = area.getString("height");
318        }
319        String fragment = URLEncoder.encode(String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height), "utf-8");
320        return fragment;
321    }
322
323    protected String parseRange(JSONObject range) throws JSONException, UnsupportedEncodingException {
324        String start = range.getString("start");
325        String end = range.getString("end");
326        String startOffset = range.getString("startOffset");
327        String endOffset = range.getString("endOffset");
328
329        String fragment = URLEncoder.encode(String.format(
330                "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start,
331                startOffset, end, endOffset), "utf-8");
332        return fragment;
333    }
334
335    /**
336     * creates an Annotation object with data from JSON.
337     *
338     * uses the specification from the annotator project: {@link https
339     * ://github.com/okfn/annotator/wiki/Annotation-format}
340     *
341     * The username will be transformed to an URI if not given already as URI,
342     * if not it will set to the MPIWG namespace defined in
343     * de.mpiwg.itgroup.annotationManager.Constants.NS
344     *
345     * @param jo
346     * @return
347     * @throws JSONException
348     * @throws UnsupportedEncodingException
349     */
350    public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException {
351        return updateAnnotation(new Annotation(), jo, entity);
352    }
353
354    public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException,
355            UnsupportedEncodingException {
356        // annotated uri
357        if (jo.has("uri")) {
358            annot.setTargetBaseUri(jo.getString("uri"));
359        }
360        // annotation text
361        if (jo.has("text")) {
362            annot.setBodyText(jo.getString("text"));
363        }
364        // check authentication
365        String authUser = checkAuthToken(entity);
366        if (authUser == null) {
367            /*
368             * // try http auth User httpUser = getHttpAuthUser(entity); if
369             * (httpUser == null) {
370             */
371            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
372            return null;
373            /*
374             * } authUser = httpUser.getIdentifier();
375             */
376        }
377        // username not required, if no username given authuser will be used
378        String username = null;
379        String userUri = annot.getCreatorUri();
380        if (jo.has("user")) {
381            if (jo.get("user") instanceof String) {
382                // user is just a String
383                username = jo.getString("user");
384                // TODO: what if username and authUser are different?
385            } else {
386                // user is an object
387                JSONObject user = jo.getJSONObject("user");
388                if (user.has("id")) {
389                    username = user.getString("id");
390                }
391                if (user.has("uri")) {
392                    userUri = user.getString("uri");
393                }
394            }
395        }
396        if (username == null) {
397            username = authUser;
398        }
399        // username should be a URI, if not it will set to the MPIWG namespace
400        // defined in
401        // de.mpiwg.itgroup.annotationManager.Constants.NS
402        if (userUri == null) {
403            if (username.startsWith("http")) {
404                userUri = username;
405            } else {
406                userUri = NS.MPIWG_PERSONS_URL + username;
407            }
408        }
409        // TODO: should we overwrite the creator?
410
411        // create xpointer from the first range/area
412        if (jo.has("ranges")) {
413            JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0);
414            annot.setFragmentType(FragmentTypes.XPOINTER);
415            String fragment = parseRange(ranges);
416            annot.setTargetFragment(fragment);
417        }
418        if (jo.has("areas")) {
419            JSONObject area = jo.getJSONArray("areas").getJSONObject(0);
420            annot.setFragmentType(FragmentTypes.AREA);
421            String fragment = parseArea(area);
422            annot.setTargetFragment(fragment);
423        }
424        return annot;
425    }
426
427}
Note: See TracBrowser for help on using the repository browser.