Package net.fortytwo.ripple.model.impl.sesame

Source Code of net.fortytwo.ripple.model.impl.sesame.SesameModelConnection

package net.fortytwo.ripple.model.impl.sesame;

import info.aduna.iteration.CloseableIteration;
import net.fortytwo.flow.Buffer;
import net.fortytwo.flow.Collector;
import net.fortytwo.flow.NullSink;
import net.fortytwo.flow.Sink;
import net.fortytwo.flow.Source;
import net.fortytwo.flow.rdf.CloseableIterationSource;
import net.fortytwo.flow.rdf.diff.RDFDiffSink;
import net.fortytwo.ripple.RippleException;
import net.fortytwo.ripple.control.Task;
import net.fortytwo.ripple.control.TaskSet;
import net.fortytwo.ripple.io.RDFImporter;
import net.fortytwo.ripple.model.GetStatementsQuery;
import net.fortytwo.ripple.model.Model;
import net.fortytwo.ripple.model.ModelConnection;
import net.fortytwo.ripple.model.NumericValue;
import net.fortytwo.ripple.model.RDFValue;
import net.fortytwo.ripple.model.RippleList;
import net.fortytwo.ripple.model.RippleValue;
import net.fortytwo.ripple.model.RippleValueComparator;
import net.fortytwo.ripple.model.StatementPatternQuery;
import org.openrdf.model.Literal;
import org.openrdf.model.Namespace;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.impl.MapBindingSet;
import org.openrdf.query.parser.ParsedQuery;
import org.openrdf.query.parser.sparql.SPARQLParser;
import org.openrdf.sail.NotifyingSailConnection;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailConnectionListener;
import org.openrdf.sail.SailException;
import org.openrdf.sail.SailReadOnlyException;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

/**
* @author Joshua Shinavier (http://fortytwo.net)
*/
public class SesameModelConnection implements ModelConnection {
    private static final Logger logger
            = Logger.getLogger(ModelConnection.class.getName());

    // instantiate this factory lazily, for the sake of Android applications which don't support javax
    private static DatatypeFactory DATATYPE_FACTORY;

    protected final SesameModel model;
    protected SailConnection sailConnection;
    protected final RDFDiffSink listenerSink;
    protected final ValueFactory valueFactory;
    private final TaskSet taskSet = new TaskSet();
    private final Comparator<RippleValue> comparator;

    private boolean closed = false;

    protected SesameModelConnection(final SesameModel model, final RDFDiffSink listenerSink)
            throws RippleException {
        this.model = model;
        this.listenerSink = listenerSink;

        try {
            valueFactory = model.sail.getValueFactory();
        } catch (Throwable t) {
            throw new RippleException(t);
        }

        openSailConnection();

        synchronized (model.openConnections) {
            model.openConnections.add(this);
        }

        comparator = new RippleValueComparator(this);
    }

    public Model getModel() {
        return model;
    }

    ValueFactory getValueFactory() {
        return valueFactory;
    }

    public void toList(final RippleValue v,
                       final Sink<RippleList> sink) throws RippleException {
        SesameList.from(v, sink, this);
    }

    public RippleList list() {
        return SesameList.nilList();
    }

    public void finish() throws RippleException {
        taskSet.waitUntilEmpty();
    }

    public void close() throws RippleException {
        closed = true;

        finish();

        closeSailConnection(true);

        synchronized (model.openConnections) {
            model.openConnections.remove(this);
        }
    }

    /**
     * Returns the ModelConnection to a normal state after an Exception has
     * been thrown.
     */
    public void reset(final boolean rollback) throws RippleException {
        closeSailConnection(rollback);
        openSailConnection();
    }

    public void commit() throws RippleException {
        try {
            sailConnection.commit();
            sailConnection.begin();
        } catch (SailReadOnlyException e) {
            handleSailReadOnlyException(e);
        } catch (Throwable t) {
            throw new RippleException(t);
        }
    }

    /**
     * @return the existing <code>SailConnection</code> of this model connection.
     * A new <code>SailConnection</code> is not created.
     * The connection comes with an active transaction,
     * and a new transaction should be begun immediately after external commit or rollback operations.
     */
    public SailConnection getSailConnection() {
        return sailConnection;
    }

