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