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