001/** 002 * Copyright 2015 DuraSpace, Inc. 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 */ 016package org.fcrepo.kernel.modeshape; 017 018import static com.google.common.base.Throwables.propagate; 019import static com.google.common.collect.Iterators.concat; 020import static com.google.common.collect.Iterators.filter; 021import static com.google.common.collect.Iterators.singletonIterator; 022import static com.google.common.collect.Iterators.transform; 023import static com.google.common.collect.Lists.newArrayList; 024import static com.hp.hpl.jena.update.UpdateAction.execute; 025import static com.hp.hpl.jena.update.UpdateFactory.create; 026import static java.util.Arrays.asList; 027import static java.util.stream.Collectors.joining; 028import static org.apache.commons.codec.digest.DigestUtils.shaHex; 029import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.isFrozen; 030import static org.fcrepo.kernel.api.services.functions.JcrPropertyFunctions.property2values; 031import static org.fcrepo.kernel.api.utils.UncheckedFunction.uncheck; 032import static org.fcrepo.kernel.modeshape.identifiers.NodeResourceConverter.nodeConverter; 033import static org.fcrepo.kernel.modeshape.rdf.JcrRdfTools.getRDFNamespaceForJcrNamespace; 034import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isFrozenNode; 035import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.isInternalNode; 036import static org.modeshape.jcr.api.JcrConstants.JCR_CONTENT; 037import static org.slf4j.LoggerFactory.getLogger; 038 039import java.lang.reflect.Constructor; 040import java.lang.reflect.InvocationTargetException; 041import java.net.URI; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.Collection; 045import java.util.Collections; 046import java.util.Date; 047import java.util.Iterator; 048import java.util.List; 049import java.util.function.Function; 050import java.util.function.Predicate; 051import java.util.stream.Collectors; 052import java.util.stream.Stream; 053 054import javax.jcr.AccessDeniedException; 055import javax.jcr.ItemNotFoundException; 056import javax.jcr.Node; 057import javax.jcr.PathNotFoundException; 058import javax.jcr.Property; 059import javax.jcr.PropertyType; 060import javax.jcr.RepositoryException; 061import javax.jcr.Session; 062import javax.jcr.Value; 063import javax.jcr.nodetype.NodeType; 064import javax.jcr.version.Version; 065import javax.jcr.version.VersionHistory; 066 067import com.google.common.base.Converter; 068import com.google.common.collect.Iterators; 069import com.hp.hpl.jena.rdf.model.Resource; 070 071import org.fcrepo.kernel.api.FedoraJcrTypes; 072import org.fcrepo.kernel.api.models.NonRdfSourceDescription; 073import org.fcrepo.kernel.api.models.FedoraBinary; 074import org.fcrepo.kernel.api.models.FedoraResource; 075import org.fcrepo.kernel.api.exception.ConstraintViolationException; 076import org.fcrepo.kernel.api.exception.MalformedRdfException; 077import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException; 078import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 079import org.fcrepo.kernel.api.identifiers.IdentifierConverter; 080import org.fcrepo.kernel.api.utils.UncheckedPredicate; 081import org.fcrepo.kernel.api.utils.iterators.GraphDifferencingIterator; 082import org.fcrepo.kernel.api.utils.iterators.RdfStream; 083import org.fcrepo.kernel.modeshape.utils.JcrPropertyStatementListener; 084import org.fcrepo.kernel.modeshape.utils.iterators.RdfAdder; 085import org.fcrepo.kernel.modeshape.utils.iterators.RdfRemover; 086 087import org.modeshape.jcr.api.JcrTools; 088import org.slf4j.Logger; 089 090import com.hp.hpl.jena.rdf.model.Model; 091import com.hp.hpl.jena.sparql.modify.request.UpdateData; 092import com.hp.hpl.jena.sparql.modify.request.UpdateDeleteWhere; 093import com.hp.hpl.jena.sparql.modify.request.UpdateModify; 094import com.hp.hpl.jena.update.UpdateRequest; 095 096/** 097 * Common behaviors across {@link org.fcrepo.kernel.api.models.Container} and 098 * {@link org.fcrepo.kernel.api.models.NonRdfSourceDescription} types; also used 099 * when the exact type of an object is irrelevant 100 * 101 * @author ajs6f 102 */ 103public class FedoraResourceImpl extends JcrTools implements FedoraJcrTypes, FedoraResource { 104 105 private static final Logger LOGGER = getLogger(FedoraResourceImpl.class); 106 107 protected Node node; 108 109 /** 110 * Construct a {@link org.fcrepo.kernel.api.models.FedoraResource} from an existing JCR Node 111 * @param node an existing JCR node to treat as an fcrepo object 112 */ 113 public FedoraResourceImpl(final Node node) { 114 this.node = node; 115 } 116 117 /* (non-Javadoc) 118 * @see org.fcrepo.kernel.api.models.FedoraResource#getNode() 119 */ 120 @Override 121 public Node getNode() { 122 return node; 123 } 124 125 /* (non-Javadoc) 126 * @see org.fcrepo.kernel.api.models.FedoraResource#getPath() 127 */ 128 @Override 129 public String getPath() { 130 try { 131 return node.getPath(); 132 } catch (final RepositoryException e) { 133 throw new RepositoryRuntimeException(e); 134 } 135 } 136 137 /* (non-Javadoc) 138 * @see org.fcrepo.kernel.api.models.FedoraResource#getChildren() 139 */ 140 @Override 141 public Iterator<FedoraResource> getChildren() { 142 try { 143 return concat(nodeToGoodChildren(node)); 144 } catch (final RepositoryException e) { 145 throw new RepositoryRuntimeException(e); 146 } 147 } 148 149 /** 150 * Get the "good" children for a node by skipping all pairtree nodes in the way. 151 * @param input 152 * @return 153 * @throws RepositoryException 154 */ 155 private Iterator<Iterator<FedoraResource>> nodeToGoodChildren(final Node input) throws RepositoryException { 156 @SuppressWarnings("unchecked") 157 final Iterator<Node> allChildren = input.getNodes(); 158 final Iterator<Node> children = filter(allChildren, nastyChildren.negate()::test); 159 return transform(children, (new Function<Node, Iterator<FedoraResource>>() { 160 161 @Override 162 public Iterator<FedoraResource> apply(final Node input) { 163 try { 164 if (input.isNodeType(FEDORA_PAIRTREE)) { 165 return concat(nodeToGoodChildren(input)); 166 } 167 return singletonIterator(nodeToObjectBinaryConverter.convert(input)); 168 } catch (final RepositoryException e) { 169 throw new RepositoryRuntimeException(e); 170 } 171 } 172 })::apply); 173 } 174 175 /** 176 * Children for whom we will not generate triples. 177 */ 178 private static Predicate<Node> nastyChildren = isInternalNode 179 .or(TombstoneImpl::hasMixin) 180 .or(UncheckedPredicate.uncheck(p -> p.getName().equals(JCR_CONTENT))) 181 .or(UncheckedPredicate.uncheck(p -> p.getName().equals("#"))); 182 183 private static final Converter<FedoraResource, FedoraResource> datastreamToBinary 184 = new Converter<FedoraResource, FedoraResource>() { 185 186 @Override 187 protected FedoraResource doForward(final FedoraResource fedoraResource) { 188 if (fedoraResource instanceof NonRdfSourceDescription) { 189 return ((NonRdfSourceDescription) fedoraResource).getDescribedResource(); 190 } 191 return fedoraResource; 192 } 193 194 @Override 195 protected FedoraResource doBackward(final FedoraResource fedoraResource) { 196 if (fedoraResource instanceof FedoraBinary) { 197 return ((FedoraBinary) fedoraResource).getDescription(); 198 } 199 return fedoraResource; 200 } 201 }; 202 203 private static final Converter<Node, FedoraResource> nodeToObjectBinaryConverter 204 = nodeConverter.andThen(datastreamToBinary); 205 206 @Override 207 public FedoraResource getContainer() { 208 try { 209 210 if (getNode().getDepth() == 0) { 211 return null; 212 } 213 214 Node container = getNode().getParent(); 215 while (container.getDepth() > 0) { 216 if (container.isNodeType(FEDORA_PAIRTREE) 217 || container.isNodeType(FEDORA_NON_RDF_SOURCE_DESCRIPTION)) { 218 container = container.getParent(); 219 } else { 220 return nodeConverter.convert(container); 221 } 222 } 223 224 return nodeConverter.convert(container); 225 } catch (final RepositoryException e) { 226 throw new RepositoryRuntimeException(e); 227 } 228 } 229 230 @Override 231 public FedoraResource getChild(final String relPath) { 232 try { 233 return nodeConverter.convert(getNode().getNode(relPath)); 234 } catch (final RepositoryException e) { 235 throw new RepositoryRuntimeException(e); 236 } 237 } 238 239 @Override 240 public boolean hasProperty(final String relPath) { 241 try { 242 return getNode().hasProperty(relPath); 243 } catch (final RepositoryException e) { 244 throw new RepositoryRuntimeException(e); 245 } 246 } 247 248 @Override 249 public Property getProperty(final String relPath) { 250 try { 251 return getNode().getProperty(relPath); 252 } catch (final RepositoryException e) { 253 throw new RepositoryRuntimeException(e); 254 } 255 } 256 257 /** 258 * Set the given property value for this resource as a URI, without translating any URIs that 259 * appear to be references to repository resources. Using untranslated URIs to refer to 260 * repository resources will disable referential integrity checking, but also allows referring 261 * to resources that do not exist, have been deleted, etc. 262 * @param relPath the given path 263 * @param value the URI value 264 */ 265 @Override 266 public void setURIProperty(final String relPath, final URI value) { 267 try { 268 getNode().setProperty(relPath, value.toString(), PropertyType.URI); 269 } catch (final RepositoryException e) { 270 throw new RepositoryRuntimeException(e); 271 } 272 } 273 274 @Override 275 public void delete() { 276 try { 277 @SuppressWarnings("unchecked") 278 final Iterator<Property> references = node.getReferences(); 279 @SuppressWarnings("unchecked") 280 final Iterator<Property> weakReferences = node.getWeakReferences(); 281 final Iterator<Property> inboundProperties = Iterators.concat(references, weakReferences); 282 283 while (inboundProperties.hasNext()) { 284 final Property prop = inboundProperties.next(); 285 final List<Value> newVals = new ArrayList<>(); 286 final Iterator<Value> propIt = property2values.apply(prop); 287 while (propIt.hasNext()) { 288 final Value v = propIt.next(); 289 if (!node.equals(getSession().getNodeByIdentifier(v.getString()))) { 290 newVals.add(v); 291 LOGGER.trace("Keeping multivalue reference property when deleting node"); 292 } 293 } 294 if (newVals.size() == 0) { 295 prop.remove(); 296 } else { 297 prop.setValue(newVals.toArray(new Value[newVals.size()])); 298 } 299 } 300 301 final Node parent; 302 303 if (getNode().getDepth() > 0) { 304 parent = getNode().getParent(); 305 } else { 306 parent = null; 307 } 308 final String name = getNode().getName(); 309 310 node.remove(); 311 312 if (parent != null) { 313 createTombstone(parent, name); 314 } 315 316 } catch (final RepositoryException e) { 317 throw new RepositoryRuntimeException(e); 318 } 319 } 320 321 private void createTombstone(final Node parent, final String path) throws RepositoryException { 322 findOrCreateChild(parent, path, FEDORA_TOMBSTONE); 323 } 324 325 /* (non-Javadoc) 326 * @see org.fcrepo.kernel.api.models.FedoraResource#getCreatedDate() 327 */ 328 @Override 329 public Date getCreatedDate() { 330 try { 331 if (hasProperty(JCR_CREATED)) { 332 return new Date(getProperty(JCR_CREATED).getDate().getTimeInMillis()); 333 } 334 } catch (final PathNotFoundException e) { 335 throw new PathNotFoundRuntimeException(e); 336 } catch (final RepositoryException e) { 337 throw new RepositoryRuntimeException(e); 338 } 339 LOGGER.debug("Node {} does not have a createdDate", node); 340 return null; 341 } 342 343 /* (non-Javadoc) 344 * @see org.fcrepo.kernel.api.models.FedoraResource#getLastModifiedDate() 345 */ 346 @Override 347 public Date getLastModifiedDate() { 348 349 try { 350 if (hasProperty(JCR_LASTMODIFIED)) { 351 return new Date(getProperty(JCR_LASTMODIFIED).getDate().getTimeInMillis()); 352 } 353 } catch (final PathNotFoundException e) { 354 throw new PathNotFoundRuntimeException(e); 355 } catch (final RepositoryException e) { 356 throw new RepositoryRuntimeException(e); 357 } 358 LOGGER.debug("Could not get last modified date property for node {}", node); 359 360 final Date createdDate = getCreatedDate(); 361 if (createdDate != null) { 362 LOGGER.trace("Using created date for last modified date for node {}", node); 363 return createdDate; 364 } 365 366 return null; 367 } 368 369 370 @Override 371 public boolean hasType(final String type) { 372 try { 373 if (isFrozen.test(node) && hasProperty(FROZEN_MIXIN_TYPES)) { 374 final List<String> types = newArrayList( 375 transform(property2values.apply(getProperty(FROZEN_MIXIN_TYPES)), uncheck(Value::getString)::apply) 376 ); 377 return types.contains(type); 378 } 379 return node.isNodeType(type); 380 } catch (final PathNotFoundException e) { 381 throw new PathNotFoundRuntimeException(e); 382 } catch (final RepositoryException e) { 383 throw new RepositoryRuntimeException(e); 384 } 385 } 386 387 @Override 388 public List<URI> getTypes() { 389 try { 390 final List<NodeType> nodeTypes = new ArrayList<>(); 391 final NodeType primaryNodeType = node.getPrimaryNodeType(); 392 nodeTypes.add(primaryNodeType); 393 nodeTypes.addAll(asList(primaryNodeType.getSupertypes())); 394 final List<NodeType> mixinTypes = asList(node.getMixinNodeTypes()); 395 396 nodeTypes.addAll(mixinTypes); 397 mixinTypes.stream() 398 .map(NodeType::getSupertypes) 399 .flatMap(Arrays::stream) 400 .forEach(nodeTypes::add); 401 402 return nodeTypes.stream() 403 .map(uncheck(x -> x.getName())) 404 .distinct() 405 .map(nodeTypeNameToURI::apply) 406 .peek(x -> LOGGER.debug("node has rdf:type {}", x)) 407 .collect(Collectors.toList()); 408 409 } catch (final PathNotFoundException e) { 410 throw new PathNotFoundRuntimeException(e); 411 } catch (final RepositoryException e) { 412 throw new RepositoryRuntimeException(e); 413 } 414 } 415 416 private Function<String, URI> nodeTypeNameToURI = uncheck(name -> { 417 final String prefix = name.split(":")[0]; 418 final String typeName = name.split(":")[1]; 419 final String namespace = getSession().getWorkspace().getNamespaceRegistry().getURI(prefix); 420 return URI.create(getRDFNamespaceForJcrNamespace(namespace) + typeName); 421 }); 422 423 /* (non-Javadoc) 424 * @see org.fcrepo.kernel.api.models.FedoraResource#updateProperties 425 * (org.fcrepo.kernel.api.identifiers.IdentifierConverter, java.lang.String, RdfStream) 426 */ 427 @Override 428 public void updateProperties(final IdentifierConverter<Resource, FedoraResource> idTranslator, 429 final String sparqlUpdateStatement, final RdfStream originalTriples) 430 throws MalformedRdfException, AccessDeniedException { 431 432 final Model model = originalTriples.asModel(); 433 434 final UpdateRequest request = create(sparqlUpdateStatement, 435 idTranslator.reverse().convert(this).toString()); 436 437 final Collection<IllegalArgumentException> errors = checkInvalidPredicates(request); 438 439 if (!errors.isEmpty()) { 440 throw new IllegalArgumentException(errors.stream().map(Exception::getMessage).collect(joining(",\n"))); 441 } 442 443 final JcrPropertyStatementListener listener = new JcrPropertyStatementListener( 444 idTranslator, getSession(), idTranslator.reverse().convert(this).asNode()); 445 446 model.register(listener); 447 448 model.setNsPrefixes(request.getPrefixMapping()); 449 execute(request, model); 450 451 listener.assertNoExceptions(); 452 } 453 454 @Override 455 public RdfStream getTriples(final IdentifierConverter<Resource, FedoraResource> idTranslator, 456 final Class<? extends RdfStream> context) { 457 return getTriples(idTranslator, Collections.singleton(context)); 458 } 459 460 @Override 461 public RdfStream getTriples(final IdentifierConverter<Resource, FedoraResource> idTranslator, 462 final Iterable<? extends Class<? extends RdfStream>> contexts) { 463 final RdfStream stream = new RdfStream(); 464 465 for (final Class<? extends RdfStream> context : contexts) { 466 try { 467 final Constructor<? extends RdfStream> declaredConstructor 468 = context.getDeclaredConstructor(FedoraResource.class, IdentifierConverter.class); 469 470 final RdfStream rdfStream = declaredConstructor.newInstance(this, idTranslator); 471 rdfStream.session(getSession()); 472 473 stream.concat(rdfStream); 474 } catch (final NoSuchMethodException | 475 InstantiationException | 476 IllegalAccessException e) { 477 // Shouldn't happen. 478 throw propagate(e); 479 } catch (final InvocationTargetException e) { 480 final Throwable cause = e.getCause(); 481 if (cause instanceof RepositoryException) { 482 throw new RepositoryRuntimeException(cause); 483 } 484 throw propagate(cause); 485 } 486 } 487 488 return stream; 489 } 490 491 /* 492 * (non-Javadoc) 493 * @see org.fcrepo.kernel.api.models.FedoraResource#getBaseVersion() 494 */ 495 @Override 496 public Version getBaseVersion() { 497 try { 498 return getSession().getWorkspace().getVersionManager().getBaseVersion(getPath()); 499 } catch (final RepositoryException e) { 500 throw new RepositoryRuntimeException(e); 501 } 502 } 503 504 /* 505 * (non-Javadoc) 506 * @see org.fcrepo.kernel.api.models.FedoraResource#getVersionHistory() 507 */ 508 @Override 509 public VersionHistory getVersionHistory() { 510 try { 511 return getSession().getWorkspace().getVersionManager().getVersionHistory(getPath()); 512 } catch (final RepositoryException e) { 513 throw new RepositoryRuntimeException(e); 514 } 515 } 516 517 /* (non-Javadoc) 518 * @see org.fcrepo.kernel.api.models.FedoraResource#isNew() 519 */ 520 @Override 521 public Boolean isNew() { 522 return node.isNew(); 523 } 524 525 /* (non-Javadoc) 526 * @see org.fcrepo.kernel.api.models.FedoraResource#replaceProperties 527 * (org.fcrepo.kernel.api.identifiers.IdentifierConverter, com.hp.hpl.jena.rdf.model.Model) 528 */ 529 @Override 530 public void replaceProperties(final IdentifierConverter<Resource, FedoraResource> idTranslator, 531 final Model inputModel, final RdfStream originalTriples) throws MalformedRdfException { 532 533 final RdfStream replacementStream = new RdfStream().namespaces(inputModel.getNsPrefixMap()) 534 .topic(idTranslator.reverse().convert(this).asNode()); 535 536 final GraphDifferencingIterator differencer = 537 new GraphDifferencingIterator(inputModel, originalTriples); 538 539 final StringBuilder exceptions = new StringBuilder(); 540 try { 541 new RdfRemover(idTranslator, getSession(), replacementStream 542 .withThisContext(differencer)).consume(); 543 } catch (final ConstraintViolationException e) { 544 throw e; 545 } catch (final MalformedRdfException e) { 546 exceptions.append(e.getMessage()); 547 exceptions.append("\n"); 548 } 549 550 try { 551 new RdfAdder(idTranslator, getSession(), replacementStream 552 .withThisContext(differencer.notCommon())).consume(); 553 } catch (final ConstraintViolationException e) { 554 throw e; 555 } catch (final MalformedRdfException e) { 556 exceptions.append(e.getMessage()); 557 } 558 559 if (exceptions.length() > 0) { 560 throw new MalformedRdfException(exceptions.toString()); 561 } 562 } 563 564 /* (non-Javadoc) 565 * @see org.fcrepo.kernel.api.models.FedoraResource#getEtagValue() 566 */ 567 @Override 568 public String getEtagValue() { 569 final Date lastModifiedDate = getLastModifiedDate(); 570 571 if (lastModifiedDate != null) { 572 return shaHex(getPath() + lastModifiedDate.getTime()); 573 } 574 return ""; 575 } 576 577 @Override 578 public void enableVersioning() { 579 try { 580 node.addMixin("mix:versionable"); 581 } catch (final RepositoryException e) { 582 throw new RepositoryRuntimeException(e); 583 } 584 } 585 586 @Override 587 public void disableVersioning() { 588 try { 589 node.removeMixin("mix:versionable"); 590 } catch (final RepositoryException e) { 591 throw new RepositoryRuntimeException(e); 592 } 593 594 } 595 596 @Override 597 public boolean isVersioned() { 598 try { 599 return node.isNodeType("mix:versionable"); 600 } catch (final RepositoryException e) { 601 throw new RepositoryRuntimeException(e); 602 } 603 } 604 605 @Override 606 public boolean isFrozenResource() { 607 return isFrozenNode.test(this); 608 } 609 610 @Override 611 public FedoraResource getVersionedAncestor() { 612 613 try { 614 if (!isFrozenResource()) { 615 return null; 616 } 617 618 Node versionableFrozenNode = getNode(); 619 FedoraResource unfrozenResource = getUnfrozenResource(); 620 621 // traverse the frozen tree looking for a node whose unfrozen equivalent is versioned 622 while (!unfrozenResource.isVersioned()) { 623 624 if (versionableFrozenNode.getDepth() == 0) { 625 return null; 626 } 627 628 // node in the frozen tree 629 versionableFrozenNode = versionableFrozenNode.getParent(); 630 631 // unfrozen equivalent 632 unfrozenResource = new FedoraResourceImpl(versionableFrozenNode).getUnfrozenResource(); 633 } 634 635 return new FedoraResourceImpl(versionableFrozenNode); 636 } catch (final RepositoryException e) { 637 throw new RepositoryRuntimeException(e); 638 } 639 640 } 641 642 @Override 643 public FedoraResource getUnfrozenResource() { 644 if (!isFrozenResource()) { 645 return this; 646 } 647 648 try { 649 return new FedoraResourceImpl(getSession().getNodeByIdentifier(getProperty("jcr:frozenUuid").getString())); 650 } catch (final RepositoryException e) { 651 throw new RepositoryRuntimeException(e); 652 } 653 } 654 655 @Override 656 public Node getNodeVersion(final String label) { 657 try { 658 final Session session = getSession(); 659 660 final Node n = getFrozenNode(label); 661 662 if (n != null) { 663 return n; 664 } 665 666 if (isVersioned()) { 667 final VersionHistory hist = 668 session.getWorkspace().getVersionManager().getVersionHistory(getPath()); 669 670 if (hist.hasVersionLabel(label)) { 671 LOGGER.debug("Found version for {} by label {}.", this, label); 672 return hist.getVersionByLabel(label).getFrozenNode(); 673 } 674 } 675 676 LOGGER.warn("Unknown version {} with label or uuid {}!", this, label); 677 return null; 678 } catch (final RepositoryException e) { 679 throw new RepositoryRuntimeException(e); 680 } 681 682 } 683 684 /** 685 * Helps ensure that there are no terminating slashes in the predicate. 686 * A terminating slash means ModeShape has trouble extracting the localName, e.g., for 687 * http://myurl.org/. 688 * 689 * @see <a href="https://jira.duraspace.org/browse/FCREPO-1409"> FCREPO-1409 </a> for details. 690 */ 691 private Collection<IllegalArgumentException> checkInvalidPredicates(final UpdateRequest request) { 692 return request.getOperations().stream() 693 .flatMap(x -> { 694 if (x instanceof UpdateModify) { 695 final UpdateModify y = (UpdateModify)x; 696 return Stream.concat(y.getInsertQuads().stream(), y.getDeleteQuads().stream()); 697 } else if (x instanceof UpdateData) { 698 return ((UpdateData)x).getQuads().stream(); 699 } else if (x instanceof UpdateDeleteWhere) { 700 return ((UpdateDeleteWhere)x).getQuads().stream(); 701 } else { 702 return Stream.empty(); 703 } 704 }) 705 .filter(x -> x.getPredicate().isURI() && x.getPredicate().getURI().endsWith("/")) 706 .map(x -> new IllegalArgumentException("Invalid predicate ends with '/': " + x.getPredicate().getURI())) 707 .collect(Collectors.toList()); 708 } 709 710 private Node getFrozenNode(final String label) throws RepositoryException { 711 try { 712 final Session session = getSession(); 713 714 final Node frozenNode = session.getNodeByIdentifier(label); 715 716 final String baseUUID = getNode().getIdentifier(); 717 718 /* 719 * We found a node whose identifier is the "label" for the version. Now 720 * we must do due dilligence to make sure it's a frozen node representing 721 * a version of the subject node. 722 */ 723 final Property p = frozenNode.getProperty("jcr:frozenUuid"); 724 if (p != null) { 725 if (p.getString().equals(baseUUID)) { 726 return frozenNode; 727 } 728 } 729 /* 730 * Though a node with an id of the label was found, it wasn't the 731 * node we were looking for, so fall through and look for a labeled 732 * node. 733 */ 734 } catch (final ItemNotFoundException ex) { 735 /* 736 * the label wasn't a uuid of a frozen node but 737 * instead possibly a version label. 738 */ 739 } 740 return null; 741 } 742 743 @Override 744 public boolean equals(final Object object) { 745 if (object instanceof FedoraResourceImpl) { 746 return ((FedoraResourceImpl) object).getNode().equals(this.getNode()); 747 } 748 return false; 749 } 750 751 @Override 752 public int hashCode() { 753 return getNode().hashCode(); 754 } 755 756 protected Session getSession() { 757 try { 758 return getNode().getSession(); 759 } catch (final RepositoryException e) { 760 throw new RepositoryRuntimeException(e); 761 } 762 } 763 764 @Override 765 public String toString() { 766 return getNode().toString(); 767 } 768}