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