source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/neo4j/AnnotationStore.java @ 18:aafa3884b2c4

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

new AnnotationStore? restlet for HTML-UI.
reorganisation of classes.

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