source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 5:bbf0cc5bee29

Last change on this file since 5:bbf0cc5bee29 was 5:bbf0cc5bee29, checked in by casties, 12 years ago

version 0.2 really works now

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