001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Anahide Tchertchian
016 *     Florent Guillaume
017 */
018
019package org.nuxeo.ecm.platform.relations.jena;
020
021import java.io.File;
022import java.io.FileInputStream;
023import java.io.FileOutputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027import java.lang.reflect.InvocationHandler;
028import java.lang.reflect.InvocationTargetException;
029import java.lang.reflect.Method;
030import java.lang.reflect.Proxy;
031import java.sql.Connection;
032import java.sql.SQLException;
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039import javax.naming.NamingException;
040import javax.sql.DataSource;
041
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.platform.relations.api.Blank;
045import org.nuxeo.ecm.platform.relations.api.Graph;
046import org.nuxeo.ecm.platform.relations.api.GraphDescription;
047import org.nuxeo.ecm.platform.relations.api.Literal;
048import org.nuxeo.ecm.platform.relations.api.Node;
049import org.nuxeo.ecm.platform.relations.api.QueryResult;
050import org.nuxeo.ecm.platform.relations.api.Resource;
051import org.nuxeo.ecm.platform.relations.api.Statement;
052import org.nuxeo.ecm.platform.relations.api.impl.NodeFactory;
053import org.nuxeo.ecm.platform.relations.api.impl.QueryResultImpl;
054import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl;
055import org.nuxeo.runtime.datasource.ConnectionHelper;
056import org.nuxeo.runtime.datasource.DataSourceHelper;
057
058import com.hp.hpl.jena.datatypes.BaseDatatype;
059import com.hp.hpl.jena.db.DBConnection;
060import com.hp.hpl.jena.graph.Triple;
061import com.hp.hpl.jena.graph.impl.LiteralLabel;
062import com.hp.hpl.jena.query.Query;
063import com.hp.hpl.jena.query.QueryExecution;
064import com.hp.hpl.jena.query.QueryExecutionFactory;
065import com.hp.hpl.jena.query.QueryFactory;
066import com.hp.hpl.jena.query.QuerySolution;
067import com.hp.hpl.jena.query.ResultSet;
068import com.hp.hpl.jena.rdf.model.AnonId;
069import com.hp.hpl.jena.rdf.model.Model;
070import com.hp.hpl.jena.rdf.model.ModelFactory;
071import com.hp.hpl.jena.rdf.model.ModelMaker;
072import com.hp.hpl.jena.rdf.model.NodeIterator;
073import com.hp.hpl.jena.rdf.model.Property;
074import com.hp.hpl.jena.rdf.model.RDFNode;
075import com.hp.hpl.jena.rdf.model.RSIterator;
076import com.hp.hpl.jena.rdf.model.ReifiedStatement;
077import com.hp.hpl.jena.rdf.model.ResIterator;
078import com.hp.hpl.jena.rdf.model.SimpleSelector;
079import com.hp.hpl.jena.rdf.model.StmtIterator;
080import com.hp.hpl.jena.shared.Lock;
081
082/**
083 * Jena plugin for NXRelations.
084 * <p>
085 * Graph implementation using the <a href="http://jena.sourceforge.net/" target="_blank">Jena</a> framework.
086 */
087public class JenaGraph implements Graph {
088
089    private static final long serialVersionUID = 1L;
090
091    private static final Log log = LogFactory.getLog(JenaGraph.class);
092
093    // keep model in a private field for memory graph (only useful for tests ;
094    // not thread safe)
095    private transient Model memoryGraph;
096
097    private String name;
098
099    /**
100     * Backend type, default is memory, other possible value is "sql".
101     */
102    private String backend = "memory";
103
104    /**
105     * Database-related options, see http://jena.sourceforge.net/DB/options.html.
106     */
107    private String datasource;
108
109    private String databaseType;
110
111    private boolean databaseDoCompressUri;
112
113    private boolean databaseTransactionEnabled;
114
115    private Map<String, String> namespaces = new HashMap<String, String>();
116
117    /**
118     * Class holding graph and connection so that we can close the connection after having used the graph.
119     * <p>
120     * It can hold the jena connection or the base connection (built from a datasource).
121     */
122    protected static final class GraphConnection {
123
124        private final Connection baseConnection;
125
126        private final DBConnection connection;
127
128        private final Model graph;
129
130        GraphConnection(DBConnection connection, Model graph) {
131            baseConnection = null;
132            this.connection = connection;
133            this.graph = graph;
134        }
135
136        GraphConnection(Connection baseConnection, Model graph) {
137            this.baseConnection = baseConnection;
138            connection = null;
139            this.graph = graph;
140        }
141
142        public Model getGraph() {
143            return graph;
144        }
145
146        protected void close() {
147            if (connection != null) {
148                try {
149                    connection.close();
150                } catch (SQLException e) {
151                    log.error("Could not close connection");
152                }
153            }
154            if (baseConnection != null) {
155                try {
156                    baseConnection.close();
157                } catch (SQLException e) {
158                    log.error("Could not close base connection");
159                }
160            }
161        }
162    }
163
164    /**
165     * Generates the Jena graph using options.
166     *
167     * @return the Jena graph (model)
168     */
169    protected GraphConnection openGraph() {
170        return openGraph(false);
171    }
172
173    /**
174     * Gets the Jena graph using options.
175     * <p>
176     * The Jena "Convenient" reification style is used when opening models: it allows to ignore reification quadlets
177     * when calling the statements list.
178     *
179     * @param forceReload boolean stating if the jena graph has to be reloaded using options
180     * @return the Jena graph (model)
181     */
182    protected synchronized GraphConnection openGraph(boolean forceReload) {
183        // create model given backend
184        if (backend.equals("memory")) {
185            if (memoryGraph == null || forceReload) {
186                memoryGraph = ModelFactory.createDefaultModel(ModelFactory.Convenient);
187                memoryGraph.setNsPrefixes(namespaces);
188            }
189            return new GraphConnection((Connection) null, memoryGraph);
190        } else if (backend.equals("sql")) {
191            if (datasource == null) {
192                throw new IllegalArgumentException("Missing datasource for sql graph : " + name);
193            }
194            // create a database connection
195            Connection baseConnection;
196            try {
197                // try single-datasource non-XA mode
198                baseConnection = ConnectionHelper.getConnection(datasource);
199                if (baseConnection == null) {
200                    // standard datasource usage
201                    DataSource ds = DataSourceHelper.getDataSource(datasource);
202                    baseConnection = ds.getConnection();
203                }
204            } catch (NamingException e) {
205                throw new IllegalArgumentException(String.format("Datasource %s not found", datasource), e);
206            } catch (SQLException e) {
207                throw new IllegalArgumentException(String.format("SQLException while opening %s", datasource), e);
208            }
209            /*
210             * We have to wrap the connection to disallow any commit() or setAutoCommit() on it. Jena calls these
211             * methods without regard to the fact that the connection may be managed by an external transaction.
212             */
213            Connection wrappedConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
214                    new Class[] { Connection.class }, new ConnectionFixInvocationHandler(baseConnection));
215            DBConnection connection = new DBConnection(wrappedConnection, databaseType);
216            // check if named model already exists
217            Model graph;
218            if (connection.containsModel(name)) {
219                ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient);
220                graph = m.openModel(name);
221            } else {
222                // create it
223                // check if other models already exist for that connection.
224                if (connection.getAllModelNames().hasNext()) {
225                    // other models already exist => do not set parameters
226                    // on driver.
227                    if (databaseDoCompressUri != connection.getDriver().getDoCompressURI()) {
228                        log.warn(String.format("Cannot set databaseDoCompressUri attribute to %s "
229                                + "for model %s, other models already " + "exist with value %s", databaseDoCompressUri,
230                                name, connection.getDriver().getDoCompressURI()));
231                    }
232                    if (databaseTransactionEnabled != connection.getDriver().getIsTransactionDb()) {
233                        log.warn(String.format("Cannot set databaseTransactionEnabled attribute to %s "
234                                + "for model %s, other models already " + "exist with value %s",
235                                databaseTransactionEnabled, name, connection.getDriver().getIsTransactionDb()));
236                    }
237                } else {
238                    if (databaseDoCompressUri) {
239                        connection.getDriver().setDoCompressURI(true);
240                    }
241                    if (databaseTransactionEnabled) {
242                        connection.getDriver().setIsTransactionDb(true);
243                    }
244                }
245                ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient);
246                graph = m.createModel(name);
247            }
248            graph.setNsPrefixes(namespaces);
249            // use baseConnection so that it is closed instead of the jena one
250            // (to let the pool handled closure).
251            if (baseConnection != null) {
252                return new GraphConnection(baseConnection, graph);
253            }
254            return new GraphConnection(connection, graph);
255        } else {
256            throw new IllegalArgumentException("Unknown backend type " + backend);
257        }
258    }
259
260    /**
261     * Gets the Jena node for given NXRelations Node instance.
262     *
263     * @param nuxNode NXrelations Node instance
264     * @return Jena node instance
265     */
266    private static com.hp.hpl.jena.graph.Node getJenaNode(Node nuxNode) {
267        if (nuxNode == null) {
268            return null;
269        }
270
271        com.hp.hpl.jena.graph.Node jenaNodeInst;
272        if (nuxNode.isBlank()) {
273            Blank blank = (Blank) nuxNode;
274            String id = blank.getId();
275            if (id == null) {
276                jenaNodeInst = com.hp.hpl.jena.graph.Node.createAnon();
277            } else {
278                jenaNodeInst = com.hp.hpl.jena.graph.Node.createAnon(new AnonId(id));
279            }
280        } else if (nuxNode.isLiteral()) {
281            Literal lit = (Literal) nuxNode;
282            String value = lit.getValue();
283            if (value == null) {
284                throw new IllegalArgumentException(String.format("Invalid literal node %s", nuxNode));
285            }
286            String language = lit.getLanguage();
287            String type = lit.getType();
288            if (language != null) {
289                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value, language, false);
290            } else if (type != null) {
291                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value, null, new BaseDatatype(type));
292            } else {
293                jenaNodeInst = com.hp.hpl.jena.graph.Node.createLiteral(value);
294            }
295        } else if (nuxNode.isResource()) {
296            Resource resource = (Resource) nuxNode;
297            String uri = resource.getUri();
298            jenaNodeInst = com.hp.hpl.jena.graph.Node.createURI(uri);
299
300        } else {
301            throw new IllegalArgumentException(String.format("Invalid NXRelations node %s", nuxNode));
302        }
303        return jenaNodeInst;
304    }
305
306    /**
307     * Gets NXRelations node instance given Jena node.
308     *
309     * @param jenaNodeInst
310     * @return NXRelations node instance
311     */
312    private Node getNXRelationsNode(com.hp.hpl.jena.graph.Node jenaNodeInst) {
313        if (jenaNodeInst == null) {
314            return null;
315        }
316        Node nuxNode = null;
317        if (jenaNodeInst.isBlank()) {
318            AnonId anonId = jenaNodeInst.getBlankNodeId();
319            String id = anonId.getLabelString();
320            nuxNode = NodeFactory.createBlank(id);
321        } else if (jenaNodeInst.isLiteral()) {
322            LiteralLabel label = jenaNodeInst.getLiteral();
323            String value = label.getLexicalForm();
324            String type = jenaNodeInst.getLiteralDatatypeURI();
325            String language = jenaNodeInst.getLiteralLanguage();
326            if (type != "") {
327                nuxNode = NodeFactory.createTypedLiteral(value, type);
328            } else if (language != "") {
329                nuxNode = NodeFactory.createLiteral(value, language);
330            } else {
331                nuxNode = NodeFactory.createLiteral(value);
332            }
333        } else if (jenaNodeInst.isURI()) {
334            String uri = jenaNodeInst.getURI();
335            // try to find corresponding prefix
336            // TODO AT: maybe take namespaces from relation service?
337            for (Map.Entry<String, String> ns : namespaces.entrySet()) {
338                String base = ns.getValue();
339                if (uri.startsWith(base)) {
340                    String localName = uri.substring(base.length());
341                    nuxNode = NodeFactory.createQNameResource(base, localName);
342                    break;
343                }
344            }
345            if (nuxNode == null) {
346                // default to resource
347                nuxNode = NodeFactory.createResource(uri);
348            }
349        } else {
350            throw new IllegalArgumentException("Cannot translate non concrete Jena node into NXRelations node");
351        }
352        return nuxNode;
353    }
354
355    /**
356     * Gets Jena statement selector corresponding to the NXRelations statement.
357     *
358     * @param graph the jena graph
359     * @param nuxStatement NXRelations statement
360     * @return jena statement selector
361     */
362    private static SimpleSelector getJenaSelector(Model graph, Statement nuxStatement) {
363        com.hp.hpl.jena.rdf.model.Resource subjResource = null;
364        com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStatement.getSubject());
365        if (subject != null && subject.isURI()) {
366            subjResource = graph.getResource(subject.getURI());
367        }
368        Property predProp = null;
369        com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStatement.getPredicate());
370        if (predicate != null && predicate.isURI()) {
371            predProp = graph.getProperty(predicate.getURI());
372        }
373        com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStatement.getObject());
374        RDFNode objRDF = null;
375        if (object != null) {
376            objRDF = graph.asRDFNode(object);
377        }
378        return new SimpleSelector(subjResource, predProp, objRDF);
379    }
380
381    /**
382     * Gets NXRelations statement corresponding to the Jena statement.
383     * <p>
384     * Reified statements may be retrieved from the Jena graph and set as properties on NXRelations statements.
385     *
386     * @param graph the jena graph
387     * @param jenaStatement jena statement
388     * @return NXRelations statement
389     */
390    private Statement getNXRelationsStatement(Model graph, com.hp.hpl.jena.rdf.model.Statement jenaStatement) {
391        Node subject = getNXRelationsNode(jenaStatement.getSubject().asNode());
392        Node predicate = getNXRelationsNode(jenaStatement.getPredicate().asNode());
393        Node object = getNXRelationsNode(jenaStatement.getObject().asNode());
394        Statement statement = new StatementImpl(subject, predicate, object);
395
396        // take care of properties
397        if (graph.isReified(jenaStatement)) {
398            com.hp.hpl.jena.rdf.model.Resource reifiedStmt = graph.getAnyReifiedStatement(jenaStatement);
399            StmtIterator it = reifiedStmt.listProperties();
400            while (it.hasNext()) {
401                com.hp.hpl.jena.rdf.model.Statement stmt = it.nextStatement();
402                Node nuxNode = getNXRelationsNode(stmt.getPredicate().asNode());
403                // ugly cast as a Resource
404                Node value = getNXRelationsNode(stmt.getObject().asNode());
405                statement.addProperty((Resource) nuxNode, value);
406            }
407        }
408
409        return statement;
410    }
411
412    /**
413     * Gets NXRelations statement list corresponding to the Jena statement list.
414     *
415     * @param graph the jena graph
416     * @param jenaStatements jena statements list
417     * @return NXRelations statements list
418     */
419    private List<Statement> getNXRelationsStatements(Model graph,
420            List<com.hp.hpl.jena.rdf.model.Statement> jenaStatements) {
421        List<Statement> nuxStmts = new ArrayList<Statement>();
422        for (com.hp.hpl.jena.rdf.model.Statement jenaStmt : jenaStatements) {
423            // NXP-2665: remove reified statements are they're as properties in
424            // nuxeo logic
425            if (!jenaStmt.getSubject().canAs(ReifiedStatement.class)) {
426                nuxStmts.add(getNXRelationsStatement(graph, jenaStmt));
427            }
428        }
429        return nuxStmts;
430    }
431
432    // Interface implementation
433
434    @Override
435    public void setDescription(GraphDescription graphDescription) {
436        name = graphDescription.getName();
437        setOptions(graphDescription.getOptions());
438        setNamespaces(graphDescription.getNamespaces());
439    }
440
441    protected void setOptions(Map<String, String> options) {
442        for (Map.Entry<String, String> option : options.entrySet()) {
443            String key = option.getKey();
444            String value = option.getValue();
445            if (key.equals("backend")) {
446                if (value.equals("memory") || value.equals("sql")) {
447                    backend = value;
448                } else {
449                    throw new IllegalArgumentException(String.format("Unknown backend %s for Jena graph", value));
450                }
451            } else if (key.equals("datasource")) {
452                datasource = value;
453            } else if (key.equals("databaseType")) {
454                databaseType = value;
455            } else if (key.equals("databaseDoCompressUri")) {
456                if (value.equals("true")) {
457                    databaseDoCompressUri = true;
458                } else if (value.equals("false")) {
459                    databaseDoCompressUri = false;
460                } else {
461                    String format = "Illegal value %s for databaseDoCompressUri, must be true or false";
462                    throw new IllegalArgumentException(String.format(format, value));
463                }
464            } else if (key.equals("databaseTransactionEnabled")) {
465                if (value.equals("true")) {
466                    databaseTransactionEnabled = true;
467                } else if (value.equals("false")) {
468                    databaseTransactionEnabled = false;
469                } else {
470                    String format = "Illegal value %s for databaseTransactionEnabled, must be true or false";
471                    throw new IllegalArgumentException(String.format(format, value));
472                }
473            }
474        }
475    }
476
477    public void setNamespaces(Map<String, String> namespaces) {
478        this.namespaces = namespaces;
479    }
480
481    @Override
482    public Map<String, String> getNamespaces() {
483        return namespaces;
484    }
485
486    @Override
487    public void add(Statement statement) {
488        add(Collections.singletonList(statement));
489    }
490
491    @Override
492    public void add(List<Statement> statements) {
493        Model graph = null;
494        GraphConnection graphConnection = null;
495        try {
496            graphConnection = openGraph();
497            graph = graphConnection.getGraph();
498            graph.enterCriticalSection(Lock.WRITE);
499            for (Statement nuxStmt : statements) {
500                com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject());
501                com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate());
502                com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStmt.getObject());
503                Triple jenaTriple = Triple.create(subject, predicate, object);
504                com.hp.hpl.jena.rdf.model.Statement jenaStmt = graph.asStatement(jenaTriple);
505
506                // properties
507                Map<Resource, Node[]> properties = nuxStmt.getProperties();
508                if (properties == null || properties.isEmpty()) {
509                    // no properties
510                    graph.add(jenaStmt);
511                } else {
512                    List<com.hp.hpl.jena.rdf.model.Statement> stmts = new ArrayList<com.hp.hpl.jena.rdf.model.Statement>();
513                    stmts.add(jenaStmt);
514                    // create reified statement if it does not exist
515                    com.hp.hpl.jena.graph.Node reifiedStmt = graph.getAnyReifiedStatement(jenaStmt).asNode();
516                    for (Map.Entry<Resource, Node[]> property : properties.entrySet()) {
517                        com.hp.hpl.jena.graph.Node prop = getJenaNode(property.getKey());
518                        for (Node node : property.getValue()) {
519                            com.hp.hpl.jena.graph.Node value = getJenaNode(node);
520                            Triple propTriple = Triple.create(reifiedStmt, prop, value);
521                            stmts.add(graph.asStatement(propTriple));
522                        }
523                    }
524                    graph.add(stmts);
525                }
526            }
527        } finally {
528            if (graph != null) {
529                graph.leaveCriticalSection();
530            }
531            if (graphConnection != null) {
532                graphConnection.close();
533            }
534        }
535    }
536
537    @Override
538    public void remove(Statement statement) {
539        remove(Collections.singletonList(statement));
540    }
541
542    @Override
543    public void remove(List<Statement> statements) {
544        Model graph = null;
545        GraphConnection graphConnection = null;
546        try {
547            graphConnection = openGraph();
548            graph = graphConnection.getGraph();
549            graph.enterCriticalSection(Lock.WRITE);
550            for (Statement nuxStmt : statements) {
551                com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject());
552                com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate());
553                com.hp.hpl.jena.graph.Node object = getJenaNode(nuxStmt.getObject());
554                Triple jenaTriple = Triple.create(subject, predicate, object);
555                com.hp.hpl.jena.rdf.model.Statement jenaStmt = graph.asStatement(jenaTriple);
556                graph.remove(jenaStmt);
557                // remove properties
558                RSIterator it = graph.listReifiedStatements(jenaStmt);
559                while (it.hasNext()) {
560                    ReifiedStatement rs = it.nextRS();
561                    rs.removeProperties();
562                }
563                // remove quadlets
564                graph.removeAllReifications(jenaStmt);
565                // graph.removeReification(reifiedStmt);
566            }
567        } finally {
568            if (graph != null) {
569                graph.leaveCriticalSection();
570            }
571            if (graphConnection != null) {
572                graphConnection.close();
573            }
574        }
575    }
576
577    @Override
578    public List<Statement> getStatements() {
579        Model graph = null;
580        GraphConnection graphConnection = null;
581        try {
582            graphConnection = openGraph();
583            graph = graphConnection.getGraph();
584            graph.enterCriticalSection(Lock.READ);
585            StmtIterator it = graph.listStatements();
586            return getNXRelationsStatements(graph, it.toList());
587        } finally {
588            if (graph != null) {
589                graph.leaveCriticalSection();
590            }
591            if (graphConnection != null) {
592                graphConnection.close();
593            }
594        }
595    }
596
597    @Override
598    public List<Statement> getStatements(Node subject, Node predicate, Node object) {
599        return getStatements(new StatementImpl(subject, predicate, object));
600    }
601
602    @Override
603    public List<Statement> getStatements(Statement statement) {
604        Model graph = null;
605        GraphConnection graphConnection = null;
606        try {
607            graphConnection = openGraph();
608            graph = graphConnection.getGraph();
609            graph.enterCriticalSection(Lock.READ);
610            SimpleSelector selector = getJenaSelector(graph, statement);
611            StmtIterator it = graph.listStatements(selector);
612            return getNXRelationsStatements(graph, it.toList());
613        } finally {
614            if (graph != null) {
615                graph.leaveCriticalSection();
616            }
617            if (graphConnection != null) {
618                graphConnection.close();
619            }
620        }
621    }
622
623    @Override
624    public List<Node> getSubjects(Node predicate, Node object) {
625        Model graph = null;
626        GraphConnection graphConnection = null;
627        try {
628            graphConnection = openGraph();
629            graph = graphConnection.getGraph();
630            graph.enterCriticalSection(Lock.READ);
631            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(null, predicate, object));
632            ResIterator it = graph.listSubjectsWithProperty(selector.getPredicate(), selector.getObject());
633            List<Node> res = new ArrayList<Node>();
634            while (it.hasNext()) {
635                res.add(getNXRelationsNode(it.nextResource().asNode()));
636            }
637            return res;
638        } finally {
639            if (graph != null) {
640                graph.leaveCriticalSection();
641            }
642            if (graphConnection != null) {
643                graphConnection.close();
644            }
645        }
646    }
647
648    @Override
649    public List<Node> getPredicates(Node subject, Node object) {
650        Model graph = null;
651        GraphConnection graphConnection = null;
652        try {
653            graphConnection = openGraph();
654            graph = graphConnection.getGraph();
655            graph.enterCriticalSection(Lock.READ);
656            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(subject, null, object));
657            StmtIterator it = graph.listStatements(selector);
658            List<Statement> statements = getNXRelationsStatements(graph, it.toList());
659            List<Node> res = new ArrayList<Node>();
660            for (Statement stmt : statements) {
661                Node predicate = stmt.getPredicate();
662                if (!res.contains(predicate)) {
663                    // remove duplicates
664                    res.add(predicate);
665                }
666            }
667            return res;
668        } finally {
669            if (graph != null) {
670                graph.leaveCriticalSection();
671            }
672            if (graphConnection != null) {
673                graphConnection.close();
674            }
675        }
676    }
677
678    @Override
679    public List<Node> getObjects(Node subject, Node predicate) {
680        Model graph = null;
681        GraphConnection graphConnection = null;
682        try {
683            graphConnection = openGraph();
684            graph = graphConnection.getGraph();
685            graph.enterCriticalSection(Lock.READ);
686            SimpleSelector selector = getJenaSelector(graph, new StatementImpl(subject, predicate, null));
687            NodeIterator it = graph.listObjectsOfProperty(selector.getSubject(), selector.getPredicate());
688            List<Node> res = new ArrayList<Node>();
689            while (it.hasNext()) {
690                res.add(getNXRelationsNode(it.nextNode().asNode()));
691            }
692            return res;
693        } finally {
694            if (graph != null) {
695                graph.leaveCriticalSection();
696            }
697            if (graphConnection != null) {
698                graphConnection.close();
699            }
700        }
701    }
702
703    @Override
704    public boolean hasStatement(Statement statement) {
705        if (statement == null) {
706            return false;
707        }
708        Model graph = null;
709        GraphConnection graphConnection = null;
710        try {
711            graphConnection = openGraph();
712            graph = graphConnection.getGraph();
713            graph.enterCriticalSection(Lock.READ);
714            SimpleSelector selector = getJenaSelector(graph, statement);
715            return graph.contains(selector.getSubject(), selector.getPredicate(), selector.getObject());
716        } finally {
717            if (graph != null) {
718                graph.leaveCriticalSection();
719            }
720            if (graphConnection != null) {
721                graphConnection.close();
722            }
723        }
724    }
725
726    @Override
727    public boolean hasResource(Resource resource) {
728        if (resource == null) {
729            return false;
730        }
731        Model graph = null;
732        GraphConnection graphConnection = null;
733        try {
734            graphConnection = openGraph();
735            graph = graphConnection.getGraph();
736            graph.enterCriticalSection(Lock.READ);
737            com.hp.hpl.jena.graph.Node jenaNodeInst = getJenaNode(resource);
738            RDFNode jenaNode = graph.asRDFNode(jenaNodeInst);
739            return graph.containsResource(jenaNode);
740        } finally {
741            if (graph != null) {
742                graph.leaveCriticalSection();
743            }
744            if (graphConnection != null) {
745                graphConnection.close();
746            }
747        }
748    }
749
750    /**
751     * Returns the number of statements in the graph.
752     * <p>
753     * XXX AT: this size may not be equal to the number of statements retrieved via getStatements() because it counts
754     * each statement property.
755     *
756     * @return integer number of statements in the graph
757     */
758    @Override
759    public Long size() {
760        Model graph = null;
761        GraphConnection graphConnection = null;
762        try {
763            graphConnection = openGraph();
764            graph = graphConnection.getGraph();
765            graph.enterCriticalSection(Lock.READ);
766            return graph.size();
767        } finally {
768            if (graph != null) {
769                graph.leaveCriticalSection();
770            }
771            if (graphConnection != null) {
772                graphConnection.close();
773            }
774        }
775    }
776
777    @Override
778    public void clear() {
779        Model graph = null;
780        GraphConnection graphConnection = null;
781        try {
782            graphConnection = openGraph();
783            graph = graphConnection.getGraph();
784            graph.enterCriticalSection(Lock.READ);
785            graph.removeAll();
786            // XXX AT: remove reification quadlets explicitly
787            RSIterator it = graph.listReifiedStatements();
788            List<ReifiedStatement> rss = new ArrayList<ReifiedStatement>();
789            while (it.hasNext()) {
790                rss.add(it.nextRS());
791            }
792            for (ReifiedStatement rs : rss) {
793                graph.removeReification(rs);
794            }
795        } finally {
796            if (graph != null) {
797                graph.leaveCriticalSection();
798            }
799            if (graphConnection != null) {
800                graphConnection.close();
801            }
802        }
803    }
804
805    @Override
806    public QueryResult query(String queryString, String language, String baseURI) {
807        Model graph = null;
808        GraphConnection graphConnection = null;
809        QueryResult res = null;
810        QueryExecution qe = null;
811        try {
812            graphConnection = openGraph();
813            graph = graphConnection.getGraph();
814            graph.enterCriticalSection(Lock.READ);
815            log.debug(String.format("Running query %s", queryString));
816            // XXX AT: ignore language for now
817            if (language != null && !language.equals("sparql")) {
818                log.warn(String.format("Unknown language %s for query, using SPARQL", language));
819            }
820            Query query = QueryFactory.create(queryString);
821            query.setBaseURI(baseURI);
822            qe = QueryExecutionFactory.create(query, graph);
823            res = new QueryResultImpl(0, new ArrayList<String>(), new ArrayList<Map<String, Node>>());
824            ResultSet jenaResults = qe.execSelect();
825            Integer count = 0;
826            List<String> variableNames = jenaResults.getResultVars();
827            List<Map<String, Node>> nuxResults = new ArrayList<Map<String, Node>>();
828            while (jenaResults.hasNext()) {
829                QuerySolution soln = jenaResults.nextSolution();
830                Map<String, Node> nuxSol = new HashMap<String, Node>();
831                for (String varName : variableNames) {
832                    RDFNode x = soln.get(varName);
833                    nuxSol.put(varName, getNXRelationsNode(x.asNode()));
834                }
835                nuxResults.add(nuxSol);
836                count++;
837            }
838            res = new QueryResultImpl(count, variableNames, nuxResults);
839        } finally {
840            if (qe != null) {
841                // Important - free up resources used running the query
842                qe.close();
843            }
844            if (graph != null) {
845                graph.leaveCriticalSection();
846            }
847            if (graphConnection != null) {
848                graphConnection.close();
849            }
850        }
851        return res;
852    }
853
854    @Override
855    public int queryCount(String queryString, String language, String baseURI) {
856        return query(queryString, language, baseURI).getResults().size();
857    }
858
859    @Override
860    public boolean read(InputStream in, String lang, String base) {
861        // XXX AT: maybe update namespaces in case some new appeared
862        Model graph = null;
863        GraphConnection graphConnection = null;
864        try {
865            graphConnection = openGraph();
866            graph = graphConnection.getGraph();
867            graph.enterCriticalSection(Lock.READ);
868            graph.read(in, base, lang);
869            // default to true
870            return true;
871        } finally {
872            if (graph != null) {
873                graph.leaveCriticalSection();
874            }
875            if (graphConnection != null) {
876                graphConnection.close();
877            }
878        }
879    }
880
881    @Override
882    public boolean read(String path, String lang, String base) {
883        // XXX AT: maybe update namespaces in case some new appeared
884        InputStream in = null;
885        try {
886            in = new FileInputStream(path);
887            return read(in, lang, base);
888        } catch (IOException e) {
889            throw new RuntimeException(e);
890        } finally {
891            if (in != null) {
892                try {
893                    in.close();
894                } catch (IOException e) {
895                }
896            }
897        }
898    }
899
900    @Override
901    public boolean write(OutputStream out, String lang, String base) {
902        Model graph = null;
903        GraphConnection graphConnection = null;
904        try {
905            graphConnection = openGraph();
906            graph = graphConnection.getGraph();
907            graph.enterCriticalSection(Lock.WRITE);
908            graph.write(out, lang, base);
909            // default to true
910            return true;
911        } finally {
912            if (graph != null) {
913                graph.leaveCriticalSection();
914            }
915            if (graphConnection != null) {
916                graphConnection.close();
917            }
918        }
919    }
920
921    @Override
922    public boolean write(String path, String lang, String base) {
923        OutputStream out = null;
924        try {
925            File file = new File(path);
926            out = new FileOutputStream(file);
927            return write(out, lang, base);
928        } catch (IOException e) {
929            throw new RuntimeException(e);
930        } finally {
931            if (out != null) {
932                try {
933                    out.close();
934                } catch (IOException e) {
935                }
936            }
937        }
938    }
939
940}
941
942/**
943 * This invocation handler is designed to wrap a normal connection but avoid all calls to
944 * <ul>
945 * <li>{@link Connection#commit}</li>
946 * <li>{@link Connection#setAutoCommit}</li>
947 * </ul>
948 * <p>
949 * We have to do this because Jena calls these methods without regard to the fact that the connection may be managed by
950 * an external transaction.
951 *
952 * @author Florent Guillaume
953 */
954
955class ConnectionFixInvocationHandler implements InvocationHandler {
956
957    private final Connection connection;
958
959    ConnectionFixInvocationHandler(Connection connection) {
960        this.connection = connection;
961    }
962
963    @Override
964    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
965        final String name = method.getName();
966        if (name.equals("commit")) {
967            return null;
968        } else if (name.equals("setAutoCommit")) {
969            return null;
970        } else {
971            try {
972                return method.invoke(connection, args);
973            } catch (InvocationTargetException e) {
974                throw e.getCause();
975            }
976        }
977    }
978}