    private synchronized void openSailConnection()
            throws RippleException {
        try {
            sailConnection = model.sail.getConnection();
            sailConnection.begin();

// FIXME: this doesn't give the LexiconUpdater any information about namespaces
// FIXME: removed because SailConnectionListener is no longer supported by arbitrary Sails... you would need to wrap
//        the Sail in a notifying Sail... but this still wouldn't give you namespace updates.
            if (null != listenerSink && sailConnection instanceof NotifyingSailConnection) {
                SailConnectionListener listener
                        = new SailConnectionListenerAdapter(listenerSink);

                ((NotifyingSailConnection) sailConnection).addConnectionListener(listener);
            }
        } catch (Throwable t) {
            throw new RippleException(t);
        }
    }

    private synchronized void closeSailConnection(final boolean rollback)
            throws RippleException {
        try {
            if (sailConnection.isOpen()) {
                if (rollback) {
                    sailConnection.rollback();
                }

                sailConnection.close();
            } else {
                // Don't throw an exception: we could easily end up in a loop.
                logger.severe("tried to close an already-closed connection");
            }
        } catch (SailReadOnlyException e) {
            handleSailReadOnlyException(e);
        } catch (Throwable t) {
            throw new RippleException(t);
        }
    }

    private Literal castToLiteral(final Value v) throws RippleException {
        if (v instanceof Literal) {
            return (Literal) v;
        } else {
            throw new RippleException("value " + v + " is not a Literal");
        }
    }

    // Note: everything apart from xsd:true is considered false.
    // Eventually, this method may throw a type mismatch exception if it is
    // given a value other than "true"^^xsd:boolean or "false"^^xsd:boolean.

    public boolean toBoolean(final RippleValue rv) throws RippleException {
        Literal l = castToLiteral(rv.toRDF(this).sesameValue());

        //TODO: is capitalization relevant? Can 'true' also be represented as '1'?
        URI datatype = l.getDatatype();
        return null != datatype
                && XMLSchema.BOOLEAN.equals(datatype)
                && l.getLabel().equals("true");
    }

    public NumericValue toNumericValue(final RippleValue rv)
            throws RippleException {
        if (rv instanceof NumericValue) {
            return (NumericValue) rv;
        } else {
            return new SesameNumericValue(rv.toRDF(this));
        }
    }

    public Date toDateValue(final RippleValue v) throws RippleException {
        Literal l = castToLiteral(v.toRDF(this).sesameValue());

        XMLGregorianCalendar c = l.calendarValue();
        return c.toGregorianCalendar().getTime();
    }

    // TODO: this method is incomplete
    public String toString(final RippleValue v) throws RippleException {
        if (v instanceof RDFValue) {
            Value r = ((RDFValue) v).sesameValue();

            if (r instanceof Literal) {
                return ((Literal) r).getLabel();
            } else {
                return r.toString();
            }
        } else {
            return v.toString();
        }
    }

    public void add(final RippleValue subj, final RippleValue pred, final RippleValue obj, RippleValue... contexts)
            throws RippleException {
        ensureOpen();

        Value subjValue = subj.toRDF(this).sesameValue();
        Value predValue = pred.toRDF(this).sesameValue();
        Value objValue = obj.toRDF(this).sesameValue();

        if (!(subjValue instanceof Resource)
                || !(predValue instanceof URI)) {
            return;
        }

        try {
            // Trick to be able to iterate through contexts even if none are given
            if (0 == contexts.length) {
                sailConnection.addStatement(
                        (Resource) subjValue, (URI) predValue, objValue);
            } else {
                for (RippleValue context : contexts) {
                    Value contextValue;

                    if (null == context) {
                        contextValue = null;
                    } else {
                        contextValue = context.toRDF(this).sesameValue();

                        // rdf:nil is a special case -- as a analysis name in Ripple, it
                        // actually represents the null analysis.
                        if (contextValue.equals(RDF.NIL)) {
                            contextValue = null;
                        } else if (!(contextValue instanceof Resource)) {
                            return;
                        }
                    }

                    sailConnection.addStatement(
                            (Resource) subjValue, (URI) predValue, objValue, (Resource) contextValue);
                }
            }
        } catch (SailReadOnlyException e) {
            handleSailReadOnlyException(e);
        } catch (SailException e) {
            reset(true);
            throw new RippleException(e);
        }
    }

