source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java @ 15:58357a4b86de

Last change on this file since 15:58357a4b86de was 15:58357a4b86de, checked in by casties, 12 years ago

ASSIGNED - # 249: Annotations shared in groups
https://it-dev.mpiwg-berlin.mpg.de/tracs/mpdl-project-software/ticket/249

File size: 19.6 KB
Line 
1/**
2 *
3 */
4package de.mpiwg.itgroup.annotations.neo4j;
5
6import java.util.ArrayList;
7import java.util.Calendar;
8import java.util.List;
9
10import org.apache.log4j.Logger;
11import org.neo4j.graphdb.Direction;
12import org.neo4j.graphdb.GraphDatabaseService;
13import org.neo4j.graphdb.Node;
14import org.neo4j.graphdb.Relationship;
15import org.neo4j.graphdb.RelationshipType;
16import org.neo4j.graphdb.Transaction;
17import org.neo4j.graphdb.index.Index;
18import org.neo4j.graphdb.index.IndexHits;
19
20import de.mpiwg.itgroup.annotations.Actor;
21import de.mpiwg.itgroup.annotations.Annotation;
22import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes;
23import de.mpiwg.itgroup.annotations.Group;
24import de.mpiwg.itgroup.annotations.Person;
25
26/**
27 * @author casties
28 *
29 */
30public class AnnotationStore {
31
32    protected static Logger logger = Logger.getLogger(AnnotationStore.class);
33
34    protected GraphDatabaseService graphDb;
35
36    public static enum NodeTypes {
37        ANNOTATION, PERSON, TARGET, GROUP
38    }
39
40    protected List<Index<Node>> nodeIndexes;
41
42    public static enum RelationTypes implements RelationshipType {
43        ANNOTATES, CREATED, PERMITS_ADMIN, PERMITS_DELETE, PERMITS_UPDATE, PERMITS_READ, MEMBER_OF
44    }
45
46    public static String ANNOTATION_URI_BASE = "http://entities.mpiwg-berlin.mpg.de/annotations/";
47
48    public AnnotationStore(GraphDatabaseService graphDb) {
49        super();
50        this.graphDb = graphDb;
51        nodeIndexes = new ArrayList<Index<Node>>(3);
52        // List.set(enum.ordinal(), val) seems not to work.
53        nodeIndexes.add(NodeTypes.ANNOTATION.ordinal(), graphDb.index().forNodes("annotations"));
54        nodeIndexes.add(NodeTypes.PERSON.ordinal(), graphDb.index().forNodes("persons"));
55        nodeIndexes.add(NodeTypes.TARGET.ordinal(), graphDb.index().forNodes("targets"));
56        nodeIndexes.add(NodeTypes.GROUP.ordinal(), graphDb.index().forNodes("groups"));
57    }
58
59    protected Index<Node> getNodeIndex(NodeTypes type) {
60        return nodeIndexes.get(type.ordinal());
61    }
62
63    /**
64     * @param userUri
65     * @return
66     */
67    public Node getPersonNodeByUri(String userUri) {
68        if (userUri == null) return null;
69        Node person = getNodeIndex(NodeTypes.PERSON).get("uri", userUri).getSingle();
70        return person;
71    }
72
73   
74    /**
75     * Returns List of Groups the person is member of.
76     *
77     * @param person
78     * @return
79     */
80    public List<Group> getGroupsForPersonNode(Node person) {
81        ArrayList<Group> groups = new ArrayList<Group>();
82        Iterable<Relationship> rels = person.getRelationships(RelationTypes.MEMBER_OF);
83        for (Relationship rel : rels) {
84            Node groupNode = rel.getEndNode();
85            Actor group = createActorFromNode(groupNode);
86            // make sure we're getting a group
87            if (!(group instanceof Group)) {
88                logger.error("target of MEMBER_OF is not GROUP! rel="+rel);
89                continue;
90            }
91            groups.add((Group) group);
92        }
93        return groups;
94    }
95   
96    /**
97     * Returns if person with uri is in Group group.
98     *
99     * @param person
100     * @param group
101     * @return
102     */
103    public boolean isPersonInGroup(Person person, Group group) {
104        Node pn = getPersonNodeByUri(person.getUriString());
105        if (pn == null) return false;
106        // optimised version of getGroupsForPersonNode
107        Iterable<Relationship> rels = pn.getRelationships(RelationTypes.MEMBER_OF);
108        for (Relationship rel : rels) {
109            Node gn = rel.getEndNode();
110            if (gn.getProperty("uri", "").equals(group.getUriString()) || gn.getProperty("id", "").equals(group.getId())) {
111                return true;
112            }
113        }
114        return false;
115    }
116   
117    /**
118     * Returns the Annotation with the given id.
119     *
120     * @param id
121     * @return
122     */
123    public Annotation getAnnotationById(String id) {
124        Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle();
125        Annotation annot = createAnnotationFromNode(annotNode);
126        return annot;
127    }
128
129    /**
130     * Returns an Annotation object from an annotation-Node.
131     *
132     * @param annotNode
133     * @return
134     */
135    public Annotation createAnnotationFromNode(Node annotNode) {
136        Annotation annot = new Annotation();
137        annot.setUri((String) annotNode.getProperty("id", null));
138        annot.setBodyText((String) annotNode.getProperty("bodyText", null));
139        annot.setBodyUri((String) annotNode.getProperty("bodyUri", null));
140        // get annotation target from relation
141        Relationship targetRel = getRelation(annotNode, RelationTypes.ANNOTATES, null);
142        if (targetRel != null) {
143            Node target = targetRel.getEndNode();
144            annot.setTargetBaseUri((String) target.getProperty("uri", null));
145        } else {
146            logger.error("annotation " + annotNode + " has no target node!");
147        }
148        annot.setTargetFragment((String) annotNode.getProperty("targetFragment", null));
149        String ft = (String) annotNode.getProperty("fragmentType", null);
150        if (ft != null) {
151            annot.setFragmentType(FragmentTypes.valueOf(ft));
152        }
153        // get creator from relation
154        Relationship creatorRel = getRelation(annotNode, RelationTypes.CREATED, null);
155        if (creatorRel != null) {
156            Node creatorNode = creatorRel.getStartNode();
157            Actor creator = createActorFromNode(creatorNode);
158            annot.setCreator(creator);
159        } else {
160            logger.error("annotation " + annotNode + " has no creator node!");
161        }
162        // get creation date
163        annot.setCreated((String) annotNode.getProperty("created", null));
164        // get permissions
165        Relationship adminRel = getRelation(annotNode, RelationTypes.PERMITS_ADMIN, null);
166        if (adminRel != null) {
167            Node adminNode = adminRel.getEndNode();
168            Actor admin = createActorFromNode(adminNode);
169            annot.setAdminPermission(admin);
170        }
171        Relationship deleteRel = getRelation(annotNode, RelationTypes.PERMITS_DELETE, null);
172        if (deleteRel != null) {
173            Node deleteNode = deleteRel.getEndNode();
174            Actor delete = createActorFromNode(deleteNode);
175            annot.setDeletePermission(delete);
176        }
177        Relationship updateRel = getRelation(annotNode, RelationTypes.PERMITS_UPDATE, null);
178        if (updateRel != null) {
179            Node updateNode = updateRel.getEndNode();
180            Actor update = createActorFromNode(updateNode);
181            annot.setUpdatePermission(update);
182        }
183        Relationship readRel = getRelation(annotNode, RelationTypes.PERMITS_READ, null);
184        if (readRel != null) {
185            Node readNode = readRel.getEndNode();
186            Actor read = createActorFromNode(readNode);
187            annot.setReadPermission(read);
188        }
189
190        return annot;
191    }
192
193    /**
194     * Returns an Actor object from a node.
195     *
196     * @param actorNode
197     * @return
198     */
199    protected Actor createActorFromNode(Node actorNode) {
200        String id = (String) actorNode.getProperty("id", null);
201        String uri = (String) actorNode.getProperty("uri", null);
202        String name = (String) actorNode.getProperty("name", null);
203        String type = (String) actorNode.getProperty("TYPE", null);
204        if (type != null && type.equals("PERSON")) {
205            return new Person(id, uri, name);
206        } else if (type != null && type.equals("GROUP")) {
207            return new Group(id, uri, name);
208        }
209        return null;
210    }
211
212    /**
213     * Store a new annotation in the store or update an existing one. Returns
214     * the stored annotation.
215     *
216     * @param annot
217     * @return
218     */
219    public Annotation storeAnnotation(Annotation annot) {
220        Node annotNode = null;
221        Transaction tx = graphDb.beginTx();
222        try {
223            /*
224             * create or get the annotation
225             */
226            String id = annot.getUri();
227            if (id == null) {
228                id = createRessourceURI("annot:");
229            }
230            annotNode = getOrCreateAnnotationNode(id);
231
232            /*
233             * the annotation body
234             */
235            String bodyText = annot.getBodyText();
236            if (bodyText != null) {
237                annotNode.setProperty("bodyText", bodyText);
238            }
239            String bodyUri = annot.getBodyUri();
240            if (bodyUri != null) {
241                annotNode.setProperty("bodyUri", bodyUri);
242            }
243
244            /*
245             * the annotation target
246             */
247            String targetBaseUri = annot.getTargetBaseUri();
248            if (targetBaseUri != null) {
249                Node target = getOrCreateTargetNode(targetBaseUri);
250                getOrCreateRelation(annotNode, RelationTypes.ANNOTATES, target);
251            }
252
253            /*
254             * The fragment part of the annotation target.
255             */
256            String targetFragment = annot.getTargetFragment();
257            FragmentTypes fragmentType = annot.getFragmentType();
258            if (targetFragment != null) {
259                annotNode.setProperty("targetFragment", targetFragment);
260                annotNode.setProperty("fragmentType", fragmentType.name());
261            }
262
263            /*
264             * The creator of this annotation.
265             */
266            Actor creator = annot.getCreator();
267            if (creator != null) {
268                Node creatorNode = getOrCreateActorNode(creator);
269                getOrCreateRelation(creatorNode, RelationTypes.CREATED, annotNode);
270            }
271
272            /*
273             * The creation date of this annotation.
274             */
275            String created = annot.getCreated();
276            if (created != null) {
277                annotNode.setProperty("created", created);
278            }
279
280            /*
281             * Permissions for this annotation.
282             */
283            setPermissionRelation(annotNode, RelationTypes.PERMITS_ADMIN, annot.getAdminPermission());
284            setPermissionRelation(annotNode, RelationTypes.PERMITS_DELETE, annot.getDeletePermission());
285            setPermissionRelation(annotNode, RelationTypes.PERMITS_UPDATE, annot.getUpdatePermission());
286            setPermissionRelation(annotNode, RelationTypes.PERMITS_READ, annot.getReadPermission());
287
288            tx.success();
289        } finally {
290            tx.finish();
291        }
292
293        // re-read and return annotation
294        Annotation storedAnnot = createAnnotationFromNode(annotNode);
295        return storedAnnot;
296    }
297
298    /**
299     * Deletes the annotation with the given id.
300     *
301     * @param id
302     */
303    public void deleteById(String id) {
304        Node annotNode = getNodeIndex(NodeTypes.ANNOTATION).get("id", id).getSingle();
305        if (annotNode != null) {
306            // delete related objects
307            Transaction tx = graphDb.beginTx();
308            try {
309                for (Relationship rel : annotNode.getRelationships()) {
310                    // delete relation and the related node if it has no other
311                    // relations
312                    Node other = rel.getOtherNode(annotNode);
313                    rel.delete();
314                    if (!other.hasRelationship()) {
315                        deleteNode(other);
316                    }
317                }
318                if (!annotNode.hasRelationship()) {
319                    deleteNode(annotNode);
320                } else {
321                    logger.error("deleteById: unable to delete: Node still has relations.");
322                }
323                tx.success();
324            } finally {
325                tx.finish();
326            }
327        }
328    }
329
330    /**
331     * Returns all annotations with the given uri and/or user.
332     *
333     * @param uri
334     * @param userUri
335     * @param limit
336     * @param offset
337     * @return
338     */
339    public List<Annotation> searchByUriUser(String targetUri, String userUri, String limit, String offset) {
340        List<Annotation> annotations = new ArrayList<Annotation>();
341        if (targetUri != null) {
342            // there should be only one
343            Node target = getNodeIndex(NodeTypes.TARGET).get("uri", targetUri).getSingle();
344            if (target != null) {
345                Iterable<Relationship> relations = target.getRelationships(RelationTypes.ANNOTATES);
346                for (Relationship relation : relations) {
347                    Node ann = relation.getStartNode();
348                    if (ann.getProperty("TYPE", "").equals("ANNOTATION")) {
349                        Annotation annot = createAnnotationFromNode(ann);
350                        annotations.add(annot);
351                    } else {
352                        logger.error("ANNOTATES relation does not start with ANNOTATION: " + ann);
353                    }
354                }
355            }
356        }
357        if (userUri != null) {
358            // there should be only one
359            Node person = getPersonNodeByUri(userUri);
360            if (person != null) {
361                Iterable<Relationship> relations = person.getRelationships(RelationTypes.CREATED);
362                for (Relationship relation : relations) {
363                    Node ann = relation.getEndNode();
364                    if (ann.getProperty("TYPE", "").equals("ANNOTATION")) {
365                        Annotation annot = createAnnotationFromNode(ann);
366                        annotations.add(annot);
367                    } else {
368                        logger.error("CREATED relation does not end with ANNOTATION: " + ann);
369                    }
370                }
371            }
372        }
373        return annotations;
374    }
375
376    protected Relationship getOrCreateRelation(Node start, RelationshipType type, Node end) {
377        if (start.hasRelationship()) {
378            // there are relations
379            Iterable<Relationship> rels = start.getRelationships(type, Direction.OUTGOING);
380            for (Relationship rel : rels) {
381                if (rel.getEndNode().equals(end)) {
382                    // relation exists
383                    return rel;
384                }
385            }
386        }
387        // create new one
388        Relationship rel;
389        Transaction tx = graphDb.beginTx();
390        try {
391            rel = start.createRelationshipTo(end, type);
392            tx.success();
393        } finally {
394            tx.finish();
395        }
396        return rel;
397    }
398
399    protected Node getOrCreateAnnotationNode(String id) {
400        Index<Node> idx = getNodeIndex(NodeTypes.ANNOTATION);
401        IndexHits<Node> annotations = idx.get("id", id);
402        Node annotation = annotations.getSingle();
403        if (annotation == null) {
404            // does not exist yet
405            Transaction tx = graphDb.beginTx();
406            try {
407                annotation = graphDb.createNode();
408                annotation.setProperty("TYPE", NodeTypes.ANNOTATION.name());
409                annotation.setProperty("id", id);
410                idx.add(annotation, "id", id);
411                tx.success();
412            } finally {
413                tx.finish();
414            }
415        }
416        return annotation;
417    }
418
419    protected Node getOrCreateTargetNode(String uri) {
420        Index<Node> idx = getNodeIndex(NodeTypes.TARGET);
421        IndexHits<Node> targets = idx.get("uri", uri);
422        Node target = targets.getSingle();
423        if (target == null) {
424            // does not exist yet
425            Transaction tx = graphDb.beginTx();
426            try {
427                target = graphDb.createNode();
428                target.setProperty("TYPE", NodeTypes.TARGET.name());
429                target.setProperty("uri", uri);
430                idx.add(target, "uri", uri);
431                tx.success();
432            } finally {
433                tx.finish();
434            }
435        }
436        return target;
437    }
438
439    protected Node getOrCreateActorNode(Actor actor) {
440        // Person/Group is identified by URI or id
441        String uri = actor.getUriString();
442        String name = actor.getName();
443        String id = actor.getId();
444        Index<Node> idx;
445        if (actor.isGroup()) {
446            idx = getNodeIndex(NodeTypes.GROUP);
447        } else {
448            idx = getNodeIndex(NodeTypes.PERSON);
449        }
450        IndexHits<Node> persons = idx.get("uri", uri);
451        Node person = persons.getSingle();
452        if (person == null) {
453            // does not exist yet
454            Transaction tx = graphDb.beginTx();
455            try {
456                person = graphDb.createNode();
457                if (actor.isGroup()) {
458                    person.setProperty("TYPE", NodeTypes.GROUP.name());
459                } else {
460                    person.setProperty("TYPE", NodeTypes.PERSON.name());                   
461                }
462                person.setProperty("uri", uri);
463                idx.add(person, "uri", uri);
464                if (name != null) {
465                    person.setProperty("name", name);
466                }
467                if (id != null) {
468                    person.setProperty("id", id);
469                }
470                tx.success();
471            } finally {
472                tx.finish();
473            }
474        }
475        return person;
476    }
477
478    /**
479     * Create or update permissions relations for an annotation.
480     *
481     * @param annotNode
482     * @param type
483     * @param annot
484     */
485    protected void setPermissionRelation(Node annotNode, RelationTypes type, Actor actor) {
486        Node newActorNode = null;
487        if (actor != null) {
488            newActorNode = getOrCreateActorNode(actor);
489        }
490        Relationship rel = getRelation(annotNode, type, null);
491        if (rel != null) {
492            // relation exists
493            Node oldActorNode = rel.getEndNode();
494            if (!oldActorNode.equals(newActorNode)) {
495                // new admin is different
496                rel.delete();
497                if (newActorNode != null) {
498                    rel = getOrCreateRelation(annotNode, type, newActorNode);
499                }
500            }
501        } else {
502            // no relation yet
503            if (newActorNode != null) {
504                rel = getOrCreateRelation(annotNode, type, newActorNode);
505            }
506        }
507    }
508
509    /**
510     * Unindexes and deletes given Node if it has no relations.
511     *
512     * @param node
513     */
514    protected void deleteNode(Node node) {
515        Transaction tx = graphDb.beginTx();
516        try {
517            if (node.hasRelationship()) {
518                logger.error("deleteNode: unable to delete: Node still has relations.");
519            } else {
520                String ts = (String) node.getProperty("TYPE", null);
521                try {
522                    NodeTypes type = NodeTypes.valueOf(ts);
523                    getNodeIndex(type).remove(node);
524                } catch (Exception e) {
525                    logger.error("deleteNode: unable to get TYPE of node: " + node);
526                }
527                node.delete();
528            }
529            tx.success();
530        } finally {
531            tx.finish();
532        }
533    }
534
535    /** returns the (first) Relationship of RelationTypes type from Node start.
536     * 
537     * @param start
538     * @param type
539     * @param direction
540     * @return
541     */
542    protected Relationship getRelation(Node start, RelationTypes type, Direction direction) {
543        Iterable<Relationship> rels;
544        if (direction == null) {
545            // ignore direction
546            rels = start.getRelationships(type);
547        } else {
548            rels = start.getRelationships(type, direction);
549        }
550        for (Relationship rel : rels) {
551            return rel;
552        }
553        return null;
554    }
555
556    /**
557     * Erzeuge eine urn aus der aktuellen Zeit in millis
558     *
559     * @return
560     */
561    private String createRessourceURI(String prefix) {
562
563        Calendar cal = Calendar.getInstance();
564
565        long time = cal.getTimeInMillis();
566
567        return String.format("%s%s%s", ANNOTATION_URI_BASE, prefix, time);
568
569    }
570
571}
Note: See TracBrowser for help on using the repository browser.