001/* 002 * (C) Copyright 2011 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 * 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.lang.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 String BLANK_NS = "-:"; 112 113 public static String DOCUMENT_NAMESPACE = RelationConstants.DOCUMENT_NAMESPACE; 114 115 /** Without final slash (compat). */ 116 public static 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.<String> emptyList() : new ArrayList<String>( 159 new LinkedHashSet<String>(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 IterableQueryResult it = session.queryAndFetch("SELECT " + NXQL.ECM_UUID + " FROM " + docType, NXQL.NXQL); 210 try { 211 size = it.size(); 212 } finally { 213 it.close(); 214 } 215 } 216 } 217 218 @Override 219 public void clear() { 220 remove(Collections.singletonList(ALL)); 221 } 222 223 @Override 224 public void add(Statement statement) { 225 add(Collections.singletonList(statement)); 226 } 227 228 @Override 229 public void add(List<Statement> statements) { 230 StatementAdder statementAdder = session == null ? new StatementAdder(statements) : new StatementAdder( 231 statements, session); 232 statementAdder.runUnrestricted(); 233 } 234 235 protected class StatementAdder extends UnrestrictedSessionRunner { 236 237 protected List<Statement> statements; 238 239 protected Date now; 240 241 protected StatementAdder(List<Statement> statements) { 242 super(getDefaultRepositoryName(), "system"); 243 this.statements = statements; 244 } 245 246 protected StatementAdder(List<Statement> statements, CoreSession session) { 247 super(session); 248 this.statements = statements; 249 } 250 251 @Override 252 public void run() { 253 now = new Date(); 254 for (Statement statement : statements) { 255 add(statement); 256 } 257 session.save(); 258 } 259 260 protected void add(Statement statement) { 261 DocumentModel rel = session.createDocumentModel(null, "relation", docType); 262 rel = setRelationProperties(rel, statement); 263 session.createDocument(rel); 264 } 265 266 protected DocumentModel setRelationProperties(DocumentModel rel, Statement statement) { 267 Resource pred = statement.getPredicate(); 268 String predicateUri = pred.getUri(); 269 if (predicateUri == null) { 270 throw new IllegalArgumentException("Invalid predicate in statement: " + statement); 271 } 272 273 Subject subject = statement.getSubject(); 274 if (subject.isLiteral()) { 275 throw new IllegalArgumentException("Invalid literal subject in statement: " + statement); 276 } 277 NodeAsString source = getNodeAsString(subject); 278 279 Node object = statement.getObject(); 280 NodeAsString target = getNodeAsString(object); 281 282 String author = getAuthor(statement); 283 if (author == null) { 284 author = getOriginatingUsername(); 285 } 286 287 Date created = getCreationDate(statement); 288 if (created == null) { 289 created = now; 290 } 291 292 Date modified = getModificationDate(statement); 293 if (modified == null) { 294 modified = now; 295 } 296 297 String comment = getComment(statement); 298 299 String title = (source.id != null ? source.id : source.uri) + " " 300 + predicateUri.substring(predicateUri.lastIndexOf('/') + 1) + " " 301 + (target.id != null ? target.id : target.uri != null ? target.uri : target.string); 302 int MAX_TITLE = 200; 303 if (title.length() > MAX_TITLE) { 304 title = title.substring(0, MAX_TITLE); 305 } 306 307 rel.setPropertyValue(REL_PREDICATE, predicateUri); 308 if (source.id != null) { 309 rel.setPropertyValue(REL_SOURCE_ID, source.id); 310 } else { 311 rel.setPropertyValue(REL_SOURCE_URI, source.uri); 312 } 313 if (target.id != null) { 314 rel.setPropertyValue(REL_TARGET_ID, target.id); 315 } else if (target.uri != null) { 316 rel.setPropertyValue(REL_TARGET_URI, target.uri); 317 } else { 318 rel.setPropertyValue(REL_TARGET_STRING, target.string); 319 } 320 if (author != null) { 321 // will usually get overwritten by DublinCoreListener 322 // but not in tests 323 rel.setPropertyValue(DC_CREATOR, author); 324 } 325 if (created != null) { 326 // will usually get overwritten by DublinCoreListener 327 // but not in tests 328 rel.setPropertyValue(DC_CREATED, created); 329 } 330 if (modified != null) { 331 // will usually get overwritten by DublinCoreListener 332 // but not in tests 333 rel.setPropertyValue(DC_MODIFIED, modified); 334 } 335 rel.setPropertyValue(DC_TITLE, title); // for debug 336 if (comment != null) { 337 rel.setPropertyValue(DC_DESCRIPTION, comment); 338 } 339 return rel; 340 } 341 } 342 343 @Override 344 public void remove(Statement statement) { 345 remove(Collections.singletonList(statement)); 346 } 347 348 @Override 349 public void remove(List<Statement> statements) { 350 StatementRemover statementRemover = session == null ? new StatementRemover(statements) : new StatementRemover( 351 statements, session); 352 statementRemover.runUnrestricted(); 353 } 354 355 protected class StatementRemover extends UnrestrictedSessionRunner { 356 357 protected List<Statement> statements; 358 359 protected Date now; 360 361 protected StatementRemover(List<Statement> statements) { 362 super(getDefaultRepositoryName()); 363 this.statements = statements; 364 } 365 366 protected StatementRemover(List<Statement> statements, CoreSession session) { 367 super(session); 368 this.statements = statements; 369 } 370 371 @Override 372 public void run() { 373 now = new Date(); 374 for (Statement statement : statements) { 375 remove(statement); 376 } 377 } 378 379 protected void remove(Statement statement) { 380 String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; 381 query = whereBuilder(query, statement); 382 if (query == null) { 383 return; 384 } 385 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 386 try { 387 for (Map<String, Serializable> map : it) { 388 String id = (String) map.get(NXQL.ECM_UUID); 389 session.removeDocument(new IdRef(id)); 390 } 391 } finally { 392 it.close(); 393 } 394 } 395 } 396 397 protected class StatementFinder extends UnrestrictedSessionRunner { 398 399 protected List<Statement> statements; 400 401 protected Statement statement; 402 403 protected StatementFinder(Statement statement) { 404 super(getDefaultRepositoryName()); 405 this.statement = statement; 406 } 407 408 protected StatementFinder(Statement statement, CoreSession session) { 409 super(session); 410 this.statement = statement; 411 } 412 413 @Override 414 public void run() { 415 String query = "SELECT " + REL_PREDICATE + ", " + REL_SOURCE_ID + ", " + REL_SOURCE_URI + ", " 416 + REL_TARGET_ID + ", " + REL_TARGET_URI + ", " + REL_TARGET_STRING + ", " + DC_CREATED + ", " 417 + DC_CREATOR + ", " + DC_MODIFIED + ", " + DC_DESCRIPTION + " FROM " + docType; 418 query = whereBuilder(query, statement); 419 if (query == null) { 420 statements = EMPTY_STATEMENTS; 421 return; 422 } 423 statements = new ArrayList<Statement>(); 424 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 425 try { 426 for (Map<String, Serializable> map : it) { 427 String pred = (String) map.get(REL_PREDICATE); 428 String source = (String) map.get(REL_SOURCE_ID); 429 String sourceUri = (String) map.get(REL_SOURCE_URI); 430 String target = (String) map.get(REL_TARGET_ID); 431 String targetUri = (String) map.get(REL_TARGET_URI); 432 String targetString = (String) map.get(REL_TARGET_STRING); 433 Calendar created = (Calendar) map.get(DC_CREATED); 434 String creator = (String) map.get(DC_CREATOR); 435 Calendar modified = (Calendar) map.get(DC_MODIFIED); 436 String comment = (String) map.get(DC_DESCRIPTION); 437 438 Resource predicate = NodeFactory.createResource(pred); 439 Node subject; 440 if (source != null) { 441 subject = createId(source); 442 } else { 443 subject = createUri(sourceUri); 444 } 445 Node object; 446 if (target != null) { 447 object = createId(target); 448 } else if (targetUri != null) { 449 object = createUri(targetUri); 450 } else { 451 object = NodeFactory.createLiteral(targetString); 452 } 453 Statement statement = new StatementImpl(subject, predicate, object); 454 setCreationDate(statement, created); 455 setAuthor(statement, creator); 456 setModificationDate(statement, modified); 457 setComment(statement, comment); 458 statements.add(statement); 459 } 460 } finally { 461 it.close(); 462 } 463 } 464 465 protected QNameResource createId(String id) { 466 return NodeFactory.createQNameResource(DOCUMENT_NAMESPACE, session.getRepositoryName() + '/' + id); 467 } 468 469 protected Node createUri(String uri) { 470 if (uri.startsWith(BLANK_NS)) { 471 // skolemization 472 String id = uri.substring(BLANK_NS.length()); 473 return NodeFactory.createBlank(id.isEmpty() ? null : id); 474 } else { 475 for (String ns : namespaceList) { 476 if (uri.startsWith(ns)) { 477 String localName = uri.substring(ns.length()); 478 return NodeFactory.createQNameResource(ns, localName); 479 } 480 } 481 return NodeFactory.createResource(uri); 482 } 483 } 484 485 } 486 487 @Override 488 public List<Statement> getStatements() { 489 return getStatements(ALL); 490 } 491 492 @Override 493 public List<Statement> getStatements(Node subject, Node predicate, Node object) { 494 return getStatements(new StatementImpl(subject, predicate, object)); 495 } 496 497 @Override 498 public List<Statement> getStatements(Statement statement) { 499 StatementFinder statementFinder = session == null ? new StatementFinder(statement) : new StatementFinder( 500 statement, session); 501 statementFinder.runUnrestricted(); 502 return statementFinder.statements; 503 } 504 505 @Override 506 public List<Node> getSubjects(Node predicate, Node object) { 507 List<Statement> statements = getStatements(new StatementImpl(null, predicate, object)); 508 List<Node> nodes = new ArrayList<Node>(statements.size()); 509 for (Statement statement : statements) { 510 nodes.add(statement.getSubject()); 511 } 512 return nodes; 513 } 514 515 @Override 516 public List<Node> getPredicates(Node subject, Node object) { 517 List<Statement> statements = getStatements(new StatementImpl(subject, null, object)); 518 List<Node> nodes = new ArrayList<Node>(statements.size()); 519 for (Statement statement : statements) { 520 nodes.add(statement.getPredicate()); 521 } 522 return nodes; 523 } 524 525 @Override 526 public List<Node> getObjects(Node subject, Node predicate) { 527 List<Statement> statements = getStatements(new StatementImpl(subject, predicate, null)); 528 List<Node> nodes = new ArrayList<Node>(statements.size()); 529 for (Statement statement : statements) { 530 nodes.add(statement.getObject()); 531 } 532 return nodes; 533 } 534 535 @Override 536 public boolean hasStatement(Statement statement) { 537 if (statement == null) { 538 return false; 539 } 540 // could be optimized in the null/blank case, but this method seems 541 // unused 542 return !getStatements(statement).isEmpty(); 543 } 544 545 @Override 546 public boolean hasResource(Resource resource) { 547 if (resource == null) { 548 return false; 549 } 550 ResourceFinder resourceFinder = session == null ? new ResourceFinder(resource) : new ResourceFinder(resource, 551 session); 552 resourceFinder.runUnrestricted(); 553 return resourceFinder.found; 554 } 555 556 protected class ResourceFinder extends UnrestrictedSessionRunner { 557 558 protected boolean found; 559 560 protected Resource resource; 561 562 protected ResourceFinder(Resource resource) { 563 super(getDefaultRepositoryName()); 564 this.resource = resource; 565 } 566 567 protected ResourceFinder(Resource resource, CoreSession session) { 568 super(session); 569 this.resource = resource; 570 } 571 572 @Override 573 public void run() { 574 String query = "SELECT " + NXQL.ECM_UUID + " FROM " + docType; 575 query = whereAnyBuilder(query, resource); 576 IterableQueryResult it = session.queryAndFetch(query, NXQL.NXQL); 577 try { 578 found = it.iterator().hasNext(); 579 } finally { 580 it.close(); 581 } 582 } 583 584 protected String whereAnyBuilder(String query, Resource resource) { 585 List<Object> params = new ArrayList<Object>(3); 586 List<String> clauses = new ArrayList<String>(3); 587 588 NodeAsString nas = getNodeAsString(resource); 589 if (nas.id != null) { 590 // don't allow predicates that are actually doc ids 591 clauses.add(REL_SOURCE_ID + " = ?"); 592 params.add(nas.id); 593 clauses.add(REL_TARGET_URI + " = ?"); 594 params.add(DOCUMENT_NAMESPACE + session.getRepositoryName() + '/' + nas.id); 595 } else if (nas.uri != null) { 596 for (String ns : DOC_NAMESPACES) { 597 if (nas.uri.startsWith(ns)) { 598 String id = localNameToId(nas.uri.substring(ns.length())); 599 clauses.add(REL_SOURCE_ID + " = ?"); 600 params.add(id); 601 break; 602 } 603 } 604 clauses.add(REL_SOURCE_URI + " = ?"); 605 params.add(nas.uri); 606 clauses.add(REL_TARGET_URI + " = ?"); 607 params.add(nas.uri); 608 clauses.add(REL_PREDICATE + " = ?"); 609 params.add(nas.uri); 610 } 611 query += " WHERE " + StringUtils.join(clauses, " OR "); 612 query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); 613 return query; 614 } 615 } 616 617 public static final Pattern SPARQL_SPO_PO = Pattern.compile("SELECT \\?s \\?p \\?o WHERE \\{ \\?s \\?p \\?o . \\?s <(.*)> <(.*)> . \\}"); 618 619 public static final Pattern SPARQL_PO_S = Pattern.compile("SELECT \\?p \\?o WHERE \\{ <(.*)> \\?p \\?o \\}"); 620 621 @Override 622 public QueryResult query(String queryString, String language, String baseURI) { 623 // language is ignored, assume SPARQL 624 Matcher matcher = SPARQL_SPO_PO.matcher(queryString); 625 if (matcher.matches()) { 626 Node predicate = NodeFactory.createResource(matcher.group(1)); 627 Node object = NodeFactory.createResource(matcher.group(2)); 628 // find subjects with this predicate and object 629 List<Node> subjects = getSubjects(predicate, object); 630 List<Map<String, Node>> results = new ArrayList<>(); 631 if (!subjects.isEmpty()) { 632 // find all statements with these subjects 633 List<Statement> statements = getStatements(new Subjects(subjects), null, null); 634 for (Statement st : statements) { 635 Map<String, Node> map = new HashMap<>(); 636 map.put("s", st.getSubject()); 637 map.put("p", st.getPredicate()); 638 map.put("o", st.getObject()); 639 results.add(map); 640 } 641 } 642 return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("s", "p", "o"), results); 643 } 644 matcher = SPARQL_PO_S.matcher(queryString); 645 if (matcher.matches()) { 646 Node subject = NodeFactory.createResource(matcher.group(1)); 647 // find predicates and objects with this subject 648 List<Statement> statements = getStatements(new StatementImpl(subject, null, null)); 649 List<Map<String, Node>> results = new ArrayList<>(); 650 for (Statement st : statements) { 651 Map<String, Node> map = new HashMap<>(); 652 map.put("p", st.getPredicate()); 653 map.put("o", st.getObject()); 654 results.add(map); 655 } 656 return new QueryResultImpl(Integer.valueOf(results.size()), Arrays.asList("p", "o"), results); 657 } 658 throw new UnsupportedOperationException("Cannot parse query: " + queryString); 659 } 660 661 public static final Pattern SPARQL_S_PO = Pattern.compile("SELECT \\?s WHERE \\{ \\?s <(.*)> <(.*)> \\}"); 662 663 @Override 664 public int queryCount(String queryString, String language, String baseURI) { 665 // language is ignored, assume SPARQL 666 Matcher matcher = SPARQL_S_PO.matcher(queryString); 667 if (matcher.matches()) { 668 Node predicate = NodeFactory.createResource(matcher.group(1)); 669 Node object = NodeFactory.createResource(matcher.group(2)); 670 List<Node> subjects = getSubjects(predicate, object); 671 return subjects.size(); 672 } 673 throw new UnsupportedOperationException("Cannot parse query: " + queryString); 674 } 675 676 @Override 677 public boolean read(String path, String lang, String base) { 678 InputStream in = null; 679 try { 680 in = new FileInputStream(path); 681 return read(in, lang, base); 682 } catch (FileNotFoundException e) { 683 throw new RuntimeException(e); 684 } finally { 685 if (in != null) { 686 try { 687 in.close(); 688 } catch (IOException e) { 689 log.error(e); 690 } 691 } 692 } 693 } 694 695 @Override 696 public boolean write(String path, String lang, String base) { 697 OutputStream out = null; 698 try { 699 out = new FileOutputStream(new File(path)); 700 return write(out, lang, base); 701 } catch (FileNotFoundException e) { 702 throw new RuntimeException(e); 703 } finally { 704 if (out != null) { 705 try { 706 out.close(); 707 } catch (IOException e) { 708 log.error(e); 709 } 710 } 711 } 712 } 713 714 @Override 715 public boolean read(InputStream in, String lang, String base) { 716 throw new UnsupportedOperationException(); 717 } 718 719 @Override 720 public boolean write(OutputStream out, String lang, String base) { 721 throw new UnsupportedOperationException(); 722 } 723 724 protected static String getDefaultRepositoryName() { 725 return Framework.getService(RepositoryManager.class).getDefaultRepositoryName(); 726 } 727 728 /** Fake Node type used to pass down multiple nodes into whereBuilder. */ 729 public static class Subjects extends AbstractNode implements Subject { 730 731 private static final long serialVersionUID = 1L; 732 733 protected List<Node> nodes; 734 735 public Subjects(List<Node> nodes) { 736 this.nodes = nodes; 737 } 738 739 public List<Node> getNodes() { 740 return nodes; 741 } 742 743 @Override 744 public NodeType getNodeType() { 745 return null; 746 } 747 } 748 749 protected String whereBuilder(String query, Statement statement) { 750 List<Object> params = new ArrayList<Object>(3); 751 List<String> clauses = new ArrayList<String>(3); 752 753 Resource p = statement.getPredicate(); 754 if (p != null) { 755 NodeAsString pn = getNodeAsString(p); 756 if (pn.uri == null) { 757 return null; 758 } 759 clauses.add(REL_PREDICATE + " = ?"); 760 params.add(pn.uri); 761 } 762 763 Node s = statement.getSubject(); 764 if (s != null) { 765 if (s instanceof Subjects) { 766 List<Node> subjects = ((Subjects) s).getNodes(); 767 if (subjects.isEmpty()) { 768 throw new UnsupportedOperationException("empty subjects"); 769 } 770 StringBuilder buf = new StringBuilder(REL_SOURCE_URI); 771 buf.append(" IN ("); 772 for (Node sub : subjects) { 773 NodeAsString sn = getNodeAsString(sub); 774 if (sn.id != null) { 775 throw new UnsupportedOperationException("subjects ListNode with id instead of uri" + subjects); 776 } 777 buf.append("?, "); 778 params.add(sn.uri); 779 } 780 buf.setLength(buf.length() - 2); // remove last comma/space 781 buf.append(")"); 782 clauses.add(buf.toString()); 783 } else { 784 NodeAsString sn = getNodeAsString(s); 785 if (sn.id != null) { 786 clauses.add(REL_SOURCE_ID + " = ?"); 787 params.add(sn.id); 788 } else { 789 clauses.add(REL_SOURCE_URI + " = ?"); 790 params.add(sn.uri); 791 } 792 793 } 794 } 795 796 Node o = statement.getObject(); 797 if (o != null) { 798 NodeAsString on = getNodeAsString(o); 799 if (on.id != null) { 800 clauses.add(REL_TARGET_ID + " = ?"); 801 params.add(on.id); 802 } else if (on.uri != null) { 803 clauses.add(REL_TARGET_URI + " = ?"); 804 params.add(on.uri); 805 } else { 806 clauses.add(REL_TARGET_STRING + " = ?"); 807 params.add(on.string); 808 } 809 } 810 811 if (!clauses.isEmpty()) { 812 query += " WHERE " + StringUtils.join(clauses, " AND "); 813 query = NXQLQueryBuilder.getQuery(query, params.toArray(), true, true, null); 814 } 815 return query; 816 } 817 818 protected static NodeAsString getNodeAsString(Node node) { 819 NodeAsString nas = new NodeAsString(); 820 if (node.isBlank()) { 821 // skolemization 822 String id = ((Blank) node).getId(); 823 nas.uri = BLANK_NS + (id == null ? "" : id); 824 } else if (node.isLiteral()) { 825 nas.string = ((Literal) node).getValue(); 826 } else if (node.isQNameResource()) { 827 String ns = ((QNameResource) node).getNamespace(); 828 if (DOCUMENT_NAMESPACE.equals(ns) || DOCUMENT_NAMESPACE2.equals(ns) || COMMENT_NAMESPACE.equals(ns)) { 829 nas.id = localNameToId(((QNameResource) node).getLocalName()); 830 } else { 831 nas.uri = ((Resource) node).getUri(); 832 } 833 } else { // node.isResource() 834 String uri = ((Resource) node).getUri(); 835 for (String ns : DOC_NAMESPACES) { 836 if (uri.startsWith(ns)) { 837 nas.id = localNameToId(uri.substring(ns.length())); 838 break; 839 } 840 } 841 if (nas.id == null) { 842 nas.uri = uri; 843 } 844 } 845 return nas; 846 } 847 848 protected static String localNameToId(String localName) { 849 String[] split = localName.split("/"); 850 if (split.length == 1) { 851 return localName; // compat, no repository name 852 } else { 853 return split[1]; 854 } 855 } 856 857 protected static String getAuthor(Statement statement) { 858 return getStringProperty(statement, RelationConstants.AUTHOR); 859 } 860 861 protected static void setAuthor(Statement statement, String author) { 862 setStringProperty(statement, RelationConstants.AUTHOR, author); 863 } 864 865 protected static Date getCreationDate(Statement statement) { 866 return getDateProperty(statement, RelationConstants.CREATION_DATE); 867 } 868 869 protected static void setCreationDate(Statement statement, Calendar created) { 870 setDateProperty(statement, RelationConstants.CREATION_DATE, created); 871 } 872 873 protected static Date getModificationDate(Statement statement) { 874 return getDateProperty(statement, RelationConstants.MODIFICATION_DATE); 875 } 876 877 protected static void setModificationDate(Statement statement, Calendar modified) { 878 setDateProperty(statement, RelationConstants.MODIFICATION_DATE, modified); 879 } 880 881 protected static String getComment(Statement statement) { 882 return getStringProperty(statement, RelationConstants.COMMENT); 883 } 884 885 protected static void setComment(Statement statement, String comment) { 886 setStringProperty(statement, RelationConstants.COMMENT, comment); 887 } 888 889 protected static String getStringProperty(Statement statement, Resource prop) { 890 Node node = statement.getProperty(prop); 891 if (node == null || !node.isLiteral()) { 892 return null; 893 } 894 return ((Literal) node).getValue(); 895 } 896 897 protected static void setStringProperty(Statement statement, Resource prop, String string) { 898 if (string == null) { 899 return; 900 } 901 statement.setProperty(prop, NodeFactory.createLiteral(string)); 902 } 903 904 protected static Date getDateProperty(Statement statement, Resource prop) { 905 Node node = statement.getProperty(prop); 906 if (node == null || !node.isLiteral()) { 907 return null; 908 } 909 return RelationDate.getDate((Literal) node); 910 } 911 912 protected static void setDateProperty(Statement statement, Resource prop, Calendar date) { 913 if (date == null) { 914 return; 915 } 916 statement.setProperty(prop, RelationDate.getLiteralDate(date)); 917 } 918 919}