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