Project

General

Profile

« Previous | Next » 

Revision 9014

add Annotator Store implementation -- pass through to D1 API for the AnnotatorJS API

View differences:

test/annotator-sample.json
1
{
2
  "pid": "tao.1.1", 
3
  "field": "annotation_sm", 
4
  "reject": false, 
5
  "ranges": [
6
    {
7
      "start": "/section[1]/article[1]/form[1]/section[1]/div[1]/div[1]", 
8
      "end": "/section[1]/article[1]/form[1]/section[1]/div[1]/div[1]", 
9
      "startOffset": 0, 
10
      "endOffset": 4
11
    }
12
  ], 
13
  "permissions": {
14
    "read": [
15
      "group:__world__"
16
    ], 
17
    "delete": [], 
18
    "admin": [], 
19
    "update": []
20
  }, 
21
  "user": "CN=Benjamin Leinfelder A515,O=University of Chicago,C=US,DC=cilogon,DC=org", 
22
  "consumer": "metacat", 
23
  "updated": "2014-12-03T23:29:20.262152+00:00", 
24
  "quote": "Data", 
25
  "oa:Motivation": "oa:tagging", 
26
  "tags": [], 
27
  "text": "Original annotation content", 
28
  "created": "2014-12-03T23:09:25.501665+00:00", 
29
  "uri": "https://cn-dev.test.dataone.org/cn/v1/object/tao.1.1"
30
}
test/edu/ucsb/nceas/metacat/annotation/store/AnnotatorStoreTest.java
1
/**  '$RCSfile$'
2
 *  Copyright: 2010 Regents of the University of California and the
3
 *              National Center for Ecological Analysis and Synthesis
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 */
19
package edu.ucsb.nceas.metacat.annotation.store;
20

  
21
import java.io.ByteArrayInputStream;
22
import java.io.InputStream;
23

  
24
import org.dataone.portal.TokenGenerator;
25

  
26
import junit.framework.Test;
27
import junit.framework.TestSuite;
28
import net.minidev.json.JSONObject;
29
import net.minidev.json.JSONValue;
30
import edu.ucsb.nceas.metacat.dataone.D1NodeServiceTest;
31

  
32
public class AnnotatorStoreTest extends D1NodeServiceTest {
33

  
34
	private static String ANNOTATION_TEST_DOC = "test/annotator-sample.json";
35
	
36
	/**
37
	 * constructor for the test
38
	 */
39
	public AnnotatorStoreTest(String name) {
40
		super(name);
41
		
42
	}
43

  
44
	/**
45
	 * Establish a testing framework by initializing appropriate objects
46
	 */
47
	public void setUp() throws Exception {
48
		super.setUp();		
49
		String testToken = TokenGenerator.getJWT("uid=kepler,o=unaffiliated,dc=ecoinformatics,dc=org", "Kepler");
50
		request.addHeader("x-annotator-auth-token", testToken);
51

  
52
	}
53

  
54
	/**
55
	 * Release any objects after tests are complete
56
	 */
57
	public void tearDown() {
58
	}
59

  
60
	/**
61
	 * Create a suite of tests to be run together
62
	 */
63
	public static Test suite() {
64
		TestSuite suite = new TestSuite();
65
		suite.addTest(new AnnotatorStoreTest("testRoundTrip"));
66

  
67
		return suite;
68
	}
69
	
70
	public void testRoundTrip() {
71
		try {
72
			
73
			
74
			// read the test annotation for CRUD operations
75
			AnnotatorStore as = new AnnotatorStore(request);
76
			InputStream is = new ByteArrayInputStream(this.getTestDocFromFile(ANNOTATION_TEST_DOC).getBytes("UTF-8"));
77
			JSONObject annotation = (JSONObject) JSONValue.parse(is);
78
			String id = as.create(annotation);
79
			
80
			// check the created
81
			JSONObject originalAnnotation = as.read(id);
82
			String text = "Original annotation content";
83
			String originalText = originalAnnotation.get("text").toString();
84
			assertEquals(text, originalText);
85
			
86
			// test the update
87
//			text = "updated content";
88
//			JSONObject partialAnnotation =  (JSONObject) JSONValue.parse("{'text' : '" + text + "' }");
89
//			as.update(id, partialAnnotation);
90
//			JSONObject updatedAnnotation = as.read(id);
91
//			String updatedText = updatedAnnotation.get("text").toString();
92
//			assertEquals(text, updatedText);
93
			
94
			// TODO: check delete
95
			as.delete(id);
96
			
97
		} catch (Exception e) {
98
			e.printStackTrace();
99
			fail(e.getMessage());
100

  
101
		}
102
	}
103
	
104

  
105
}
0 106

  
lib/web.xml.tomcat6
218 218
      </init-param>
