All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.interedition.text.neo4j.Neo4jTextRepository Maven / Gradle / Ivy

The newest version!
package eu.interedition.text.neo4j;

import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.CharStreams;
import eu.interedition.text.Anchor;
import eu.interedition.text.Layer;
import eu.interedition.text.Name;
import eu.interedition.text.Query;
import eu.interedition.text.QueryResult;
import eu.interedition.text.TextRange;
import eu.interedition.text.TextRepository;
import eu.interedition.text.simple.SimpleLayer;
import eu.interedition.text.util.BatchLayerAdditionSupport;
import eu.interedition.text.util.UpdateSupport;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.RelationshipIndex;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.index.lucene.QueryContext;
import org.neo4j.index.lucene.ValueContext;
import org.neo4j.kernel.Traversal;
import org.neo4j.kernel.Uniqueness;

import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import static eu.interedition.text.neo4j.LayerNode.Relationships.ANCHORS;
import static eu.interedition.text.neo4j.LayerNode.Relationships.HAS_TEXT;

/**
 * @author Gregor Middell
 */
public class Neo4jTextRepository implements TextRepository, UpdateSupport, BatchLayerAdditionSupport {
    private final Logger LOG = Logger.getLogger(getClass().getName());

    private final Neo4jIndexQuery indexQuery = new Neo4jIndexQuery();
    private final Class dataType;
    private final DataNodeMapper dataNodeMapper;
    private final GraphDatabaseService db;
    private final boolean transactional;
    private Node base;

    public Neo4jTextRepository(Class dataType, DataNodeMapper dataNodeMapper, GraphDatabaseService db, boolean transactional) {
        this.dataType = dataType;
        this.dataNodeMapper = dataNodeMapper;
        this.db = db;
        this.transactional = transactional;
    }

    @Override
    public Layer findByIdentifier(long id) {
        try {
            return new LayerNode(this, db.getNodeById(id));
        } catch (NotFoundException e) {
            return null;
        }
    }

