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}