219 219
      <load-on-startup>20</load-on-startup>
220 220
    </servlet>
221
    
222
    <!-- annotator servlet -->
223
    <servlet>
224
      <servlet-name>annotatorServlet</servlet-name>
225
      <servlet-class>edu.ucsb.nceas.metacat.annotation.store.AnnotatorRestServlet</servlet-class>
226
      <init-param>
227
        <param-name>debug</param-name>
228
        <param-value>1</param-value>
229
      </init-param>
230
      <init-param>
231
        <param-name>listings</param-name>
232
        <param-value>true</param-value>
233
      </init-param>
234
      <load-on-startup>20</load-on-startup>
235
    </servlet>
236
    
237
    <servlet-mapping>
238
        <servlet-name>annotatorServlet</servlet-name>
239
        <url-pattern>/annotator</url-pattern>
240
    </servlet-mapping>
241
    
242
    <servlet-mapping>
243
        <servlet-name>annotatorServlet</servlet-name>
244
        <url-pattern>/annotator/*</url-pattern>
245
    </servlet-mapping>
221 246
  
222 247
    <servlet-mapping>
223 248
        <servlet-name>metacat</servlet-name>
src/edu/ucsb/nceas/metacat/annotation/store/AnnotatorStore.java
1
/**
2
 * 
3
 */
4
package edu.ucsb.nceas.metacat.annotation.store;
5

  
6
import java.io.ByteArrayInputStream;
7
import java.io.InputStream;
8
import java.math.BigInteger;
9
import java.nio.charset.Charset;
10
import java.util.ArrayList;
11
import java.util.Calendar;
12
import java.util.Collection;
13
import java.util.Date;
14
import java.util.List;
15

  
16
import javax.servlet.http.HttpServletRequest;
17

  
18
import org.apache.commons.collections.CollectionUtils;
19
import org.apache.commons.collections.Predicate;
20
import org.apache.commons.collections.PredicateUtils;
21
import org.apache.commons.logging.Log;
22
import org.apache.commons.logging.LogFactory;
23
import org.apache.http.NameValuePair;
24
import org.apache.http.client.utils.URLEncodedUtils;
25
import org.dataone.client.auth.CertificateManager;
26
import org.dataone.portal.TokenGenerator;
27
import org.dataone.service.exceptions.InvalidToken;
28
import org.dataone.service.types.v1.AccessPolicy;
29
import org.dataone.service.types.v1.AccessRule;
30
import org.dataone.service.types.v1.Checksum;
31
import org.dataone.service.types.v1.Identifier;
32
import org.dataone.service.types.v1.NodeReference;
33
import org.dataone.service.types.v1.ObjectFormatIdentifier;
34
import org.dataone.service.types.v1.ObjectInfo;
35
import org.dataone.service.types.v1.ObjectList;
36
import org.dataone.service.types.v1.Permission;
37
import org.dataone.service.types.v1.Session;
38
import org.dataone.service.types.v1.Subject;
39
import org.dataone.service.types.v1.util.ChecksumUtil;
40
import org.dataone.service.types.v2.SystemMetadata;
41
import org.dataone.service.util.Constants;
42
import org.dataone.service.util.DateTimeMarshaller;
43

  
44

  
45
import edu.ucsb.nceas.metacat.MetaCatServlet;
46
import edu.ucsb.nceas.metacat.dataone.MNodeService;
47
import net.minidev.json.JSONArray;
48
import net.minidev.json.JSONObject;
49
import net.minidev.json.JSONValue;
50

  
51
/**
52
 * @author leinfelder
53
 *
54
 */