    public void remove(final RippleValue subj,
                       final RippleValue pred,
                       final RippleValue obj,
                       final RippleValue... contexts) throws RippleException {

        ensureOpen();

        Value subjValue, predValue, objValue;

        if (null == subj) {
            subjValue = null;
        } else {
            subjValue = subj.toRDF(this).sesameValue();
            if (!(subjValue instanceof Resource)) {
                return;
            }
        }

        if (null == pred) {
            predValue = null;
        } else {
            predValue = pred.toRDF(this).sesameValue();
            if (!(predValue instanceof URI)) {
                return;
            }
        }

        if (null == obj) {
            objValue = null;
        } else {
            objValue = obj.toRDF(this).sesameValue();
        }

        try {
            // Trick to be able to iterate through contexts even if none are given
            if (0 == contexts.length) {
                sailConnection.removeStatements(
                        (Resource) subjValue, (URI) predValue, objValue);
            } else {
                for (RippleValue context : contexts) {
                    Value contextValue;

                    if (null == context) {
                        contextValue = null;
                    } else {
                        contextValue = context.toRDF(this).sesameValue();

                        // rdf:nil is a special case -- as a analysis name in Ripple, it
                        // actually represents the null analysis.
                        if (contextValue.equals(RDF.NIL)) {
                            contextValue = null;
                        } else if (!(contextValue instanceof Resource)) {
                            return;
                        }
                    }

                    sailConnection.removeStatements(
                            (Resource) subjValue, (URI) predValue, objValue, (Resource) contextValue);
                }
            }
        } catch (SailReadOnlyException e) {
            handleSailReadOnlyException(e);
        } catch (SailException e) {
            reset(true);
            throw new RippleException(e);
        }
    }

    // Note: this method is no longer in the ModelConnection API
    public long countStatements(final Resource... contexts)
            throws RippleException {
        ensureOpen();

        int count = 0;

        try {
            //synchronized ( model )
            {
                CloseableIteration<? extends Statement, SailException> stmtIter
                        = sailConnection.getStatements(
                        null, null, null, false, contexts);

                while (stmtIter.hasNext()) {
                    stmtIter.next();
                    count++;
                }

                stmtIter.close();
            }
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }

        return count;
    }

    public Comparator<RippleValue> getComparator() {
        return comparator;
    }

