4
|
1 /**
|
|
2 *
|
|
3 */
|
|
4 package de.mpiwg.itgroup.annotations.neo4j;
|
|
5
|
|
6 import java.util.ArrayList;
|
|
7 import java.util.Calendar;
|
|
8 import java.util.List;
|
|
9
|
|
10 import org.apache.log4j.Logger;
|
5
|
11 import org.neo4j.graphdb.Direction;
|
4
|
12 import org.neo4j.graphdb.GraphDatabaseService;
|
|
13 import org.neo4j.graphdb.Node;
|
|
14 import org.neo4j.graphdb.Relationship;
|
|
15 import org.neo4j.graphdb.RelationshipType;
|
|
16 import org.neo4j.graphdb.Transaction;
|
|
17 import org.neo4j.graphdb.index.Index;
|
|
18 import org.neo4j.graphdb.index.IndexHits;
|
|
19
|
9
|
20 import de.mpiwg.itgroup.annotations.Actor;
|
4
|
21 import de.mpiwg.itgroup.annotations.Annotation;
|
|
22 import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes;
|
10
|
23 import de.mpiwg.itgroup.annotations.Person;
|
4
|
24
|
|
25 /**
|
|
26 * @author casties
|
|
27 *
|
|
28 */
|
|
29 public class AnnotationStore {
|
|
30
|
6
|
31 protected static Logger logger = Logger.getLogger(AnnotationStore.class);
|
4
|
32
|
6
|
33 protected GraphDatabaseService graphDb;
|
|
34
|
|
35 public static enum NodeTypes {
|
|
36 ANNOTATION, PERSON, TARGET
|
|
37 }
|
4
|
38
|
6
|
39 protected List<Index<Node>> nodeIndexes;
|
|
40
|
|
41 public static enum RelationTypes implements RelationshipType {
|
8
|
42 ANNOTATES, CREATED, PERMITS
|
6
|
43 }
|
|
44
|
|
45 public static String ANNOTATION_URI_BASE = "http://entities.mpiwg-berlin.mpg.de/annotations/";
|
4
|
46
|
6
|
47 public AnnotationStore(GraphDatabaseService graphDb) {
|
|
48 super();
|
|
49 this.graphDb = graphDb;
|
|
50 nodeIndexes = new ArrayList<Index<Node>>(3);
|
|
51 // List.set(enum.ordinal(), val) seems not to work.
|
|
52 nodeIndexes.add(NodeTypes.ANNOTATION.ordinal(), graphDb.index().forNodes("annotations"));
|
|
53 nodeIndexes.add(NodeTypes.PERSON.ordinal(), graphDb.index().forNodes("persons"));
|
|
54 nodeIndexes.add(NodeTypes.TARGET.ordinal(), graphDb.index().forNodes("targets"));
|
|
55 }
|
4
|
56
|
6
|
57 protected Index<Node> getNodeIndex(NodeTypes type) {
|
|
58 return nodeIndexes.get(type.ordinal());
|
|
59 }
|
|
60
|
|
61 /**
|
|
62 * Returns the Annotation with the given id.
|
|
63 *
|
|
64 * @param id
|
|
65 * @return
|
|
66 */
|
|
67 public Annotation getAnnotationById(String id) {
|
|
68 Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle();
|
8
|
69 Annotation annot = createAnnotationFromNode(annotNode);
|
6
|
70 return annot;
|
|
71 }
|
|
72
|
|
73 /**
|
|
74 * Returns an Annotation object from an annotation-Node.
|
|
75 *
|
|
76 * @param annotNode
|
|
77 * @return
|
|
78 */
|
8
|
79 public Annotation createAnnotationFromNode(Node annotNode) {
|
6
|
80 Annotation annot = new Annotation();
|
|
81 annot.setUri((String) annotNode.getProperty("id", null));
|
|
82 annot.setBodyText((String) annotNode.getProperty("bodyText", null));
|
|
83 annot.setBodyUri((String) annotNode.getProperty("bodyUri", null));
|
|
84 // get annotation target from relation
|
|
85 Iterable<Relationship> targetRels = annotNode
|
|
86 .getRelationships(RelationTypes.ANNOTATES);
|
|
87 for (Relationship targetRel : targetRels) {
|
|
88 Node target = targetRel.getEndNode();
|
|
89 annot.setTargetBaseUri((String) target.getProperty("uri", null));
|
|
90 // just the first one
|
|
91 break;
|
|
92 }
|
|
93 annot.setTargetFragment((String) annotNode.getProperty(
|
|
94 "targetFragment", null));
|
|
95 String ft = (String) annotNode.getProperty("fragmentType", null);
|
|
96 if (ft != null) {
|
|
97 annot.setFragmentType(FragmentTypes.valueOf(ft));
|
|
98 }
|
9
|
99 // get creator from relation
|
6
|
100 Iterable<Relationship> creatorRels = annotNode
|
|
101 .getRelationships(RelationTypes.CREATED);
|
|
102 for (Relationship creatorRel : creatorRels) {
|
9
|
103 Node creatorNode = creatorRel.getStartNode();
|
|
104 String uri = (String) creatorNode.getProperty("uri", null);
|
|
105 String name = (String) creatorNode.getProperty("name", null);
|
10
|
106 Actor creator = new Person(uri, name);
|
9
|
107 annot.setCreator(creator);
|
6
|
108 // just the first one
|
|
109 break;
|
|
110 }
|
|
111 annot.setCreated((String) annotNode.getProperty("created", null));
|
|
112 return annot;
|
|
113 }
|
4
|
114
|
6
|
115 /**
|
|
116 * Store a new annotation in the store or update an existing one. Returns
|
|
117 * the stored annotation.
|
|
118 *
|
|
119 * @param annot
|
|
120 * @return
|
|
121 */
|
|
122 public Annotation storeAnnotation(Annotation annot) {
|
|
123 Node annotNode = null;
|
|
124 Transaction tx = graphDb.beginTx();
|
|
125 try {
|
|
126 /*
|
|
127 * create or get the annotation
|
|
128 */
|
|
129 String id = annot.getUri();
|
|
130 if (id == null) {
|
|
131 id = createRessourceURI("annot:");
|
|
132 }
|
|
133 annotNode = getOrCreateAnnotationNode(id);
|
4
|
134
|
6
|
135 /*
|
|
136 * the annotation body
|
|
137 */
|
|
138 String bodyText = annot.getBodyText();
|
|
139 if (bodyText != null) {
|
|
140 annotNode.setProperty("bodyText", bodyText);
|
|
141 }
|
|
142 String bodyUri = annot.getBodyUri();
|
|
143 if (bodyUri != null) {
|
|
144 annotNode.setProperty("bodyUri", bodyUri);
|
|
145 }
|
4
|
146
|
6
|
147 /*
|
|
148 * the annotation target
|
|
149 */
|
|
150 String targetBaseUri = annot.getTargetBaseUri();
|
|
151 if (targetBaseUri != null) {
|
|
152 Node target = getOrCreateTargetNode(targetBaseUri);
|
|
153 getOrCreateRelation(annotNode, RelationTypes.ANNOTATES, target);
|
|
154 }
|
4
|
155
|
6
|
156 /*
|
|
157 * The fragment part of the annotation target.
|
|
158 */
|
|
159 String targetFragment = annot.getTargetFragment();
|
|
160 FragmentTypes fragmentType = annot.getFragmentType();
|
|
161 if (targetFragment != null) {
|
|
162 annotNode.setProperty("targetFragment", targetFragment);
|
|
163 annotNode.setProperty("fragmentType", fragmentType.name());
|
|
164 }
|
4
|
165
|
6
|
166 /*
|
10
|
167 * The creator of this annotation.
|
6
|
168 */
|
10
|
169 Actor creator = annot.getCreator();
|
|
170 if (creator != null) {
|
|
171 Node creatorNode = getOrCreatePersonNode(creator);
|
|
172 getOrCreateRelation(creatorNode, RelationTypes.CREATED, annotNode);
|
6
|
173 }
|
4
|
174
|
6
|
175 /*
|
|
176 * The creation date of this annotation.
|
|
177 */
|
|
178 String created = annot.getCreated();
|
|
179 if (created != null) {
|
|
180 annotNode.setProperty("created", created);
|
|
181 }
|
4
|
182
|
6
|
183 tx.success();
|
|
184 } finally {
|
|
185 tx.finish();
|
|
186 }
|
|
187
|
|
188 // re-read and return annotation
|
8
|
189 Annotation storedAnnot = createAnnotationFromNode(annotNode);
|
6
|
190 return storedAnnot;
|
|
191 }
|
4
|
192
|
6
|
193 /**
|
|
194 * Deletes the annotation with the given id.
|
|
195 *
|
|
196 * @param id
|
|
197 */
|
|
198 public void deleteById(String id) {
|
|
199 Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle();
|
|
200 if (annotNode != null) {
|
|
201 // delete related objects
|
|
202 Transaction tx = graphDb.beginTx();
|
|
203 try {
|
|
204 for (Relationship rel : annotNode.getRelationships()) {
|
|
205 // delete relation and the related node if it has no other relations
|
|
206 Node other = rel.getOtherNode(annotNode);
|
|
207 rel.delete();
|
|
208 if (! other.hasRelationship()) {
|
|
209 deleteNode(other);
|
|
210 }
|
|
211 }
|
|
212 if (! annotNode.hasRelationship()) {
|
|
213 deleteNode(annotNode);
|
|
214 } else {
|
|
215 logger.error("deleteById: unable to delete: Node still has relations.");
|
|
216 }
|
|
217 tx.success();
|
|
218 } finally {
|
|
219 tx.finish();
|
|
220 }
|
|
221 }
|
|
222 }
|
4
|
223
|
6
|
224 /**
|
|
225 * Returns all annotations with the given uri and/or user.
|
|
226 *
|
|
227 * @param uri
|
|
228 * @param userUri
|
|
229 * @param limit
|
|
230 * @param offset
|
|
231 * @return
|
|
232 */
|
|
233 public List<Annotation> searchByUriUser(String targetUri, String userUri,
|
|
234 String limit, String offset) {
|
|
235 List<Annotation> annotations = new ArrayList<Annotation>();
|
|
236 if (targetUri != null) {
|
|
237 // there should be only one
|
|
238 Node target = getNodeIndex(NodeTypes.TARGET).get("uri", targetUri).getSingle();
|
|
239 if (target != null) {
|
|
240 Iterable<Relationship> relations = target
|
|
241 .getRelationships(RelationTypes.ANNOTATES);
|
|
242 for (Relationship relation : relations) {
|
|
243 Node ann = relation.getStartNode();
|
|
244 if (ann.getProperty("TYPE", "").equals("ANNOTATION")) {
|
8
|
245 Annotation annot = createAnnotationFromNode(ann);
|
6
|
246 annotations.add(annot);
|
|
247 } else {
|
|
248 logger.error("ANNOTATES relation does not start with ANNOTATION: "
|
|
249 + ann);
|
|
250 }
|
|
251 }
|
|
252 }
|
|
253 }
|
|
254 if (userUri != null) {
|
|
255 // there should be only one
|
|
256 Node person = getNodeIndex(NodeTypes.PERSON).get("uri", userUri).getSingle();
|
|
257 if (person != null) {
|
|
258 Iterable<Relationship> relations = person
|
|
259 .getRelationships(RelationTypes.CREATED);
|
|
260 for (Relationship relation : relations) {
|
|
261 Node ann = relation.getEndNode();
|
|
262 if (ann.getProperty("TYPE", "").equals("ANNOTATION")) {
|
8
|
263 Annotation annot = createAnnotationFromNode(ann);
|
6
|
264 annotations.add(annot);
|
|
265 } else {
|
|
266 logger.error("CREATED relation does not end with ANNOTATION: "
|
|
267 + ann);
|
|
268 }
|
|
269 }
|
|
270 }
|
|
271 }
|
|
272 return annotations;
|
|
273 }
|
5
|
274
|
6
|
275 protected Relationship getOrCreateRelation(Node start,
|
|
276 RelationshipType type, Node end) {
|
|
277 if (start.hasRelationship()) {
|
|
278 // there are relations
|
|
279 Iterable<Relationship> rels = start.getRelationships(type,
|
|
280 Direction.OUTGOING);
|
|
281 for (Relationship rel : rels) {
|
|
282 if (rel.getEndNode().equals(end)) {
|
|
283 // relation exists
|
|
284 return rel;
|
|
285 }
|
|
286 }
|
|
287 }
|
|
288 // create new one
|
|
289 Relationship rel;
|
|
290 Transaction tx = graphDb.beginTx();
|
|
291 try {
|
|
292 rel = start.createRelationshipTo(end, type);
|
|
293 tx.success();
|
|
294 } finally {
|
|
295 tx.finish();
|
|
296 }
|
|
297 return rel;
|
|
298 }
|
|
299
|
|
300 protected Node getOrCreateAnnotationNode(String id) {
|
|
301 Index<Node> idx = getNodeIndex(NodeTypes.ANNOTATION);
|
|
302 IndexHits<Node> annotations = idx.get("id", id);
|
|
303 Node annotation = annotations.getSingle();
|
|
304 if (annotation == null) {
|
|
305 // does not exist yet
|
|
306 Transaction tx = graphDb.beginTx();
|
|
307 try {
|
|
308 annotation = graphDb.createNode();
|
|
309 annotation.setProperty("TYPE", NodeTypes.ANNOTATION.name());
|
|
310 annotation.setProperty("id", id);
|
|
311 idx.add(annotation, "id", id);
|
|
312 tx.success();
|
|
313 } finally {
|
|
314 tx.finish();
|
|
315 }
|
|
316 }
|
|
317 return annotation;
|
|
318 }
|
4
|
319
|
6
|
320 protected Node getOrCreateTargetNode(String uri) {
|
|
321 Index<Node> idx = getNodeIndex(NodeTypes.TARGET);
|
|
322 IndexHits<Node> targets = idx.get("uri", uri);
|
|
323 Node target = targets.getSingle();
|
|
324 if (target == null) {
|
|
325 // does not exist yet
|
|
326 Transaction tx = graphDb.beginTx();
|
|
327 try {
|
|
328 target = graphDb.createNode();
|
|
329 target.setProperty("TYPE", NodeTypes.TARGET.name());
|
|
330 target.setProperty("uri", uri);
|
|
331 idx.add(target, "uri", uri);
|
|
332 tx.success();
|
|
333 } finally {
|
|
334 tx.finish();
|
|
335 }
|
|
336 }
|
|
337 return target;
|
|
338 }
|
4
|
339
|
10
|
340 protected Node getOrCreatePersonNode(Actor actor) {
|
|
341 /*
|
6
|
342 // Person is identified by URI
|
|
343 Index<Node> idx = getNodeIndex(NodeTypes.PERSON);
|
|
344 IndexHits<Node> persons = idx.get("uri", uri);
|
|
345 Node person = persons.getSingle();
|
|
346 if (person == null) {
|
|
347 // does not exist yet
|
|
348 Transaction tx = graphDb.beginTx();
|
|
349 try {
|
|
350 person = graphDb.createNode();
|
|
351 person.setProperty("TYPE", NodeTypes.PERSON.name());
|
|
352 person.setProperty("uri", uri);
|
|
353 idx.add(person, "uri", uri);
|
|
354 if (name != null) {
|
|
355 person.setProperty("name", name);
|
|
356 }
|
|
357 tx.success();
|
|
358 } finally {
|
|
359 tx.finish();
|
|
360 }
|
|
361 }
|
|
362 return person;
|
10
|
363 */
|
|
364 return null;
|
6
|
365 }
|
4
|
366
|
6
|
367 /**
|
|
368 * Unindexes and deletes given Node if it has no relations.
|
|
369 * @param node
|
|
370 */
|
|
371 protected void deleteNode(Node node) {
|
|
372 Transaction tx = graphDb.beginTx();
|
|
373 try {
|
|
374 if (node.hasRelationship()) {
|
|
375 logger.error("deleteNode: unable to delete: Node still has relations.");
|
|
376 } else {
|
|
377 String ts = (String) node.getProperty("TYPE", null);
|
|
378 try {
|
|
379 NodeTypes type = NodeTypes.valueOf(ts);
|
|
380 getNodeIndex(type).remove(node);
|
|
381 } catch (Exception e) {
|
|
382 logger.error("deleteNode: unable to get TYPE of node: "+node);
|
|
383 }
|
|
384 node.delete();
|
|
385 }
|
|
386 tx.success();
|
|
387 } finally {
|
|
388 tx.finish();
|
|
389 }
|
|
390 }
|
|
391
|
|
392 /**
|
|
393 * Erzeuge eine urn aus der aktuellen Zeit in millis
|
|
394 *
|
|
395 * @return
|
|
396 */
|
|
397 private String createRessourceURI(String prefix) {
|
4
|
398
|
6
|
399 Calendar cal = Calendar.getInstance();
|
4
|
400
|
6
|
401 long time = cal.getTimeInMillis();
|
4
|
402
|
6
|
403 return String.format("%s%s%s", ANNOTATION_URI_BASE, prefix, time);
|
4
|
404
|
6
|
405 }
|
4
|
406
|
|
407 }
|