org.nuxeo.ecm.platform.relations.jena.JenaGraph.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.relations.jena.JenaGraph.java

Source

/*
 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributors:
 *     Anahide Tchertchian
 *     Florent Guillaume
 */

package org.nuxeo.ecm.platform.relations.jena;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.platform.relations.api.Blank;
import org.nuxeo.ecm.platform.relations.api.Graph;
import org.nuxeo.ecm.platform.relations.api.GraphDescription;
import org.nuxeo.ecm.platform.relations.api.Literal;
import org.nuxeo.ecm.platform.relations.api.Node;
import org.nuxeo.ecm.platform.relations.api.QueryResult;
import org.nuxeo.ecm.platform.relations.api.Resource;
import org.nuxeo.ecm.platform.relations.api.Statement;
import org.nuxeo.ecm.platform.relations.api.impl.NodeFactory;
import org.nuxeo.ecm.platform.relations.api.impl.QueryResultImpl;
import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl;
import org.nuxeo.runtime.datasource.ConnectionHelper;

import com.hp.hpl.jena.datatypes.BaseDatatype;
import com.hp.hpl.jena.db.DBConnection;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.graph.impl.LiteralLabel;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.AnonId;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.ModelMaker;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.RSIterator;
import com.hp.hpl.jena.rdf.model.ReifiedStatement;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.SimpleSelector;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.Lock;

/**
 * Jena plugin for NXRelations.
 * <p>
 * Graph implementation using the <a href="http://jena.sourceforge.net/" target="_blank">Jena</a> framework.
 */
public class JenaGraph implements Graph {

    private static final long serialVersionUID = 1L;

    private static final Log log = LogFactory.getLog(JenaGraph.class);

    // keep model in a private field for memory graph (only useful for tests ;
    // not thread safe)
    private transient Model memoryGraph;

    private String name;

    /**
     * Backend type, default is memory, other possible value is "sql".
     */
    private String backend = "memory";

    /**
     * Database-related options, see http://jena.sourceforge.net/DB/options.html.
     */
    private String datasource;

    private String databaseType;

    private boolean databaseDoCompressUri;

    private boolean databaseTransactionEnabled;

    private Map<String, String> namespaces = new HashMap<String, String>();

    /**
     * Class holding graph and connection so that we can close the connection after having used the graph.
     * <p>
     * It can hold the jena connection or the base connection (built from a datasource).
     */
    protected static final class GraphConnection {

        private final Connection baseConnection;

        private final DBConnection connection;

        private final Model graph;

        GraphConnection(DBConnection connection, Model graph) {
            baseConnection = null;
            this.connection = connection;
            this.graph = graph;
        }

        GraphConnection(Connection baseConnection, Model graph) {
            this.baseConnection = baseConnection;
            connection = null;
            this.graph = graph;
        }

        public Model getGraph() {
            return graph;
        }