55
public class AnnotatorStore {
56

  
57
    public static Log logMetacat = LogFactory.getLog(AnnotatorStore.class);
58

  
59
    public static final String ANNOTATION_FORMAT_ID = "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html";
60
	
61
    private HttpServletRequest request;
62
	private Session session;
63
	
64
	public AnnotatorStore(HttpServletRequest request) {
65
		
66
		this.request = request;
67
		
68
		try {
69
			session = CertificateManager.getInstance().getSession(request);
70
		} catch (InvalidToken e) {
71
			logMetacat.warn(e.getMessage(), e);
72
		}
73
		
74
		// try getting it from the token
75
		if (session == null) {
76
			String token = request.getHeader("x-annotator-auth-token");
77
			session = TokenGenerator.getSession(token);
78
		}
79
		
80
	}
81
	
82
	/**
83
	 * Generate minimal systemmetadata for new/updated annotation objects
84
	 * @param annotation
85
	 * @return
86
	 * @throws Exception
87
	 */
88
	private SystemMetadata computeSystemMetadata(JSONObject annotation) throws Exception {
89
		SystemMetadata sysmeta = new SystemMetadata();
90
		
91
		ObjectFormatIdentifier formatId = new ObjectFormatIdentifier();
92
		formatId.setValue(ANNOTATION_FORMAT_ID);
93
		sysmeta.setFormatId(formatId);
94
		
95
		BigInteger size = BigInteger.valueOf(annotation.toJSONString().getBytes(MetaCatServlet.DEFAULT_ENCODING).length);
96
		sysmeta.setSize(size);
97
		
98
		Checksum checksum = ChecksumUtil.checksum(annotation.toJSONString().getBytes(MetaCatServlet.DEFAULT_ENCODING), "MD5");
99
		sysmeta.setChecksum(checksum);
100
		
101
		Subject rightsHolder = session.getSubject();
102
		sysmeta.setRightsHolder(rightsHolder);
103
		sysmeta.setSubmitter(rightsHolder);
104

  
105
		NodeReference authoritativeMemberNode = MNodeService.getInstance(request).getCapabilities().getIdentifier();
106
		sysmeta.setAuthoritativeMemberNode(authoritativeMemberNode );
107
		sysmeta.setOriginMemberNode(authoritativeMemberNode);
108
		
109
		sysmeta.setDateSysMetadataModified(DateTimeMarshaller.deserializeDateToUTC(annotation.get("updated").toString()));
110
		sysmeta.setDateUploaded(DateTimeMarshaller.deserializeDateToUTC(annotation.get("created").toString()));		
111
	
112
		// add access access rules for read
113
		AccessPolicy accessPolicy = new AccessPolicy();
114
		JSONObject permissions =  (JSONObject) annotation.get("permissions");
115
		JSONArray readList = (JSONArray) permissions.get("read");
116
		for (Object read: readList) {
117
			AccessRule accessRule = new AccessRule();
118
			Subject user = new Subject();
119
			
120
			String username = read.toString();
121
			if (username.equals("group:__world__")) {
122
				user.setValue(Constants.SUBJECT_PUBLIC);
123
			}
124
			accessRule.addSubject(user);
125
			accessRule.addPermission(Permission.READ);
126
			accessPolicy.addAllow(accessRule);
127
			
128
		}
129
		sysmeta.setAccessPolicy(accessPolicy);
130

  
131
		return sysmeta;
132
	}
133
	
134
	/**
135
	 * Create a new annotation from given object
136
	 * @param annotation
137
	 * @return the generated identifier for the annotation
138
	 * @throws Exception
139
	 */
140
	public String create(JSONObject annotation) throws Exception {
141
		
142
		// use the dataone API to create an object for the annotation
143
		
144
		// create identifiers for the object
145
		Identifier pid = MNodeService.getInstance(request).generateIdentifier(session, "UUID", "annotation");
146
		Identifier sid = MNodeService.getInstance(request).generateIdentifier(session, "UUID", "annotation");
147
		
148
		// add properties to the annotation
149
		// TODO: use SID for the identifier when implemented
150
		annotation.put("id", pid.getValue());
151
		annotation.put("user", session.getSubject().getValue());
152
		Date now = Calendar.getInstance().getTime();
153
		annotation.put("created", DateTimeMarshaller.serializeDateToUTC(now));
154
		annotation.put("updated", DateTimeMarshaller.serializeDateToUTC(now));
155

  
156
		// generate sys meta
157
		SystemMetadata sysmeta = computeSystemMetadata(annotation);
158
		sysmeta.setIdentifier(pid);
159
		sysmeta.setSeriesId(sid);
160
		
161
		// create it on the node
162
		InputStream object = new ByteArrayInputStream(annotation.toJSONString().getBytes(MetaCatServlet.DEFAULT_ENCODING));
163
		MNodeService.getInstance(request).create(session, pid, object, sysmeta);
164
		
165
		return pid.getValue();
166
	}
167

  
168
	/**
169
	 * Read the annotation for given id
170
	 * @param id
171
	 * @return
172
	 * @throws Exception
173
	 */
174
	public JSONObject read(String id) throws Exception {
175
		// read the annotation out as JSON object
176
		Identifier sid = new Identifier();
177
		sid.setValue(id);
178
		InputStream object = MNodeService.getInstance(request).get(session, sid);
179
		JSONObject annotation = (JSONObject) JSONValue.parse(object);
180
		return annotation;
181
	}
182

  
183
	/**
184
	 * TODO: implement when series ID is supported
185
	 * @param id
186
	 * @param partialAnnotation
187
	 * @return
188
	 */
189
	public JSONObject update(String id, JSONObject partialAnnotation) {
190
		// TODO Auto-generated method stub
191
		return null;
192
	}
193

  
194
	/**
195
	 * TODO: allow full delete?
196
	 * Remove the annotation from the store
197
	 * @param id
198
	 * @throws Exception
199
	 */
200
	public void delete(String id) throws Exception {
201
		// read the annotation out as JSON object
202
		Identifier sid = new Identifier();
203
		sid.setValue(id);
204
		
205
		//MNodeService.getInstance(request).delete(session, sid);
206
		MNodeService.getInstance(request).archive(session, sid);
207

  
208
	}
209

  
210
	/**
211
	 * Query annotation store using given query expression
212
	 * @param query
213
	 * @return result listing the total matches and each annotation as a "row"
214
	 * @throws Exception
215
	 */
216
	public JSONObject search(String query) throws Exception {
217
		
218
		JSONObject results = new JSONObject();
219
		
220
		// TODO: better search algorithm!
221
		JSONArray annotations = this.index();
222
		
223
		Collection<Predicate> predicates = new ArrayList<Predicate>();
224
		List<NameValuePair> criteria = URLEncodedUtils.parse(query, Charset.forName(MetaCatServlet.DEFAULT_ENCODING));
225
		for (NameValuePair pair: criteria) {
226
			if (pair.getName().equals("limit") || pair.getName().equals("offset")) {
227
				continue;
228
			}
229
			// otherwise add the criteria
230
			predicates.add(new AnnotationPredicate(pair.getName(), pair.getValue()));
231
			
232
		}
233
		Predicate allPredicate = PredicateUtils.allPredicate(predicates);
234
		CollectionUtils.filter(annotations, allPredicate);
235
		
236
		results.put("total", annotations.size());
237
		results.put("rows", annotations);
238
		return results ;
239
	}
240

  
241
	/**
242
	 * Show the API version information for this store
243
	 * @return
244
	 */
245
	public JSONObject root() {
246
		JSONObject versionInfo = new JSONObject();
247
		versionInfo.put("name", "Metacat Annotator Store API");
248
		versionInfo.put("version", "1.2.9");
249
		return versionInfo ;
250
	}
251

  
252
	/**
253
	 * List all the annotations in the store
254
	 * @return
255
	 * @throws Exception
256
	 */
257
	public JSONArray index() throws Exception {
258
		
259
		JSONArray annotations = new JSONArray();
260
		ObjectFormatIdentifier objectFormatId = new ObjectFormatIdentifier();
261
		objectFormatId.setValue(ANNOTATION_FORMAT_ID);
262
		Integer start = 0;
263
		Integer count = 1000;
264
		ObjectList objects = MNodeService.getInstance(request).listObjects(session, null, null, objectFormatId, null, true, start, count);
265
		for (ObjectInfo info: objects.getObjectInfoList()) {
266
			Identifier pid = info.getIdentifier();
267
			SystemMetadata sysMeta = MNodeService.getInstance(request).getSystemMetadata(session, pid);
268
			// remember we don't have true delete yet
269
			if ( (sysMeta.getArchived() != null && sysMeta.getArchived().booleanValue()) || sysMeta.getObsoletedBy() != null) {
270
				continue;
271
			}
272
			JSONObject annotation = this.read(pid.getValue());
273
			annotations.add(annotation);
274
		}
275

  
276
		return annotations ;
277
	}
278

  
279
}
280

  
281
class AnnotationPredicate implements Predicate {
282

  
283
	private String name;
284
	private String value;
285
	
286
	public AnnotationPredicate(String name, String value) {
287
		this.name = name;
288
		this.value = value;
289
	}
290
	
291
	@Override
292
	public boolean evaluate(Object obj) {
293
		JSONObject annotation = (JSONObject) obj;
294
		// simple string equals for now
295
		String actualValue = (String) annotation.get(name);
296
		if (actualValue == null) {
297
			return false;
298
		}
299
		return actualValue.equals(value);
300
	}
301
		
302
}
303

  
0 304

  
src/edu/ucsb/nceas/metacat/annotation/store/AnnotatorRestServlet.java
1
/**
2
 *  '$RCSfile$'
3
 *  Copyright: 2000 Regents of the University of California and the
4
 *              National Center for Ecological Analysis and Synthesis
5
 *
6
 *   '$Author$'
7
 *     '$Date$'
8
 *
9
 * This program is free software; you can redistribute it and/or modify
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 2 of the License, or
12
 * (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 Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
 */