    @Override
    public QueryResult query(final Query query) {
        final Transaction tx = begin();
        return new QueryResult() {
            @Override
            public void close() throws IOException {
                commit(tx);
            }

            @Override
            public Iterator> iterator() {
                final org.apache.lucene.search.Query luceneQuery = indexQuery.build(query);
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.log(Level.FINE, "Lucene Query: {0}", luceneQuery);
                }
                final Iterator relationships = index().query(
                        new QueryContext(luceneQuery).sort(new Sort(new SortField("id", SortField.LONG)))
                ).iterator();

                return new AbstractIterator>() {
                    long last = 0;

                    @Override
                    protected Layer computeNext() {
                        while (relationships.hasNext()) {
                            final Node layerNode = relationships.next().getStartNode();
                            if (layerNode.getId() != last) {
                                last = layerNode.getId();
                                return new LayerNode(Neo4jTextRepository.this, layerNode);
                            }
                        }
                        return endOfData();
                    }
                };
            }
        };
    }

    @Override
    public Layer add(Name name, Reader text, T data, Set> anchors) throws IOException {
        return Iterables.getOnlyElement(add(Collections.>singleton(new SimpleLayer(name, CharStreams.toString(text), data, anchors, null))));
    }

    @Override
    public Iterable> add(Iterable> batch) throws IOException {
        final Transaction tx = begin();
        try {
            final Node baseNode = baseNode();
            final RelationshipIndex index = index();

            final List> created = Lists.newLinkedList();
            for (Layer layer : batch) {
                final Node node = db.createNode();

                final Name name = layer.getName();
                final URI namespace = name.getNamespace();
                final String localName = name.getLocalName();
                final String ns = (namespace == null ? "" : namespace.toString());
                if (!ns.isEmpty()) {
                    node.setProperty(LayerNode.NAME_NS, ns);
                }
                node.setProperty(LayerNode.NAME_LN, localName);
                node.setProperty(LayerNode.TEXT, layer.read());
                dataNodeMapper.write(layer.data(), node);

                for (Anchor anchor : layer.getAnchors()) {
                    final Layer anchorText = anchor.getText();
                    if (anchorText instanceof LayerNode) {
                        final Node anchorTextNode = ((LayerNode) anchorText).node;
                        final Relationship anchorRel = node.createRelationshipTo(anchorTextNode, ANCHORS);

                        final TextRange anchorRange = anchor.getRange();
                        final long rangeStart = anchorRange.getStart();
                        final long rangeEnd = anchorRange.getEnd();

                        anchorRel.setProperty(LayerNode.RANGE_START, rangeStart);
                        anchorRel.setProperty(LayerNode.RANGE_END, rangeEnd);

                        index.add(anchorRel, "id", ValueContext.numeric(node.getId()));
                        index.add(anchorRel, "text", ValueContext.numeric(anchorTextNode.getId()));
                        index.add(anchorRel, "rs", ValueContext.numeric(rangeStart));
                        index.add(anchorRel, "re", ValueContext.numeric(rangeEnd));
                        index.add(anchorRel, "len", ValueContext.numeric(anchorRange.length()));
                        index.add(anchorRel, "ns", ns);
                        index.add(anchorRel, "ln", localName);
                    }
                }

                baseNode.createRelationshipTo(node, HAS_TEXT);
                created.add(new LayerNode(this, node));
            }
            return created;
        } finally {
            commit(tx);
        }
    }

    protected Node baseNode() {
        if (base == null) {
            synchronized (this) {
                final Node referenceNode = db.getReferenceNode();
                Relationship baseRel = referenceNode.getSingleRelationship(HAS_TEXT, Direction.OUTGOING);
                if (baseRel == null) {
                    baseRel = referenceNode.createRelationshipTo(db.createNode(), HAS_TEXT);
                }
                base = baseRel.getEndNode();
            }
        }
        return base;
    }

    protected RelationshipIndex index() {
        return db.index().forRelationships("anchors");
    }

    @Override
    public Layer add(Name name, Reader text, T data, Anchor anchor) throws IOException {
        return add(name, text, data, Collections.singleton(anchor));
    }

    @Override
    public void delete(Iterable> layers) {
        final Transaction tx = begin();
        final RelationshipIndex index = index();
        try {
            final Set nodes = Sets.newHashSet();
            for (LayerNode toDelete : Iterables.filter(layers, LayerNode.class)) {
                nodes.add(toDelete.node);
                for (Relationship anchorRel : TRANSITIVE_ANCHORING.traverse(toDelete.node).relationships()) {
                    nodes.add(anchorRel.getStartNode());
                    index.remove(anchorRel);
                    anchorRel.delete();
                }
            }
            for (Node node : nodes) {
                for (Relationship anchorRel : node.getRelationships(ANCHORS, Direction.OUTGOING)) {
                    index.remove(anchorRel);
                    anchorRel.delete();
                }
                for (Relationship primeRel : node.getRelationships(HAS_TEXT)) {
                    primeRel.delete();
                }
                node.delete();
            }
        } finally {
            commit(tx);
        }
    }

    private void commit(Transaction tx) {
        if (tx != null) {
            try {
                tx.success();
            } finally {
                tx.finish();
            }
        }
    }

    private Transaction begin() {
        return (transactional ? db.beginTx() : null);
    }

    @Override
    public void updateText(Layer target, Reader text) throws IOException {
        if (target instanceof LayerNode) {
            final Transaction tx = begin();
            try {
                ((LayerNode) target).node.setProperty(LayerNode.TEXT, CharStreams.toString(text));
            } finally {
                commit(tx);
            }
        }
    }

    public T data(Node source) throws IOException {
        return dataNodeMapper.read(source, dataType);
    }

    private static final TraversalDescription TRANSITIVE_ANCHORING = Traversal.description()
            .uniqueness(Uniqueness.RELATIONSHIP_GLOBAL)
            .relationships(ANCHORS, Direction.INCOMING);

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy