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