23
package edu.ucsb.nceas.metacat.annotation.store;
24

  
25
import java.io.IOException;
26
import java.io.InputStream;
27

  
28
import javax.servlet.ServletConfig;
29
import javax.servlet.ServletException;
30
import javax.servlet.http.HttpServlet;
31
import javax.servlet.http.HttpServletRequest;
32
import javax.servlet.http.HttpServletResponse;
33

  
34
import net.minidev.json.JSONArray;
35
import net.minidev.json.JSONObject;
36
import net.minidev.json.JSONValue;
37

  
38
import org.apache.log4j.Logger;
39

  
40
/**
41
 * Metacat REST handler for Annotator storage API
42
 * http://docs.annotatorjs.org/en/v1.2.x/storage.html#core-storage-api 
43
 *  
44
 */
45
public class AnnotatorRestServlet extends HttpServlet {
46

  
47
    protected Logger logMetacat;
48
    
49
    private String getResource(HttpServletRequest request) {
50
    	// get the resource
51
        String resource = request.getPathInfo();
52
        resource = resource.substring(resource.indexOf("/") + 1);
53
        return resource;
54
    }
55
    
56
    /**
57
     * Initalize servlet by setting logger
58
     */
59
    @Override
60
    public void init(ServletConfig config) throws ServletException {
61
        logMetacat = Logger.getLogger(this.getClass());
62
        super.init(config);
63
    }
64

  
65
    /** Handle "GET" method requests from HTTP clients */
66
    @Override
67
    protected void doGet(HttpServletRequest request,
68
            HttpServletResponse response) throws ServletException, IOException {
69
        logMetacat.debug("HTTP Verb: GET");
70
        String resource = this.getResource(request);
71
        
72
        if (resource.startsWith("annotations/")) {
73
        	String id = request.getPathInfo().substring(request.getPathInfo().lastIndexOf("/") + 1);
74
        	AnnotatorStore as = new AnnotatorStore(request);
75
        	try {
76
				JSONObject annotation = as.read(id);
77
				annotation.writeJSONString(response.getWriter());
78
				return;
79
			} catch (Exception e) {
80
				throw new ServletException(e);
81
			}
82
        	
83
        }
84
        
85
        // handle listing them
86
        if (resource.startsWith("annotations")) {
87
        	AnnotatorStore as = new AnnotatorStore(request);
88
        	try {
89
				JSONArray annotations = as.index();
90
				annotations.writeJSONString(response.getWriter());
91
				return;
92
			} catch (Exception e) {
93
				throw new ServletException(e);
94
			}
95
        	
96
        }
97
        
98
        // handle searching them
99
        if (resource.startsWith("search")) {
100
        	String query = request.getQueryString();
101
        	AnnotatorStore as = new AnnotatorStore(request);
102
        	try {
103
				JSONObject results = as.search(query);
104
				results.writeJSONString(response.getWriter());
105
				return;
106
			} catch (Exception e) {
107
				e.printStackTrace();
108
				throw new ServletException(e);
109
			}
110
        	
111
        }
112
        
113
        
114
    }
115

  
116
    /** Handle "POST" method requests from HTTP clients */
117
    @Override
118
    protected void doPost(HttpServletRequest request,
119
            HttpServletResponse response) throws ServletException, IOException {
120
        logMetacat.debug("HTTP Verb: POST");
121

  
122
        String resource = this.getResource(request);
123
        if (resource.equals("annotations")) {
124
        	AnnotatorStore as = new AnnotatorStore(request);
125
        	try {
126
        		// get the annotation from the request
127
        		InputStream is = request.getInputStream();
128
    			JSONObject annotation = (JSONObject) JSONValue.parse(is);
129
    			
130
    			// create it on the node
131
				String id = as.create(annotation);
132
				
133
				// TODO: determine which is the correct approach for responding to CREATE
134
				
135
				// redirect to read
136
				response.setStatus(303);
137
				response.sendRedirect(request.getRequestURI() + "/" + id);
138
				
139
				// write it back in the response
140
				JSONObject result = as.read(id);
141
				result.writeJSONString(response.getWriter());
142
				
143
			} catch (Exception e) {
144
				throw new ServletException(e);
145
			}
146
        	
147
        }
148
    }
149

  
150
    /** Handle "DELETE" method requests from HTTP clients */
151
    @Override
152
    protected void doDelete(HttpServletRequest request,
153
            HttpServletResponse response) throws ServletException, IOException {
154
        logMetacat.debug("HTTP Verb: DELETE");
155
        
156
        
157
        String resource = this.getResource(request);
158
        if (resource.startsWith("annotations/")) {
159
        	String id = request.getPathInfo().substring(request.getPathInfo().lastIndexOf("/") + 1);
160
        	AnnotatorStore as = new AnnotatorStore(request);
161
        	try {
162
        		// delete the annotation
163
        		as.delete(id);
164
        		
165
        		// say no content
166
        		response.setContentLength(0);
167
				response.setStatus(204);
168
				
169
			} catch (Exception e) {
170
				throw new ServletException(e);
171
			}
172
        	
173
        }
174
        
175
    }
176

  
177
    /** Handle "PUT" method requests from HTTP clients */
178
    @Override
179
    protected void doPut(HttpServletRequest request,
180
            HttpServletResponse response) throws ServletException, IOException {
181
        logMetacat.debug("HTTP Verb: PUT");
182
    }
183

  
184
    /** Handle "HEAD" method requests from HTTP clients */
185
    @Override
186
    protected void doHead(HttpServletRequest request,
187
            HttpServletResponse response) throws ServletException, IOException {
188
        logMetacat.debug("HTTP Verb: HEAD");
189
    }
190
}
0 191

  

Also available in: Unified diff