Index: nuxeo-platform-relations-api/src/main/java/org/nuxeo/ecm/platform/relations/api/Graph.java =================================================================== --- nuxeo-platform-relations-api/src/main/java/org/nuxeo/ecm/platform/relations/api/Graph.java (revision 25385) +++ nuxeo-platform-relations-api/src/main/java/org/nuxeo/ecm/platform/relations/api/Graph.java (working copy) @@ -240,4 +240,14 @@ */ boolean write(OutputStream out, String lang, String base); + /** + * Returns true if graph needs to be shared by the service. + *

+ * This is supposed to happen in restricted use cases, for instance when + * dealing with memory graphs in tests. + * + * @return true if graph is shared. + */ + boolean isShared(); + } Index: nuxeo-platform-relations-core/src/test/java/org/nuxeo/ecm/platform/relations/DummyGraphType.java =================================================================== --- nuxeo-platform-relations-core/src/test/java/org/nuxeo/ecm/platform/relations/DummyGraphType.java (revision 25385) +++ nuxeo-platform-relations-core/src/test/java/org/nuxeo/ecm/platform/relations/DummyGraphType.java (working copy) @@ -133,4 +133,8 @@ public void remove(List statements) { } + public boolean isShared() { + return false; + } + } Index: nuxeo-platform-relations-core/src/main/java/org/nuxeo/ecm/platform/relations/services/RelationService.java =================================================================== --- nuxeo-platform-relations-core/src/main/java/org/nuxeo/ecm/platform/relations/services/RelationService.java (revision 25385) +++ nuxeo-platform-relations-core/src/main/java/org/nuxeo/ecm/platform/relations/services/RelationService.java (working copy) @@ -68,7 +68,8 @@ private final Map graphDescriptionRegistry; - private final Map graphRegistry; + // registry of memory graphs + private transient final Map graphRegistry; private final Map resourceAdapterRegistry; @@ -316,9 +317,11 @@ graph.setName(name); graph.setOptions(options); graph.setNamespaces(namespaces); - // put it in registry for later retrieval - graphRegistry.put(name, graph); - + // put it in registry for later retrieval if it is shared (happens for + // memory graphs in tests) + if (graph.isShared()) { + graphRegistry.put(name, graph); + } return graph; } @@ -346,7 +349,7 @@ public Set getAllResources(Object object) { // TODO OPTIM implement reverse map in registerContribution Set res = new HashSet(); - for (String ns: resourceAdapterRegistry.keySet()) { + for (String ns : resourceAdapterRegistry.keySet()) { ResourceAdapter adapter = getResourceAdapterForNamespace(ns); if (adapter == null) { continue; Index: nuxeo-platform-relations-facade/src/main/java/org/nuxeo/ecm/platform/relation/ejb/RelationManagerBean.java =================================================================== --- nuxeo-platform-relations-facade/src/main/java/org/nuxeo/ecm/platform/relation/ejb/RelationManagerBean.java (revision 25385) +++ nuxeo-platform-relations-facade/src/main/java/org/nuxeo/ecm/platform/relation/ejb/RelationManagerBean.java (working copy) @@ -21,7 +21,9 @@ import java.io.InputStream; import java.io.OutputStream; +import java.util.Hashtable; import java.util.List; +import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; @@ -60,6 +62,11 @@ private static final Log log = LogFactory.getLog(RelationManagerBean.class); + /** + * registry of graphs; session bean ensures that there is only one per user + */ + private Map graphRegistry; + private transient RelationManager service; @PostActivate @@ -68,6 +75,7 @@ try { // get Runtime service service = Framework.getRuntime().getService(RelationManager.class); + graphRegistry = new Hashtable(); } catch (Exception e) { log.error("Could not get relation service", e); } @@ -81,7 +89,14 @@ // TODO: maybe hack here to get graph in a cleaner way public Graph getGraphByName(String name) throws ClientException { try { - Graph graph = service.getGraphByName(name); + Graph graph = graphRegistry.get(name); + if (graph == null) { + graph = service.getGraphByName(name); + } + if (graph != null) { + // store it for later retrieval + graphRegistry.put(name, graph); + } return graph; } catch (Throwable t) { throw EJBExceptionHandler.wrapException(t); Index: nuxeo-platform-relations-jena-plugin/src/main/java/org/nuxeo/ecm/platform/relations/jena/JenaGraph.java =================================================================== --- nuxeo-platform-relations-jena-plugin/src/main/java/org/nuxeo/ecm/platform/relations/jena/JenaGraph.java (revision 25385) +++ nuxeo-platform-relations-jena-plugin/src/main/java/org/nuxeo/ecm/platform/relations/jena/JenaGraph.java (working copy) @@ -72,6 +72,7 @@ import com.hp.hpl.jena.rdf.model.ResIterator; import com.hp.hpl.jena.rdf.model.SimpleSelector; import com.hp.hpl.jena.rdf.model.StmtIterator; +import com.hp.hpl.jena.shared.Lock; /** * Jena plugin for NXRelations. @@ -88,6 +89,8 @@ private static final Log log = LogFactory.getLog(JenaGraph.class); + private transient DBConnection connection; + private transient Model graph; private String name; @@ -121,6 +124,7 @@ protected RuntimeException wrapException(Exception e) { // force reload of graph when an error occurs + connection = null; graph = null; return new RuntimeException(e); } @@ -144,14 +148,13 @@ * using options * @return the Jena graph (model) */ - protected Model getGraph(boolean forceReload) { - if (this.graph == null || forceReload) { + protected synchronized Model getGraph(boolean forceReload) { + if (graph == null || forceReload) { // create model given backend if (backend.equals("memory")) { - this.graph = ModelFactory.createDefaultModel(ModelFactory.Convenient); + graph = ModelFactory.createDefaultModel(ModelFactory.Convenient); } else if (backend.equals("sql")) { // create a database connection - DBConnection conn = null; if (datasource != null) { try { InitialContext context = new InitialContext(); @@ -161,7 +164,7 @@ // so ask the metadata to produce the connection so that // it is unwrapped and handles its own transaction. DatabaseMetaData md = wrapped.getMetaData(); - conn = new DBConnection(md.getConnection(), + connection = new DBConnection(md.getConnection(), databaseType); } catch (NamingException e) { throw new IllegalArgumentException(String.format( @@ -180,52 +183,52 @@ "Database driver class %s not found", databaseDriverClass)); } - conn = new DBConnection(databaseUrl, databaseUser, + connection = new DBConnection(databaseUrl, databaseUser, databasePassword, databaseType); } // check if named model already exists - if (conn.containsModel(name)) { - ModelMaker m = ModelFactory.createModelRDBMaker(conn, + if (connection.containsModel(name)) { + ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient); - this.graph = m.openModel(name); + graph = m.openModel(name); } else { // create it // check if other models already exist for that connection. - if (conn.getAllModelNames().hasNext()) { + if (connection.getAllModelNames().hasNext()) { // other models already exist => do not set parameters // on driver. - if (databaseDoCompressUri != conn.getDriver().getDoCompressURI()) { + if (databaseDoCompressUri != connection.getDriver().getDoCompressURI()) { log.warn(String.format( "Cannot set databaseDoCompressUri attribute to %s " + "for model %s, other models already " + "exist with value %s", databaseDoCompressUri, name, - conn.getDriver().getDoCompressURI())); + connection.getDriver().getDoCompressURI())); } - if (databaseTransactionEnabled != conn.getDriver().getIsTransactionDb()) { + if (databaseTransactionEnabled != connection.getDriver().getIsTransactionDb()) { log.warn(String.format( "Cannot set databaseTransactionEnabled attribute to %s " + "for model %s, other models already " + "exist with value %s", databaseTransactionEnabled, name, - conn.getDriver().getIsTransactionDb())); + connection.getDriver().getIsTransactionDb())); } } else { if (databaseDoCompressUri) { - conn.getDriver().setDoCompressURI(true); + connection.getDriver().setDoCompressURI(true); } if (databaseTransactionEnabled) { - conn.getDriver().setIsTransactionDb(true); + connection.getDriver().setIsTransactionDb(true); } } - ModelMaker m = ModelFactory.createModelRDBMaker(conn, + ModelMaker m = ModelFactory.createModelRDBMaker(connection, ModelFactory.Convenient); - this.graph = m.createModel(name); + graph = m.createModel(name); } } - this.graph.setNsPrefixes(this.namespaces); + graph.setNsPrefixes(this.namespaces); } - return this.graph; + return graph; } /** @@ -335,11 +338,11 @@ /** * Gets Jena statement selector corresponding to the NXRelations statement. * + * @param graph the jena graph * @param nuxStatement NXRelations statement * @return jena statement selector */ - private SimpleSelector getJenaSelector(Statement nuxStatement) { - Model graph = getGraph(); + private SimpleSelector getJenaSelector(Model graph, Statement nuxStatement) { com.hp.hpl.jena.rdf.model.Resource subjResource = null; com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStatement.getSubject()); if (subject != null && subject.isURI()) { @@ -364,12 +367,12 @@ * Reified statements may be retrieved from the Jena graph and set as * properties on NXRelations statements. * + * @param graph the jena graph * @param jenaStatement jena statement * @return NXRelations statement */ - private Statement getNXRelationsStatement( + private Statement getNXRelationsStatement(Model graph, com.hp.hpl.jena.rdf.model.Statement jenaStatement) { - Model graph = getGraph(); Node subject = getNXRelationsNode(jenaStatement.getSubject().asNode()); Node predicate = getNXRelationsNode(jenaStatement.getPredicate().asNode()); Node object = getNXRelationsNode(jenaStatement.getObject().asNode()); @@ -394,15 +397,16 @@ /** * Gets NXRelations statement list corresponding to the Jena statement list. * + * @param graph the jena graph * @param jenaStatements jena statements list * @return NXRelations statements list */ - private List getNXRelationsStatements( + private List getNXRelationsStatements(Model graph, List jenaStatements) { List nuxStmts = new ArrayList(); for (com.hp.hpl.jena.rdf.model.Statement jenaStmt : jenaStatements) { if (!jenaStmt.getSubject().isAnon()) { - nuxStmts.add(getNXRelationsStatement(jenaStmt)); + nuxStmts.add(getNXRelationsStatement(graph, jenaStmt)); } } return nuxStmts; @@ -471,8 +475,10 @@ } public void add(List statements) { + Model graph = null; try { - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.WRITE); for (Statement nuxStmt : statements) { com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject()); com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate()); @@ -505,12 +511,18 @@ } } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } public void remove(List statements) { + Model graph = null; try { - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.WRITE); for (Statement nuxStmt : statements) { com.hp.hpl.jena.graph.Node subject = getJenaNode(nuxStmt.getSubject()); com.hp.hpl.jena.graph.Node predicate = getJenaNode(nuxStmt.getPredicate()); @@ -530,38 +542,56 @@ } } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @SuppressWarnings("unchecked") public List getStatements() { + Model graph = null; try { - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); StmtIterator it = graph.listStatements(); - return getNXRelationsStatements(it.toList()); + return getNXRelationsStatements(graph, it.toList()); } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @SuppressWarnings("unchecked") public List getStatements(Statement statement) { + Model graph = null; try { - SimpleSelector selector = getJenaSelector(statement); - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + SimpleSelector selector = getJenaSelector(graph, statement); StmtIterator it = graph.listStatements(selector); - return getNXRelationsStatements(it.toList()); + return getNXRelationsStatements(graph, it.toList()); } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @SuppressWarnings("unchecked") public List getSubjects(Node predicate, Node object) { + Model graph = null; try { - Model graph = getGraph(); - SimpleSelector selector = getJenaSelector(new StatementImpl(null, - predicate, object)); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + SimpleSelector selector = getJenaSelector(graph, new StatementImpl( + null, predicate, object)); ResIterator it = graph.listSubjectsWithProperty( selector.getPredicate(), selector.getObject()); List res = new ArrayList(); @@ -571,17 +601,24 @@ return res; } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @SuppressWarnings("unchecked") public List getPredicates(Node subject, Node object) { + Model graph = null; try { - SimpleSelector selector = getJenaSelector(new StatementImpl( + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + SimpleSelector selector = getJenaSelector(graph, new StatementImpl( subject, null, object)); - Model graph = getGraph(); StmtIterator it = graph.listStatements(selector); - List statements = getNXRelationsStatements(it.toList()); + List statements = getNXRelationsStatements(graph, + it.toList()); List res = new ArrayList(); for (Statement stmt : statements) { Node predicate = stmt.getPredicate(); @@ -593,13 +630,19 @@ return res; } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } public List getObjects(Node subject, Node predicate) { + Model graph = null; try { - Model graph = getGraph(); - SimpleSelector selector = getJenaSelector(new StatementImpl( + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + SimpleSelector selector = getJenaSelector(graph, new StatementImpl( subject, predicate, null)); NodeIterator it = graph.listObjectsOfProperty( selector.getSubject(), selector.getPredicate()); @@ -610,6 +653,10 @@ return res; } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @@ -617,27 +664,39 @@ if (statement == null) { return false; } + Model graph = null; try { - Model graph = getGraph(); - SimpleSelector selector = getJenaSelector(statement); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + SimpleSelector selector = getJenaSelector(graph, statement); return graph.contains(selector.getSubject(), selector.getPredicate(), selector.getObject()); } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } public boolean hasResource(Resource resource) { + if (resource == null) { + return false; + } + Model graph = null; try { - if (resource == null) { - return false; - } - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); com.hp.hpl.jena.graph.Node jenaNodeInst = getJenaNode(resource); RDFNode jenaNode = graph.asRDFNode(jenaNodeInst); return graph.containsResource(jenaNode); } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @@ -650,38 +709,52 @@ * @return integer number of statements in the graph */ public Long size() { + Model graph = null; try { - Model jenaGraph = getGraph(); - return jenaGraph.size(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + return graph.size(); } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } - } public void clear() { + Model graph = null; try { - Model jenaGraph = getGraph(); - jenaGraph.removeAll(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); + graph.removeAll(); // XXX AT: remove reification quadlets explicitely - RSIterator it = jenaGraph.listReifiedStatements(); + RSIterator it = graph.listReifiedStatements(); List rss = new ArrayList(); while (it.hasNext()) { rss.add(it.nextRS()); } for (ReifiedStatement rs : rss) { - jenaGraph.removeReification(rs); + graph.removeReification(rs); } } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @SuppressWarnings("unchecked") public QueryResult query(String queryString, String language, String baseURI) { + Model graph = null; QueryResult res = null; QueryExecution qe = null; try { + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); log.debug(String.format("Running query %s", queryString)); // XXX AT: ignore language for now if (language != null && !language.equals("sparql")) { @@ -690,7 +763,7 @@ } Query query = QueryFactory.create(queryString); query.setBaseURI(baseURI); - qe = QueryExecutionFactory.create(query, getGraph()); + qe = QueryExecutionFactory.create(query, graph); res = new QueryResultImpl(0, new ArrayList(), new ArrayList>()); ResultSet jenaResults = qe.execSelect(); @@ -715,20 +788,29 @@ // Important - free up resources used running the query qe.close(); } + if (graph != null) { + graph.leaveCriticalSection(); + } } return res; } public boolean read(InputStream in, String lang, String base) { // XXX AT: maybe update namespaces in case some new appeared + Model graph = null; try { - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.READ); // dunno why base and lang are inverted here graph.read(in, base, lang); // default to true return true; } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @@ -751,13 +833,19 @@ } public boolean write(OutputStream out, String lang, String base) { + Model graph = null; try { - Model graph = getGraph(); + graph = getGraph(); + graph.enterCriticalSection(Lock.WRITE); graph.write(out, lang, base); // default to true return true; } catch (Exception e) { throw wrapException(e); + } finally { + if (graph != null) { + graph.leaveCriticalSection(); + } } } @@ -766,8 +854,7 @@ try { File file = new File(path); out = new FileOutputStream(file); - graph.write(out, lang, base); - return true; + return write(out, lang, base); } catch (Exception e) { throw wrapException(e); } finally { @@ -780,4 +867,38 @@ } } + public boolean isShared() { + return "memory".equals(backend); + } + + // serialization + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + // close connection before writing + if (connection != null) { + try { + connection.close(); + connection = null; + } catch (SQLException e) { + log.error("Could not close connection when serializing"); + } + } + out.defaultWriteObject(); + } + + // destructor + + @Override + protected void finalize() throws Throwable { + // close connection if needed + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + log.error("Could not close connection when deleting"); + } + } + super.finalize(); + } + }