source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java @ 16:794077e6288c

Last change on this file since 16:794077e6288c was 16:794077e6288c, checked in by casties, 12 years ago

CLOSED - # 252: Tags for Annotations
https://it-dev.mpiwg-berlin.mpg.de/tracs/mpdl-project-software/ticket/252

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