        protected void close() {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    log.error("Could not close connection");
                }
            }
            if (baseConnection != null) {
                try {
                    baseConnection.close();
                } catch (SQLException e) {
                    log.error("Could not close base connection");
                }
            }
        }
    }

    /**
     * Generates the Jena graph using options.
     *
     * @return the Jena graph (model)
     */
    protected GraphConnection openGraph() {
        return openGraph(false);
    }

    /**
     * Gets the Jena graph using options.
     * <p>
     * The Jena "Convenient" reification style is used when opening models: it allows to ignore reification quadlets
     * when calling the statements list.
     *
     * @param forceReload boolean stating if the jena graph has to be reloaded using options
     * @return the Jena graph (model)
     */
    protected synchronized GraphConnection openGraph(boolean forceReload) {
        // create model given backend
        if (backend.equals("memory")) {
            if (memoryGraph == null || forceReload) {
                memoryGraph = ModelFactory.createDefaultModel(ModelFactory.Convenient);
                memoryGraph.setNsPrefixes(namespaces);
            }
            return new GraphConnection((Connection) null, memoryGraph);
        } else if (backend.equals("sql")) {
            if (datasource == null) {
                throw new IllegalArgumentException("Missing datasource for sql graph : " + name);
            }
            // create a database connection
            Connection baseConnection;
            try {
                baseConnection = ConnectionHelper.getConnection(datasource);
            } catch (SQLException e) {
                throw new IllegalArgumentException(String.format("SQLException while opening %s", datasource), e);
            }
            /*
             * We have to wrap the connection to disallow any commit() or setAutoCommit() on it. Jena calls these
             * methods without regard to the fact that the connection may be managed by an external transaction.
             */
            Connection wrappedConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
                    new Class[] { Connection.class }, new ConnectionFixInvocationHandler(baseConnection));
            DBConnection connection = new DBConnection(wrappedConnection, databaseType);
            // check if named model already exists
            Model graph;
            if (connection.containsModel(name)) {
                ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient);
                graph = m.openModel(name);
            } else {
                // create it
                // check if other models already exist for that connection.
                if (connection.getAllModelNames().hasNext()) {
                    // other models already exist => do not set parameters
                    // on driver.
                    if (databaseDoCompressUri != connection.getDriver().getDoCompressURI()) {
                        log.warn(String.format(
                                "Cannot set databaseDoCompressUri attribute to %s "
                                        + "for model %s, other models already " + "exist with value %s",
                                databaseDoCompressUri, name, connection.getDriver().getDoCompressURI()));
                    }
                    if (databaseTransactionEnabled != connection.getDriver().getIsTransactionDb()) {
                        log.warn(String.format(
                                "Cannot set databaseTransactionEnabled attribute to %s "
                                        + "for model %s, other models already " + "exist with value %s",
                                databaseTransactionEnabled, name, connection.getDriver().getIsTransactionDb()));
                    }
                } else {
                    if (databaseDoCompressUri) {
                        connection.getDriver().setDoCompressURI(true);
                    }
                    if (databaseTransactionEnabled) {
                        connection.getDriver().setIsTransactionDb(true);
                    }
                }
                ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient);
                graph = m.createModel(name);
            }
            graph.setNsPrefixes(namespaces);
            // use baseConnection so that it is closed instead of the jena one
            // (to let the pool handled closure).
            if (baseConnection != null) {
                return new GraphConnection(baseConnection, graph);
            }
            return new GraphConnection(connection, graph);
        } else {
            throw new IllegalArgumentException("Unknown backend type " + backend);
        }
    }

    /**
     * Gets the Jena node for given NXRelations Node instance.
     *
     * @param nuxNode NXrelations Node instance
     * @return Jena node instance
     */
    private static com.hp.hpl.jena.graph.Node getJenaNode(Node nuxNode) {
        if (nuxNode == null) {
            return null;
        }

        com.hp.hpl.jena.graph.Node jenaNodeInst;
        if (nuxNode.isBlank()) {
            Blank blank = (Blank) nuxNode;
            String id = blank.getId();
            if (id == null) {
                jenaNodeInst = com.hp.hpl.jena.graph.Node.createAnon();
            } else {
                jenaNodeInst = com.hp.hpl.jena.graph.Node.createAnon(new AnonId(id));
            }
        } else if (nuxNode.isLiteral()) {
            Literal lit = (Literal) nuxNode;
            String value = lit.getValue();
            if (value == null) {
                throw new IllegalArgumentException(String.format("Invalid literal node %s", nuxNode));
            }
            String language = lit.getLanguage();
            String type = lit.getType();
            if (language != null) {
                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value, language, false);
            } else if (type != null) {
                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value, null, new BaseDatatype(type));
            } else {
                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value);
            }
        } else if (nuxNode.isResource()) {
            Resource resource = (Resource) nuxNode;
            String uri = resource.getUri();
            jenaNodeInst = com.hp.hpl.jena.graph.Node.createURI(uri);

        } else {
            throw new IllegalArgumentException(String.format("Invalid NXRelations node %s", nuxNode));
        }
        return jenaNodeInst;
    }

    /**
     * Gets NXRelations node instance given Jena node.
     *
     * @param jenaNodeInst
     * @return NXRelations node instance
     */
    private Node getNXRelationsNode(com.hp.hpl.jena.graph.Node jenaNodeInst) {
        if (jenaNodeInst == null) {
            return null;
        }
        Node nuxNode = null;
        if (jenaNodeInst.isBlank()) {
            AnonId anonId = jenaNodeInst.getBlankNodeId();
            String id = anonId.getLabelString();
            nuxNode = NodeFactory.createBlank(id);
        } else if (jenaNodeInst.isLiteral()) {
            LiteralLabel label = jenaNodeInst.getLiteral();
            String value = label.getLexicalForm();
            String type = jenaNodeInst.getLiteralDatatypeURI();
            String language = jenaNodeInst.getLiteralLanguage();
            if (type != "") {
                nuxNode = NodeFactory.createTypedLiteral(value, type);
            } else if (language != "") {
                nuxNode = NodeFactory.createLiteral(value, language);
            } else {
                nuxNode = NodeFactory.createLiteral(value);
            }
        } else if (jenaNodeInst.isURI()) {
            String uri = jenaNodeInst.getURI();
            // try to find corresponding prefix
            // TODO AT: maybe take namespaces from relation service?
            for (Map.Entry<String, String> ns : namespaces.entrySet()) {
                String base = ns.getValue();
                if (uri.startsWith(base)) {
                    String localName = uri.substring(base.length());
                    nuxNode = NodeFactory.createQNameResource(base, localName);
                    break;
                }
            }
            if (nuxNode == null) {
                // default to resource
                nuxNode = NodeFactory.createResource(uri);
            }
        } else {
            throw new IllegalArgumentException("Cannot translate non concrete Jena node into NXRelations node");
        }
        return nuxNode;
    }

    /**
     * Gets Jena statement selector corresponding to the NXRelations statement.
     *
     * @param graph the jena graph
     * @param nuxStatement NXRelations statement
     * @return jena statement selector
     */
    private static SimpleSelector getJenaSelector(Model graph, Statement nuxStatement) {
        com.hp.hpl.jena.rdf.model.Resource subjResource = null;
        com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStatement.getSubject());
        if (subject != null && subject.isURI()) {
            subjResource = graph.getResource(subject.getURI());
        }
        Property predProp = null;
        com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStatement.getPredicate());
        if (predicate != null && predicate.isURI()) {
            predProp = graph.getProperty(predicate.getURI());
        }
        com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStatement.getObject());
        RDFNode objRDF = null;
        if (object != null) {
            objRDF = graph.asRDFNode(object);
        }
        return new SimpleSelector(subjResource, predProp, objRDF);
    }

    /**
     * Gets NXRelations statement corresponding to the Jena statement.
     * <p>
     * Reified statements may be retrieved from the Jena graph and set as properties on NXRelations statements.
     *
     * @param graph the jena graph
     * @param jenaStatement jena statement
     * @return NXRelations statement
     */
    private Statement getNXRelationsStatement(Model graph, com.hp.hpl.jena.rdf.model.Statement jenaStatement) {
        Node subject = getNXRelationsNode(jenaStatement.getSubject().asNode());
        Node predicate = getNXRelationsNode(jenaStatement.getPredicate().asNode());
        Node object = getNXRelationsNode(jenaStatement.getObject().asNode());
        Statement statement = new StatementImpl(subject, predicate, object);

        // take care of properties
        if (graph.isReified(jenaStatement)) {
            com.hp.hpl.jena.rdf.model.Resource reifiedStmt = graph.getAnyReifiedStatement(jenaStatement);
            StmtIterator it = reifiedStmt.listProperties();
            while (it.hasNext()) {
                com.hp.hpl.jena.rdf.model.Statement stmt = it.nextStatement();
                Node nuxNode = getNXRelationsNode(stmt.getPredicate().asNode());
                // ugly cast as a Resource
                Node value = getNXRelationsNode(stmt.getObject().asNode());
                statement.addProperty((Resource) nuxNode, value);
            }
        }

        return statement;
    }

    /**
     * Gets NXRelations statement list corresponding to the Jena statement list.
     *
     * @param graph the jena graph
     * @param jenaStatements jena statements list
     * @return NXRelations statements list
     */
    private List<Statement> getNXRelationsStatements(Model graph,
            List<com.hp.hpl.jena.rdf.model.Statement> jenaStatements) {
        List<Statement> nuxStmts = new ArrayList<Statement>();
        for (com.hp.hpl.jena.rdf.model.Statement jenaStmt : jenaStatements) {
            // NXP-2665: remove reified statements are they're as properties in
            // nuxeo logic
            if (!jenaStmt.getSubject().canAs(ReifiedStatement.class)) {
                nuxStmts.add(getNXRelationsStatement(graph, jenaStmt));
            }
        }
        return nuxStmts;
    }

    // Interface implementation

    @Override
    public void setDescription(GraphDescription graphDescription) {
        name = graphDescription.getName();
        setOptions(graphDescription.getOptions());
        setNamespaces(graphDescription.getNamespaces());
    }

    protected void setOptions(Map<String, String> options) {
        for (Map.Entry<String, String> option : options.entrySet()) {
            String key = option.getKey();
            String value = option.getValue();
            if (key.equals("backend")) {
                if (value.equals("memory") || value.equals("sql")) {
                    backend = value;
                } else {
                    throw new IllegalArgumentException(String.format("Unknown backend %s for Jena graph", value));
                }
            } else if (key.equals("datasource")) {
                datasource = value;
            } else if (key.equals("databaseType")) {
                databaseType = value;
            } else if (key.equals("databaseDoCompressUri")) {
                if (value.equals("true")) {
                    databaseDoCompressUri = true;
                } else if (value.equals("false")) {
                    databaseDoCompressUri = false;
                } else {
                    String format = "Illegal value %s for databaseDoCompressUri, must be true or false";
                    throw new IllegalArgumentException(String.format(format, value));
                }
            } else if (key.equals("databaseTransactionEnabled")) {
                if (value.equals("true")) {
                    databaseTransactionEnabled = true;
                } else if (value.equals("false")) {
                    databaseTransactionEnabled = false;
                } else {
                    String format = "Illegal value %s for databaseTransactionEnabled, must be true or false";
                    throw new IllegalArgumentException(String.format(format, value));
                }
            }
        }
    }

    public void setNamespaces(Map<String, String> namespaces) {
        this.namespaces = namespaces;
    }

    @Override
    public Map<String, String> getNamespaces() {
        return namespaces;
    }

    @Override
    public void add(Statement statement) {
        add(Collections.singletonList(statement));
    }

    @Override
    public void add(List<Statement> statements) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.WRITE);
            for (Statement nuxStmt : statements) {
                com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject());
                com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate());
                com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStmt.getObject());
                Triple jenaTriple = Triple.create(subject, predicate, object);
                com.hp.hpl.jena.rdf.model.Statement jenaStmt = graph.asStatement(jenaTriple);

                // properties
                Map<Resource, Node[]> properties = nuxStmt.getProperties();
                if (properties == null || properties.isEmpty()) {
                    // no properties
                    graph.add(jenaStmt);
                } else {
                    List<com.hp.hpl.jena.rdf.model.Statement> stmts = new ArrayList<com.hp.hpl.jena.rdf.model.Statement>();
                    stmts.add(jenaStmt);
                    // create reified statement if it does not exist
                    com.hp.hpl.jena.graph.Node reifiedStmt = graph.getAnyReifiedStatement(jenaStmt).asNode();
                    for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
                        com.hp.hpl.jena.graph.Node prop = getJenaNode(property.getKey());
                        for (Node node : property.getValue()) {
                            com.hp.hpl.jena.graph.Node value = getJenaNode(node);
                            Triple propTriple = Triple.create(reifiedStmt, prop, value);
                            stmts.add(graph.asStatement(propTriple));
                        }
                    }
                    graph.add(stmts);
                }
            }
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public void remove(Statement statement) {
        remove(Collections.singletonList(statement));
    }

    @Override
    public void remove(List<Statement> statements) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.WRITE);
            for (Statement nuxStmt : statements) {
                com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject());
                com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate());
                com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStmt.getObject());
                Triple jenaTriple = Triple.create(subject, predicate, object);
                com.hp.hpl.jena.rdf.model.Statement jenaStmt = graph.asStatement(jenaTriple);
                graph.remove(jenaStmt);
                // remove properties
                RSIterator it = graph.listReifiedStatements(jenaStmt);
                while (it.hasNext()) {
                    ReifiedStatement rs = it.nextRS();
                    rs.removeProperties();
                }
                // remove quadlets
                graph.removeAllReifications(jenaStmt);
                // graph.removeReification(reifiedStmt);
            }
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public List<Statement> getStatements() {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            StmtIterator it = graph.listStatements();
            return getNXRelationsStatements(graph, it.toList());
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public List<Statement> getStatements(Node subject, Node predicate, Node object) {
        return getStatements(new StatementImpl(subject, predicate, object));
    }

    @Override
    public List<Statement> getStatements(Statement statement) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            SimpleSelector selector = getJenaSelector(graph, statement);
            StmtIterator it = graph.listStatements(selector);
            return getNXRelationsStatements(graph, it.toList());
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public List<Node> getSubjects(Node predicate, Node object) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(null, predicate, object));
            ResIterator it = graph.listSubjectsWithProperty(selector.getPredicate(), selector.getObject());
            List<Node> res = new ArrayList<Node>();
            while (it.hasNext()) {
                res.add(getNXRelationsNode(it.nextResource().asNode()));
            }
            return res;
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public List<Node> getPredicates(Node subject, Node object) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(subject, null, object));
            StmtIterator it = graph.listStatements(selector);
            List<Statement> statements = getNXRelationsStatements(graph, it.toList());
            List<Node> res = new ArrayList<Node>();
            for (Statement stmt : statements) {
                Node predicate = stmt.getPredicate();
                if (!res.contains(predicate)) {
                    // remove duplicates
                    res.add(predicate);
                }
            }
            return res;
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public List<Node> getObjects(Node subject, Node predicate) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(subject, predicate, null));
            NodeIterator it = graph.listObjectsOfProperty(selector.getSubject(), selector.getPredicate());
            List<Node> res = new ArrayList<Node>();
            while (it.hasNext()) {
                res.add(getNXRelationsNode(it.nextNode().asNode()));
            }
            return res;
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public boolean hasStatement(Statement statement) {
        if (statement == null) {
            return false;
        }
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            SimpleSelector selector = getJenaSelector(graph, statement);
            return graph.contains(selector.getSubject(), selector.getPredicate(), selector.getObject());
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public boolean hasResource(Resource resource) {
        if (resource == null) {
            return false;
        }
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            com.hp.hpl.jena.graph.Node jenaNodeInst = getJenaNode(resource);
            RDFNode jenaNode = graph.asRDFNode(jenaNodeInst);
            return graph.containsResource(jenaNode);
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    /**
     * Returns the number of statements in the graph.
     * <p>
     * XXX AT: this size may not be equal to the number of statements retrieved via getStatements() because it counts
     * each statement property.
     *
     * @return integer number of statements in the graph
     */
    @Override
    public Long size() {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            return graph.size();
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public void clear() {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            graph.removeAll();
            // XXX AT: remove reification quadlets explicitly
            RSIterator it = graph.listReifiedStatements();
            List<ReifiedStatement> rss = new ArrayList<ReifiedStatement>();
            while (it.hasNext()) {
                rss.add(it.nextRS());
            }
            for (ReifiedStatement rs : rss) {
                graph.removeReification(rs);
            }
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public QueryResult query(String queryString, String language, String baseURI) {
        Model graph = null;
        GraphConnection graphConnection = null;
        QueryResult res = null;
        QueryExecution qe = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            log.debug(String.format("Running query %s", queryString));
            // XXX AT: ignore language for now
            if (language != null && !language.equals("sparql")) {
                log.warn(String.format("Unknown language %s for query, using SPARQL", language));
            }
            Query query = QueryFactory.create(queryString);
            query.setBaseURI(baseURI);
            qe = QueryExecutionFactory.create(query, graph);
            res = new QueryResultImpl(0, new ArrayList<String>(), new ArrayList<Map<String, Node>>());
            ResultSet jenaResults = qe.execSelect();
            Integer count = 0;
            List<String> variableNames = jenaResults.getResultVars();
            List<Map<String, Node>> nuxResults = new ArrayList<Map<String, Node>>();
            while (jenaResults.hasNext()) {
                QuerySolution soln = jenaResults.nextSolution();
                Map<String, Node> nuxSol = new HashMap<String, Node>();
                for (String varName : variableNames) {
                    RDFNode x = soln.get(varName);
                    nuxSol.put(varName, getNXRelationsNode(x.asNode()));
                }
                nuxResults.add(nuxSol);
                count++;
            }
            res = new QueryResultImpl(count, variableNames, nuxResults);
        } finally {
            if (qe != null) {
                // Important - free up resources used running the query
                qe.close();
            }
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
        return res;
    }

    @Override
    public int queryCount(String queryString, String language, String baseURI) {
        return query(queryString, language, baseURI).getResults().size();
    }

    @Override
    public boolean read(InputStream in, String lang, String base) {
        // XXX AT: maybe update namespaces in case some new appeared
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.READ);
            graph.read(in, base, lang);
            // default to true
            return true;
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public boolean read(String path, String lang, String base) {
        // XXX AT: maybe update namespaces in case some new appeared
        InputStream in = null;
        try {
            in = new FileInputStream(path);
            return read(in, lang, base);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }
    }

    @Override
    public boolean write(OutputStream out, String lang, String base) {
        Model graph = null;
        GraphConnection graphConnection = null;
        try {
            graphConnection = openGraph();
            graph = graphConnection.getGraph();
            graph.enterCriticalSection(Lock.WRITE);
            graph.write(out, lang, base);
            // default to true
            return true;
        } finally {
            if (graph != null) {
                graph.leaveCriticalSection();
            }
            if (graphConnection != null) {
                graphConnection.close();
            }
        }
    }

    @Override
    public boolean write(String path, String lang, String base) {
        OutputStream out = null;
        try {
            File file = new File(path);
            out = new FileOutputStream(file);
            return write(out, lang, base);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
    }

}

/**
 * This invocation handler is designed to wrap a normal connection but avoid all calls to
 * <ul>
 * <li>{@link Connection#commit}</li>
 * <li>{@link Connection#setAutoCommit}</li>
 * </ul>
 * <p>
 * We have to do this because Jena calls these methods without regard to the fact that the connection may be managed by
 * an external transaction.
 *
 * @author Florent Guillaume
 */

class ConnectionFixInvocationHandler implements InvocationHandler {

    private final Connection connection;

    ConnectionFixInvocationHandler(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        final String name = method.getName();
        if (name.equals("commit")) {
            return null;
        } else if (name.equals("setAutoCommit")) {
            return null;
        } else {
            try {
                return method.invoke(connection, args);
            } catch (InvocationTargetException e) {
                throw e.getCause();
            }
        }
    }
}