    public RDFValue valueOf(final java.net.URI s) throws RippleException {
        try {
// return canonicalize(valueFactory.createURI(s));
            // Note: do NOT automatically canonicalize values.  Sometimes one needs the original URI (e.g. so as to
            // remove statements), and not the native object it maps to.
            return new RDFValue(valueFactory.createURI(s.toString()));
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    public RDFValue valueOf(final Date d) throws RippleException {
        // synchronize on the only other static member
        synchronized (logger) {
            if (null == DATATYPE_FACTORY) {
                try {
                    DATATYPE_FACTORY = DatatypeFactory.newInstance();
                } catch (DatatypeConfigurationException e) {
                    throw new RippleException(e);
                }
            }
        }

        GregorianCalendar c = new GregorianCalendar();
        c.setTime(d);
        XMLGregorianCalendar xml = DATATYPE_FACTORY.newXMLGregorianCalendar(c);
        return new RDFValue(valueFactory.createLiteral(xml));
    }

    public RippleValue canonicalValue(final RDFValue v) {
        return model.specialValues.get(v);
    }

    public RDFValue valueOf(final String s) throws RippleException {
        try {
            return new RDFValue(
                    valueFactory.createLiteral(s));
//                    valueFactory.createLiteral( s, XMLSchema.STRING ) );
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    public RDFValue valueOf(final String s, final String language)
            throws RippleException {
        try {
            return new RDFValue(
                    valueFactory.createLiteral(s, language));
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    public RDFValue valueOf(final String s, final URI dataType)
            throws RippleException {
        try {
            return new RDFValue(
                    valueFactory.createLiteral(s, dataType));
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    public RDFValue valueOf(final boolean b)
            throws RippleException {
        try {
            return new RDFValue(
                    valueFactory.createLiteral("" + b, XMLSchema.BOOLEAN));
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    public NumericValue valueOf(final int i) throws RippleException {
        return new SesameNumericValue(i);
    }

    public NumericValue valueOf(final long l) throws RippleException {
        return new SesameNumericValue(l);
    }

    public NumericValue valueOf(final double d) throws RippleException {
        return new SesameNumericValue(d);
    }

    public NumericValue valueOf(final BigDecimal b) throws RippleException {
        return new SesameNumericValue(b);
    }

    public void setNamespace(final String prefix, final String ns, final boolean override)
            throws RippleException {
        ensureOpen();

        //logger.info( "### setting namespace: '" + prefix + "' to " + ns );
        try {
            //synchronized ( model )
            {
                if (override || null == sailConnection.getNamespace(prefix)) {
                    if (null == ns) {
                        sailConnection.removeNamespace(prefix);
                    } else {
                        sailConnection.setNamespace(prefix, ns);
                    }
                }
            }
        } catch (SailReadOnlyException e) {
            handleSailReadOnlyException(e);
        } catch (Throwable t) {
            reset(true);
            throw new RippleException(t);
        }
    }

    private class QueryTask extends Task {
        private final StatementPatternQuery query;
        private Sink<RippleValue> sink;

        public QueryTask(final StatementPatternQuery query, final Sink<RippleValue> sink) {
            this.query = query;
            this.sink = sink;
        }

        public void executeProtected() throws RippleException {
            query(query, sink, false);

            /*
            ModelConnection mc = model.getConnection();
            try {
                mc.query(query, sink, false);
            } finally {
                mc.close();
            }*/
        }

        protected void stopProtected() {
            synchronized (query) {
                sink = new NullSink<RippleValue>();
            }
        }
    }

    public void query(final StatementPatternQuery query,
                      final Sink<RippleValue> sink,
                      final boolean asynchronous) throws RippleException {
        ensureOpen();

        if (asynchronous) {
            QueryTask task = new QueryTask(query, sink);
            taskSet.add(task);
        } else {
            GetStatementsQuery sesameQuery;

            try {
                sesameQuery = new GetStatementsQuery(query, this);
            } catch (GetStatementsQuery.InvalidQueryException e) {
                logger.fine("invalid query: " + e.getMessage());
                return;
            }

            Sink<Value> valueSink = new Sink<Value>() {
                public void put(final Value val) throws RippleException {
                    //System.out.println("got value: " + val);
                    sink.put(canonicalValue(new RDFValue(val)));
                }
            };

            try {
                sesameQuery.getValues(sailConnection, valueSink);
            } catch (RippleException e) {
                reset(true);
                throw e;
            }
            //getStatements( query.subject, query.predicate, query.object, stSink, query.includeInferred );
        }
    }

    public Source<Namespace> getNamespaces() throws RippleException {
        ensureOpen();

        Collector<Namespace> results = new Collector<Namespace>();
        Source<Namespace> source;

        try {
            source = new CloseableIterationSource<Namespace, SailException>(
                    (CloseableIteration<Namespace, SailException>) sailConnection.getNamespaces());
        } catch (SailException e) {
            throw new RippleException(e);
        }

        source.writeTo(results);

        return results;
    }

    //FIXME: Statements should be absent from the ModelConnection API
    public void getStatements(final RDFValue subj,
                              final RDFValue pred,
                              final RDFValue obj,
                              final Sink<Statement> sink)
            throws RippleException {
        ensureOpen();

        Value rdfSubj = (null == subj) ? null : subj.sesameValue();
        Value rdfPred = (null == pred) ? null : pred.sesameValue();
        Value rdfObj = (null == obj) ? null : obj.sesameValue();

        if ((null == rdfSubj || rdfSubj instanceof Resource)
                && (null == rdfPred || rdfPred instanceof URI)) {
            // Note: we must collect results in a buffer before putting anything
            //       into the sink, as inefficient as that is, because otherwise
            //       we might end up opening another RepositoryResult before
            //       the one below closes, which currently causes Sesame to
            //       deadlock.  Even using a separate RepositoryConnection for
            //       each RepositoryResult doesn't seem to help.
            Buffer<Statement> buffer = new Buffer<Statement>(sink);
            CloseableIteration<? extends Statement, SailException> stmtIter = null;

            //TODO: use CloseableIterationSource

            // Perform the query and collect results.
            try {
                //synchronized ( model )
                //{
                stmtIter = sailConnection.getStatements(
                        (Resource) rdfSubj, (URI) rdfPred, rdfObj, false);
                //stmtIter.enableDuplicateFilter();

                while (stmtIter.hasNext()) {
                    Statement st = stmtIter.next();
                    try {
                        buffer.put(st);
                    } catch (RippleException e) {
                        // Soft fail
                        e.logError();
                    }
                }

                stmtIter.close();
                //}
            } catch (Throwable t) {
                try {
                    if (null != stmtIter) {
                        stmtIter.close();
                    }
                } catch (Throwable t2) {
                    t2.printStackTrace(System.err);
                    System.exit(1);
                }

                reset(true);
                throw new RippleException(t);
            }

            buffer.flush();
        }
    }

    public CloseableIteration<? extends BindingSet, QueryEvaluationException> evaluate(final String query)
            throws RippleException {
        ensureOpen();

        SPARQLParser parser = new SPARQLParser();

        boolean useInference = false;
        // FIXME
        String baseURI = "http://example.org/bogusBaseURI/";

        ParsedQuery pq;
        try {
            pq = parser.parseQuery(query, baseURI);
        } catch (MalformedQueryException e) {
            throw new RippleException(e);
        }

        MapBindingSet bindings = new MapBindingSet();

        try {
            return sailConnection.evaluate(pq.getTupleExpr(), pq.getDataset(), bindings, useInference);
        } catch (SailException e) {
            throw new RippleException(e);
        }
    }

    public Source<RippleValue> getContexts()
            throws RippleException {
        ensureOpen();

        return new Source<RippleValue>() {
            public void writeTo(Sink<RippleValue> sink) throws RippleException {
                try {
                    CloseableIteration<? extends Resource, SailException> iter
                            = sailConnection.getContextIDs();

                    while (iter.hasNext()) {
                        sink.put(new RDFValue(iter.next()));
                    }

                    iter.close();
                } catch (SailException e) {
                    throw new RippleException(e);
                }
            }
        };
    }

    private void handleSailReadOnlyException(final SailReadOnlyException e) {
        // For now, ignore these exceptions.
    }

    public boolean internalize(final RippleList list) throws RippleException {
        ensureOpen();

        Collector<Statement> buffer = new Collector<Statement>();

        // Handle circular lists (in the unlikely event that some implementation allows them) sanely.
        // TODO: handle list containment cycles (e.g. list containing a list containing the original list) as well.
        // These are actually more likely than circular lists.
        Set<RDFValue> alreadyInterned = new HashSet<RDFValue>();

        RippleList cur = list;
        RDFValue id = cur.toRDF(this);
        while (!cur.isNil()) {
            if (alreadyInterned.contains(id)) {
                break;
            } else {
                alreadyInterned.add(id);
            }

            RDFValue firstRdf = cur.getFirst().toRDF(this);

            if (null == firstRdf) {
                System.err.println("list item has no RDF identity: " + cur.getFirst());
                return false;
            }

            if (cur.getFirst() instanceof RippleList) {
                internalize((RippleList) cur.getFirst());
            }

            RippleList rest = cur.getRest();
            RDFValue restRdf = rest.toRDF(this);

            buffer.put(
                    valueFactory.createStatement((Resource) id.sesameValue(), RDF.TYPE, RDF.LIST));
            buffer.put(
                    valueFactory.createStatement((Resource) id.sesameValue(), RDF.FIRST, firstRdf.sesameValue()));
            buffer.put(
                    valueFactory.createStatement((Resource) id.sesameValue(), RDF.REST, restRdf.sesameValue()));

            cur = rest;
            id = restRdf;
        }

        RDFImporter importer = new RDFImporter(this);
        buffer.writeTo(importer.statementSink());

        return true;
    }

    // if the connection is closed, and a thread tries to access it, bail out ASAP before something confusing happens
    // use this method with methods which read from or write to the Sail
    private void ensureOpen() {
        if (closed) {
            throw new IllegalStateException("connection closed");
        }
    }
}
TOP

Related Classes of net.fortytwo.ripple.model.impl.sesame.SesameModelConnection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.