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