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