001/* 002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 */ 017package org.nuxeo.ecm.platform.relations; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileNotFoundException; 022import java.io.FileOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.io.Serializable; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Calendar; 030import java.util.Collections; 031import java.util.Date; 032import java.util.HashMap; 033import java.util.LinkedHashSet; 034import java.util.List; 035import java.util.Map; 036import java.util.Map.Entry; 037import java.util.regex.Matcher; 038import java.util.regex.Pattern; 039 040import org.apache.commons.lang.StringUtils; 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.core.api.CoreSession; 044import org.nuxeo.ecm.core.api.DocumentModel; 045import org.nuxeo.ecm.core.api.IdRef; 046import org.nuxeo.ecm.core.api.IterableQueryResult; 047import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 048import org.nuxeo.ecm.core.api.repository.RepositoryManager; 049import org.nuxeo.ecm.core.query.sql.NXQL; 050import org.nuxeo.ecm.core.schema.DocumentType; 051import org.nuxeo.ecm.core.schema.SchemaManager; 052import org.nuxeo.ecm.core.schema.types.Type; 053import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder; 054import org.nuxeo.ecm.platform.relations.api.Blank; 055import org.nuxeo.ecm.platform.relations.api.Graph; 056import org.nuxeo.ecm.platform.relations.api.GraphDescription; 057import org.nuxeo.ecm.platform.relations.api.Literal; 058import org.nuxeo.ecm.platform.relations.api.Node; 059import org.nuxeo.ecm.platform.relations.api.NodeType; 060import org.nuxeo.ecm.platform.relations.api.QNameResource; 061import org.nuxeo.ecm.platform.relations.api.QueryResult; 062import org.nuxeo.ecm.platform.relations.api.Resource; 063import org.nuxeo.ecm.platform.relations.api.Statement; 064import org.nuxeo.ecm.platform.relations.api.Subject; 065import org.nuxeo.ecm.platform.relations.api.impl.AbstractNode; 066import org.nuxeo.ecm.platform.relations.api.impl.NodeFactory; 067import org.nuxeo.ecm.platform.relations.api.impl.QueryResultImpl; 068import org.nuxeo.ecm.platform.relations.api.impl.RelationDate; 069import org.nuxeo.ecm.platform.relations.api.impl.StatementImpl; 070import org.nuxeo.ecm.platform.relations.api.util.RelationConstants; 071import org.nuxeo.runtime.api.Framework; 072 073/** 074 * Relation graph implementation delegating to the core. 075 */ 076public class CoreGraph implements Graph { 077 078 private static final long serialVersionUID = 1L; 079 080 private static final Log log = LogFactory.getLog(CoreGraph.class); 081 082 public static final String OPTION_DOCTYPE = "doctype"; 083 084 public static final String REL_TYPE = "Relation"; 085 086 public static final String REL_PREDICATE = "relation:predicate"; 087 088 public static final String REL_SOURCE_ID = "relation:source"; 089 090 public static final String REL_SOURCE_URI = "relation:sourceUri"; 091 092 public static final String REL_TARGET_ID = "relation:target"; 093 094 public static final String REL_TARGET_URI = "relation:targetUri"; 095 096 public static final String REL_TARGET_STRING = "relation:targetString"; 097 098 public static final String DC_CREATED = "dc:created"; 099 100 public static final String DC_CREATOR = "dc:creator"; 101 102 public static final String DC_MODIFIED = "dc:modified"; 103 104 public static final String DC_TITLE = "dc:title"; 105 106 public static final String DC_DESCRIPTION = "dc:description"; 107 108 // avoid confusion with any legal uri 109 public static String BLANK_NS = "-:"; 110 111 public static String DOCUMENT_NAMESPACE = RelationConstants.DOCUMENT_NAMESPACE; 112 113 /** Without final slash (compat). */ 114 public static String DOCUMENT_NAMESPACE2 = DOCUMENT_NAMESPACE.substring(0, DOCUMENT_NAMESPACE.length() - 1); 115 116 /** Has no final slash (compat). */ 117 public static final String COMMENT_NAMESPACE = "http://www.nuxeo.org/comments/uid"; 118 119 public static final String[] DOC_NAMESPACES = { DOCUMENT_NAMESPACE, DOCUMENT_NAMESPACE2, COMMENT_NAMESPACE }; 120 121 protected static final List<Statement> EMPTY_STATEMENTS = Collections.emptyList(); 122 123 protected static final Statement ALL = new StatementImpl(null, null, null); 124 125 protected CoreSession session; 126 127 protected String name; 128 129 protected String docType = REL_TYPE; 130 131 public Map<String, String> namespaces; 132 133 public List<String> namespaceList = Collections.emptyList(); 134 135 /** Only one of those is filled. */ 136 protected static class NodeAsString { 137 public String id; 138 139 public String uri; 140 141 public String string; 142 } 143 144 /** 145 * A graph with this base session. An unrestricted session will be opened based on it. 146 */ 147 public CoreGraph(CoreSession session) { 148 this.session = session; 149 } 150 151 @Override 152 public void setDescription(GraphDescription graphDescription) { 153 name = graphDescription.getName(); 154 setOptions(graphDescription.getOptions()); 155 namespaces = graphDescription.getNamespaces(); 156 namespaceList = namespaces == null ? Collections.<String> emptyList() : new ArrayList<String>( 157 new LinkedHashSet<String>(namespaces.values())); 158 } 159 160 protected void setOptions(Map<String, String> options) { 161 for (Entry<String, String> option : options.entrySet()) { 162 String key = option.getKey(); 163 String type = option.getValue(); 164 if (key.equals(OPTION_DOCTYPE)) { 165 SchemaManager sm = Framework.getLocalService(SchemaManager.class); 166 DocumentType documentType = sm.getDocumentType(type); 167 if (documentType == null) { 168 throw new IllegalArgumentException("Unknown type: " + type + " for graph: " + name); 169 } 170 Type[] th = documentType.getTypeHierarchy(); 171 String baseType = th.length == 0 ? type : th[th.length - 1].getName(); 172 if (!REL_TYPE.equals(baseType)) { 173 throw new IllegalArgumentException("Not a Relation type: " + type + " for graph: " + name); 174 } 175 docType = type; 176 } 177 } 178 } 179 180 @Override 181 public Map<String, String> getNamespaces() { 182 return namespaces; 183 } 184 185 @Override 186 public Long size() { 187 SizeFinder sizeFinder = session == null ? new SizeFinder() : new SizeFinder(session); 188 sizeFinder.runUnrestricted(); 189 return Long.valueOf(sizeFinder.size); 190 } 191 192 protected class SizeFinder extends UnrestrictedSessionRunner { 193 194 protected long size; 195 196 protected SizeFinder() { 197 super(getDefaultRepositoryName()); 198 } 199 200 protected SizeFinder(CoreSession session) { 201 super(session); 202 } 203 204 @Override 205 public void run() { 206 // TODO could use a COUNT(*) query 207 IterableQueryResult it = session.queryAndFetch("SELECT " + NXQL.ECM_UUID + " FROM " + docType, NXQL.NXQL); 208 try { 209 size = it.size(); 210 } finally { 211 it.close(); 212 } 213 } 214 } 215 216 @Override 217 public void clear() { 218 remove(Collections.singletonList(ALL)); 219 } 220 221 @Override 222 public void add(Statement statement) { 223 add(Collections.singletonList(statement)); 224 } 225 226 @Override 227 public void add(List<Statement> statements) { 228 StatementAdder statementAdder = session == null ? new StatementAdder(statements) : new StatementAdder( 229 statements, session); 230 statementAdder.runUnrestricted(); 231 } 232 233 protected class StatementAdder extends UnrestrictedSessionRunner { 234 235 protected List<Statement> statements; 236 237 protected Date now; 238 239 protected StatementAdder(List<Statement> statements) { 240 super(getDefaultRepositoryName(), "system"); 241 this.statements = statements; 242 } 243 244 protected StatementAdder(List<Statement> statements, CoreSession session) { 245 super(session); 246 this.statements = statements; 247 } 248 249 @Override 250 public void run() { 251 now = new Date(); 252 for (Statement statement : statements) { 253 add(statement); 254 } 255 session.save(); 256 } 257 258 protected void add(Statement statement) { 259 DocumentModel rel = session.createDocumentModel(null, "relation", docType); 260 rel = setRelationProperties(rel, statement); 261 session.createDocument(rel); 262 } 263 264 protected DocumentModel setRelationProperties(DocumentModel rel, Statement statement) { 265 Resource pred = statement.getPredicate(); 266 NodeAsString predicate = getNodeAsString(pred); 267 if (predicate.uri == null) { 268 throw new IllegalArgumentException("Invalid predicate in statement: " + statement); 269 } 270 271 Subject subject = statement.getSubject(); 272 if (subject.isLiteral()) { 273 throw new IllegalArgumentException("Invalid literal subject in statement: " + statement); 274 } 275 NodeAsString source = getNodeAsString(subject); 276 277 Node object = statement.getObject(); 278 NodeAsString target = getNodeAsString(object); 279 280 String author = getAuthor(statement); 281 if (author == null) { 282 author = getOriginatingUsername(); 283 } 284 285 Date created = getCreationDate(statement); 286 if (created == null) { 287 created = now; 288 } 289 290 Date modified = getModificationDate(statement); 291 if (modified == null) { 292 modified = now; 293 } 294 295 String comment = getComment(statement); 296 297 String title = (source.id != null ? source.id : source.uri) + " " 298 + predicate.uri.substring(predicate.uri.lastIndexOf('/') + 1) + " " 299 + (target.id != null ? target.id : target.uri != null ? target.uri : target.string); 300 int MAX_TITLE = 200; 301 if (title.length() > MAX_TITLE) { 302 title = title.substring(0, MAX_TITLE); 303 } 304 305 rel.setPropertyValue(REL_PREDICATE, predicate.uri); 306 if (source.id != null) { 307 rel.setPropertyValue(REL_SOURCE_ID, source.id); 308 } else { 309 rel.setPropertyValue(REL_SOURCE_URI, source.uri); 310 } 311 if (target.id != null) { 312 rel.setPropertyValue(REL_TARGET_ID, target.id); 313 } else if (target.uri != null) { 314 rel.setPropertyValue(REL_TARGET_URI, target.uri); 315 } else { 316 rel.setPropertyValue(REL_TARGET_STRING, target.string); 317 } 318 if (author != null) { 319 // will usually get overwritten by DublinCoreListener 320 // but not in tests 321 rel.setPropertyValue(DC_CREATOR, author); 322 } 323 if (created != null) { 324 // will usually get overwritten by DublinCoreListener 325 // but not in tests 326 rel.setPropertyValue(DC_CREATED, created); 327 } 328 if (modified != null) { 329 // will usually get overwritten by DublinCoreListener 330 // but not in tests 331 rel.setPropertyValue(DC_MODIFIED, modified); 332 } 333 rel.setPropertyValue(DC_TITLE, title); // for debug 334 if (comment != null) { 335 rel.setPropertyValue(DC_DESCRIPTION, comment); 336 } 337 return rel; 338 } 339 } 340 341 @Override 342 public void remove(Statement statement) { 343 remove(Collections.singletonList(statement)); 344 } 345 346 @Override 347 public void remove(List<Statement> statements) { 348 StatementRemover statementRemover = session == null ? new StatementRemover(statements) : new StatementRemover( 349 statements, session); 350 statementRemover.runUnrestricted(); 351 } 352 353 protected class StatementRemover extends UnrestrictedSessionRunner { 354 355 protected List<Statement> statements; 356 357 protected Date now; 358 359 protected StatementRemover(List<Statement> statements) { 360 super(getDefaultRepositoryName()); 361 this.statements = statements; 362 } 363 364 protected StatementRemover(List<Statement> statements, CoreSession session) { 365 super(session); 366 this.statements = statements; 367 } 368 369 @Override 370 public void run() { 371 now = new Date(); 372 for (Statement statement : statements) { 373 remove(statement); 374 } 375 } 376 377 protected void remove(Statement statement) { 378 String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; 379 query = whereBuilder(query, statement); 380 if (query == null) { 381 return; 382 } 383 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 384 try { 385 for (Map<String, Serializable> map : it) { 386 String id = (String) map.get(NXQL.ECM_UUID); 387 session.removeDocument(new IdRef(id)); 388 } 389 } finally { 390 it.close(); 391 } 392 } 393 } 394 395 protected class StatementFinder extends UnrestrictedSessionRunner { 396 397 protected List<Statement> statements; 398 399 protected Statement statement; 400 401 protected StatementFinder(Statement statement) { 402 super(getDefaultRepositoryName()); 403 this.statement = statement; 404 } 405 406 protected StatementFinder(Statement statement, CoreSession session) { 407 super(session); 408 this.statement = statement; 409 } 410 411 @Override 412 public void run() { 413 String query = "SELECT " + REL_PREDICATE + ", " + REL_SOURCE_ID + ", " + REL_SOURCE_URI + ", " 414 + REL_TARGET_ID + ", " + REL_TARGET_URI + ", " + REL_TARGET_STRING + ", " + DC_CREATED + ", " 415 + DC_CREATOR + ", " + DC_MODIFIED + ", " + DC_DESCRIPTION + " FROM " + docType; 416 query = whereBuilder(query, statement); 417 if (query == null) { 418 statements = EMPTY_STATEMENTS; 419 return; 420 } 421 statements = new ArrayList<Statement>(); 422 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 423 try { 424 for (Map<String, Serializable> map : it) { 425 String pred = (String) map.get(REL_PREDICATE); 426 String source = (String) map.get(REL_SOURCE_ID); 427 String sourceUri = (String) map.get(REL_SOURCE_URI); 428 String target = (String) map.get(REL_TARGET_ID); 429 String targetUri = (String) map.get(REL_TARGET_URI); 430 String targetString = (String) map.get(REL_TARGET_STRING); 431 Calendar created = (Calendar) map.get(DC_CREATED); 432 String creator = (String) map.get(DC_CREATOR); 433 Calendar modified = (Calendar) map.get(DC_MODIFIED); 434 String comment = (String) map.get(DC_DESCRIPTION); 435 436 Resource predicate = NodeFactory.createResource(pred); 437 Node subject; 438 if (source != null) { 439 subject = createId(source); 440 } else { 441 subject = createUri(sourceUri); 442 } 443 Node object; 444 if (target != null) { 445 object = createId(target); 446 } else if (targetUri != null) { 447 object = createUri(targetUri); 448 } else { 449 object = NodeFactory.createLiteral(targetString); 450 } 451 Statement statement = new StatementImpl(subject, predicate, object); 452 setCreationDate(statement, created); 453 setAuthor(statement, creator); 454 setModificationDate(statement, modified); 455 setComment(statement, comment); 456 statements.add(statement); 457 } 458 } finally { 459 it.close(); 460 } 461 } 462 463 protected QNameResource createId(String id) { 464 return NodeFactory.createQNameResource(DOCUMENT_NAMESPACE, session.getRepositoryName() + '/' + id); 465 } 466 467 protected Node createUri(String uri) { 468 if (uri.startsWith(BLANK_NS)) { 469 // skolemization 470 String id = uri.substring(BLANK_NS.length()); 471 return NodeFactory.createBlank(id.isEmpty() ? null : id); 472 } else { 473 for (String ns : namespaceList) { 474 if (uri.startsWith(ns)) { 475 String localName = uri.substring(ns.length()); 476 return NodeFactory.createQNameResource(ns, localName); 477 } 478 } 479 return NodeFactory.createResource(uri); 480 } 481 } 482 483 } 484 485 @Override 486 public List<Statement> getStatements() { 487 return getStatements(ALL); 488 } 489 490 @Override 491 public List<Statement> getStatements(Node subject, Node predicate, Node object) { 492 return getStatements(new StatementImpl(subject, predicate, object)); 493 } 494 495 @Override 496 public List<Statement> getStatements(Statement statement) { 497 StatementFinder statementFinder = session == null ? new StatementFinder(statement) : new StatementFinder( 498 statement, session); 499 statementFinder.runUnrestricted(); 500 return statementFinder.statements; 501 } 502 503 @Override 504 public List<Node> getSubjects(Node predicate, Node object) { 505 List<Statement> statements = getStatements(new StatementImpl(null, predicate, object)); 506 List<Node> nodes = new ArrayList<Node>(statements.size()); 507 for (Statement statement : statements) { 508 nodes.add(statement.getSubject()); 509 } 510 return nodes; 511 } 512 513 @Override 514 public List<Node> getPredicates(Node subject, Node object) { 515 List<Statement> statements = getStatements(new StatementImpl(subject, null, object)); 516 List<Node> nodes = new ArrayList<Node>(statements.size()); 517 for (Statement statement : statements) { 518 nodes.add(statement.getPredicate()); 519 } 520 return nodes; 521 } 522 523 @Override 524 public List<Node> getObjects(Node subject, Node predicate) { 525 List<Statement> statements = getStatements(new StatementImpl(subject, predicate, null)); 526 List<Node> nodes = new ArrayList<Node>(statements.size()); 527 for (Statement statement : statements) { 528 nodes.add(statement.getObject()); 529 } 530 return nodes; 531 } 532 533 @Override 534 public boolean hasStatement(Statement statement) { 535 if (statement == null) { 536 return false; 537 } 538 // could be optimized in the null/blank case, but this method seems 539 // unused 540 return !getStatements(statement).isEmpty(); 541 } 542 543 @Override 544 public boolean hasResource(Resource resource) { 545 if (resource == null) { 546 return false; 547 } 548 ResourceFinder resourceFinder = session == null ? new ResourceFinder(resource) : new ResourceFinder(resource, 549 session); 550 resourceFinder.runUnrestricted(); 551 return resourceFinder.found; 552 } 553 554 protected class ResourceFinder extends UnrestrictedSessionRunner { 555 556 protected boolean found; 557 558 protected Resource resource; 559 560 protected ResourceFinder(Resource resource) { 561 super(getDefaultRepositoryName()); 562 this.resource = resource; 563 } 564 565 protected ResourceFinder(Resource resource, CoreSession session) { 566 super(session); 567 this.resource = resource; 568 } 569 570 @Override 571 public void run() { 572 String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; 573 query = whereAnyBuilder(query, resource); 574 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 575 try { 576 found = it.iterator().hasNext(); 577 } finally { 578 it.close(); 579 } 580 } 581 582 protected String whereAnyBuilder(String query, Resource resource) { 583 List<Object> params = new ArrayList<Object>(3); 584 List<String> clauses = new ArrayList<String>(3); 585 586 NodeAsString nas = getNodeAsString(resource); 587 if (nas.id != null) { 588 // don't allow predicates that are actually doc ids 589 clauses.add(REL_SOURCE_ID + " = ?"); 590 params.add(nas.id); 591 clauses.add(REL_TARGET_URI + " = ?"); 592 params.add(DOCUMENT_NAMESPACE + session.getRepositoryName() + '/' + nas.id); 593 } else if (nas.uri != null) { 594 for (String ns : DOC_NAMESPACES) { 595 if (nas.uri.startsWith(ns)) { 596 String id = localNameToId(nas.uri.substring(ns.length())); 597 clauses.add(REL_SOURCE_ID + " = ?"); 598 params.add(id); 599 break; 600 } 601 } 602 clauses.add(REL_SOURCE_URI + " = ?"); 603 params.add(nas.uri); 604 clauses.add(REL_TARGET_URI + " = ?"); 605 params.add(nas.uri); 606 clauses.add(REL_PREDICATE + " = ?"); 607 params.add(nas.uri); 608 } 609 query += " WHERE " + StringUtils.join(clauses, " OR "); 610 query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); 611 return query; 612 } 613 } 614 615 public static final Pattern SPARQL_SPO_PO = Pattern.compile("SELECT \\?s \\?p \\?o WHERE \\{ \\?s \\?p \\?o . \\?s <(.*)> <(.*)> . \\}"); 616 617 public static final Pattern SPARQL_PO_S = Pattern.compile("SELECT \\?p \\?o WHERE \\{ <(.*)> \\?p \\?o \\}"); 618 619 @Override 620 public QueryResult query(String queryString, String language, String baseURI) { 621 // language is ignored, assume SPARQL 622 Matcher matcher = SPARQL_SPO_PO.matcher(queryString); 623 if (matcher.matches()) { 624 Node predicate = NodeFactory.createResource(matcher.group(1)); 625 Node object = NodeFactory.createResource(matcher.group(2)); 626 // find subjects with this predicate and object 627 List<Node> subjects = getSubjects(predicate, object); 628 List<Map<String, Node>> results = new ArrayList<>(); 629 if (!subjects.isEmpty()) { 630 // find all statements with these subjects 631 List<Statement> statements = getStatements(new Subjects(subjects), null, null); 632 for (Statement st : statements) { 633 Map<String, Node> map = new HashMap<>(); 634 map.put("s", st.getSubject()); 635 map.put("p", st.getPredicate()); 636 map.put("o", st.getObject()); 637 results.add(map); 638 } 639 } 640 return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("s", "p", "o"), results); 641 } 642 matcher = SPARQL_PO_S.matcher(queryString); 643 if (matcher.matches()) { 644 Node subject = NodeFactory.createResource(matcher.group(1)); 645 // find predicates and objects with this subject 646 List<Statement> statements = getStatements(new StatementImpl(subject, null, null)); 647 List<Map<String, Node>> results = new ArrayList<>(); 648 for (Statement st : statements) { 649 Map<String, Node> map = new HashMap<>(); 650 map.put("p", st.getPredicate()); 651 map.put("o", st.getObject()); 652 results.add(map); 653 } 654 return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("p", "o"), results); 655 } 656 throw new UnsupportedOperationException("Cannot parse query: " + queryString); 657 } 658 659 public static final Pattern SPARQL_S_PO = Pattern.compile("SELECT \\?s WHERE \\{ \\?s <(.*)> <(.*)> \\}"); 660 661 @Override 662 public int queryCount(String queryString, String language, String baseURI) { 663 // language is ignored, assume SPARQL 664 Matcher matcher = SPARQL_S_PO.matcher(queryString); 665 if (matcher.matches()) { 666 Node predicate = NodeFactory.createResource(matcher.group(1)); 667 Node object = NodeFactory.createResource(matcher.group(2)); 668 List<Node> subjects = getSubjects(predicate, object); 669 return subjects.size(); 670 } 671 throw new UnsupportedOperationException("Cannot parse query: " + queryString); 672 } 673 674 @Override 675 public boolean read(String path, String lang, String base) { 676 InputStream in = null; 677 try { 678 in = new FileInputStream(path); 679 return read(in, lang, base); 680 } catch (FileNotFoundException e) { 681 throw new RuntimeException(e); 682 } finally { 683 if (in != null) { 684 try { 685 in.close(); 686 } catch (IOException e) { 687 log.error(e); 688 } 689 } 690 } 691 } 692 693 @Override 694 public boolean write(String path, String lang, String base) { 695 OutputStream out = null; 696 try { 697 out = new FileOutputStream(new File(path)); 698 return write(out, lang, base); 699 } catch (FileNotFoundException e) { 700 throw new RuntimeException(e); 701 } finally { 702 if (out != null) { 703 try { 704 out.close(); 705 } catch (IOException e) { 706 log.error(e); 707 } 708 } 709 } 710 } 711 712 @Override 713 public boolean read(InputStream in, String lang, String base) { 714 throw new UnsupportedOperationException(); 715 } 716 717 @Override 718 public boolean write(OutputStream out, String lang, String base) { 719 throw new UnsupportedOperationException(); 720 } 721 722 protected static String getDefaultRepositoryName() { 723 return Framework.getService(RepositoryManager.class).getDefaultRepositoryName(); 724 } 725 726 /** Fake Node type used to pass down multiple nodes into whereBuilder. */ 727 public static class Subjects extends AbstractNode implements Subject { 728 729 private static final long serialVersionUID = 1L; 730 731 protected List<Node> nodes; 732 733 public Subjects(List<Node> nodes) { 734 this.nodes = nodes; 735 } 736 737 public List<Node> getNodes() { 738 return nodes; 739 } 740 741 @Override 742 public NodeType getNodeType() { 743 return null; 744 } 745 } 746 747 protected String whereBuilder(String query, Statement statement) { 748 List<Object> params = new ArrayList<Object>(3); 749 List<String> clauses = new ArrayList<String>(3); 750 751 Resource p = statement.getPredicate(); 752 if (p != null) { 753 NodeAsString pn = getNodeAsString(p); 754 if (pn.uri == null) { 755 return null; 756 } 757 clauses.add(REL_PREDICATE + " = ?"); 758 params.add(pn.uri); 759 } 760 761 Node s = statement.getSubject(); 762 if (s != null) { 763 if (s instanceof Subjects) { 764 List<Node> subjects = ((Subjects) s).getNodes(); 765 if (subjects.isEmpty()) { 766 throw new UnsupportedOperationException("empty subjects"); 767 } 768 StringBuilder buf = new StringBuilder(REL_SOURCE_URI); 769 buf.append(" IN ("); 770 for (Node sub : subjects) { 771 NodeAsString sn = getNodeAsString(sub); 772 if (sn.id != null) { 773 throw new UnsupportedOperationException("subjects ListNode with id instead of uri" + subjects); 774 } 775 buf.append("?, "); 776 params.add(sn.uri); 777 } 778 buf.setLength(buf.length() - 2); // remove last comma/space 779 buf.append(")"); 780 clauses.add(buf.toString()); 781 } else { 782 NodeAsString sn = getNodeAsString(s); 783 if (sn.id != null) { 784 clauses.add(REL_SOURCE_ID + " = ?"); 785 params.add(sn.id); 786 } else { 787 clauses.add(REL_SOURCE_URI + " = ?"); 788 params.add(sn.uri); 789 } 790 791 } 792 } 793 794 Node o = statement.getObject(); 795 if (o != null) { 796 NodeAsString on = getNodeAsString(o); 797 if (on.id != null) { 798 clauses.add(REL_TARGET_ID + " = ?"); 799 params.add(on.id); 800 } else if (on.uri != null) { 801 clauses.add(REL_TARGET_URI + " = ?"); 802 params.add(on.uri); 803 } else { 804 clauses.add(REL_TARGET_STRING + " = ?"); 805 params.add(on.string); 806 } 807 } 808 809 if (!clauses.isEmpty()) { 810 query += " WHERE " + StringUtils.join(clauses, " AND "); 811 query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); 812 } 813 return query; 814 } 815 816 protected static NodeAsString getNodeAsString(Node node) { 817 NodeAsString nas = new NodeAsString(); 818 if (node.isBlank()) { 819 // skolemization 820 String id = ((Blank) node).getId(); 821 nas.uri = BLANK_NS + (id == null ? "" : id); 822 } else if (node.isLiteral()) { 823 nas.string = ((Literal) node).getValue(); 824 } else if (node.isQNameResource()) { 825 String ns = ((QNameResource) node).getNamespace(); 826 if (DOCUMENT_NAMESPACE.equals(ns) || DOCUMENT_NAMESPACE2.equals(ns) || COMMENT_NAMESPACE.equals(ns)) { 827 nas.id = localNameToId(((QNameResource) node).getLocalName()); 828 } else { 829 nas.uri = ((Resource) node).getUri(); 830 } 831 } else { // node.isResource() 832 String uri = ((Resource) node).getUri(); 833 for (String ns : DOC_NAMESPACES) { 834 if (uri.startsWith(ns)) { 835 nas.id = localNameToId(uri.substring(ns.length())); 836 break; 837 } 838 } 839 if (nas.id == null) { 840 nas.uri = uri; 841 } 842 } 843 return nas; 844 } 845 846 protected static String localNameToId(String localName) { 847 String[] split = localName.split("/"); 848 if (split.length == 1) { 849 return localName; // compat, no repository name 850 } else { 851 return split[1]; 852 } 853 } 854 855 protected static String getAuthor(Statement statement) { 856 return getStringProperty(statement, RelationConstants.AUTHOR); 857 } 858 859 protected static void setAuthor(Statement statement, String author) { 860 setStringProperty(statement, RelationConstants.AUTHOR, author); 861 } 862 863 protected static Date getCreationDate(Statement statement) { 864 return getDateProperty(statement, RelationConstants.CREATION_DATE); 865 } 866 867 protected static void setCreationDate(Statement statement, Calendar created) { 868 setDateProperty(statement, RelationConstants.CREATION_DATE, created); 869 } 870 871 protected static Date getModificationDate(Statement statement) { 872 return getDateProperty(statement, RelationConstants.MODIFICATION_DATE); 873 } 874 875 protected static void setModificationDate(Statement statement, Calendar modified) { 876 setDateProperty(statement, RelationConstants.MODIFICATION_DATE, modified); 877 } 878 879 protected static String getComment(Statement statement) { 880 return getStringProperty(statement, RelationConstants.COMMENT); 881 } 882 883 protected static void setComment(Statement statement, String comment) { 884 setStringProperty(statement, RelationConstants.COMMENT, comment); 885 } 886 887 protected static String getStringProperty(Statement statement, Resource prop) { 888 Node node = statement.getProperty(prop); 889 if (node == null || !node.isLiteral()) { 890 return null; 891 } 892 return ((Literal) node).getValue(); 893 } 894 895 protected static void setStringProperty(Statement statement, Resource prop, String string) { 896 if (string == null) { 897 return; 898 } 899 statement.setProperty(prop, NodeFactory.createLiteral(string)); 900 } 901 902 protected static Date getDateProperty(Statement statement, Resource prop) { 903 Node node = statement.getProperty(prop); 904 if (node == null || !node.isLiteral()) { 905 return null; 906 } 907 return RelationDate.getDate((Literal) node); 908 } 909 910 protected static void setDateProperty(Statement statement, Resource prop, Calendar date) { 911 if (date == null) { 912 return; 913 } 914 statement.setProperty(prop, RelationDate.getLiteralDate(date)); 915 } 916 917}