source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java @ 19:f0f55ab768c9

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

more work on HTML UI.

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