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