001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.impl.models; 007 008import org.apache.jena.graph.Triple; 009import org.fcrepo.kernel.api.RdfStream; 010import org.fcrepo.kernel.api.Transaction; 011import org.fcrepo.kernel.api.cache.UserTypesCache; 012import org.fcrepo.kernel.api.exception.ItemNotFoundException; 013import org.fcrepo.kernel.api.exception.PathNotFoundException; 014import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException; 015import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 016import org.fcrepo.kernel.api.identifiers.FedoraId; 017import org.fcrepo.kernel.api.models.FedoraResource; 018import org.fcrepo.kernel.api.models.ResourceFactory; 019import org.fcrepo.kernel.api.models.TimeMap; 020import org.fcrepo.kernel.api.rdf.DefaultRdfStream; 021import org.fcrepo.persistence.api.PersistentStorageSession; 022import org.fcrepo.persistence.api.PersistentStorageSessionManager; 023import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 024import org.fcrepo.persistence.api.exceptions.PersistentStorageException; 025 026import java.net.URI; 027import java.time.Duration; 028import java.time.Instant; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.List; 032import java.util.Optional; 033import java.util.stream.Stream; 034 035import static java.net.URI.create; 036import static java.util.stream.Collectors.toList; 037import static org.apache.jena.graph.NodeFactory.createURI; 038import static org.apache.jena.vocabulary.RDF.type; 039import static org.fcrepo.kernel.api.RdfLexicon.ARCHIVAL_GROUP; 040import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE; 041import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_TYPE; 042import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_ROOT; 043import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 044import static org.fcrepo.kernel.api.RdfLexicon.VERSIONED_RESOURCE; 045import static org.fcrepo.kernel.api.RdfLexicon.VERSIONING_TIMEGATE_TYPE; 046 047/** 048 * Implementation of a Fedora resource, containing functionality common to the more concrete resource implementations. 049 * 050 * @author bbpennel 051 */ 052public class FedoraResourceImpl implements FedoraResource { 053 054 private static final URI RESOURCE_URI = create(RESOURCE.toString()); 055 private static final URI FEDORA_RESOURCE_URI = create(FEDORA_RESOURCE.getURI()); 056 private static final URI ARCHIVAL_GROUP_URI = create(ARCHIVAL_GROUP.getURI()); 057 private static final URI MEMENTO_URI = create(MEMENTO_TYPE); 058 private static final URI VERSIONED_RESOURCE_URI = create(VERSIONED_RESOURCE.getURI()); 059 private static final URI VERSIONING_TIMEGATE_URI = create(VERSIONING_TIMEGATE_TYPE); 060 private static final URI REPOSITORY_ROOT_URI = create(REPOSITORY_ROOT.getURI()); 061 062 private final PersistentStorageSessionManager pSessionManager; 063 064 protected final ResourceFactory resourceFactory; 065 066 private final UserTypesCache userTypesCache; 067 068 protected final FedoraId fedoraId; 069 070 private FedoraId parentId; 071 072 private FedoraId archivalGroupId; 073 074 private List<URI> types; 075 076 private List<URI> systemTypes; 077 078 private List<URI> systemTypesForRdf; 079 080 private List<URI> userTypes; 081 082 private Instant lastModifiedDate; 083 084 private String lastModifiedBy; 085 086 private Instant createdDate; 087 088 private String createdBy; 089 090 private Instant mementoDatetime; 091 092 private String stateToken; 093 094 private String etag; 095 096 private boolean isMemento; 097 098 private String interactionModel; 099 100 // The transaction this representation of the resource belongs to 101 protected final Transaction transaction; 102 103 private boolean isArchivalGroup; 104 105 protected FedoraResourceImpl(final FedoraId fedoraId, 106 final Transaction transaction, 107 final PersistentStorageSessionManager pSessionManager, 108 final ResourceFactory resourceFactory, 109 final UserTypesCache userTypesCache) { 110 this.fedoraId = fedoraId; 111 this.transaction = transaction; 112 this.pSessionManager = pSessionManager; 113 this.resourceFactory = resourceFactory; 114 this.userTypesCache = userTypesCache; 115 } 116 117 @Override 118 public String getId() { 119 return this.fedoraId.getResourceId(); 120 } 121 122 @Override 123 public Stream<FedoraResource> getChildren(final Boolean recursive) { 124 return Stream.empty(); 125 } 126 127 @Override 128 public FedoraResource getContainer() { 129 return resourceFactory.getContainer(transaction, fedoraId); 130 } 131 132 @Override 133 public FedoraResource getOriginalResource() { 134 if (isMemento()) { 135 try { 136 // We are in a memento so we need to create a FedoraId for just the original resource. 137 final var fedoraId = FedoraId.create(getFedoraId().getResourceId()); 138 return getFedoraResource(fedoraId); 139 } catch (final PathNotFoundException e) { 140 throw new PathNotFoundRuntimeException(e.getMessage(), e); 141 } 142 } 143 return this; 144 } 145 146 private FedoraResource getFedoraResource(final FedoraId fedoraId) throws PathNotFoundException { 147 return resourceFactory.getResource(transaction, fedoraId); 148 } 149 150 @Override 151 public TimeMap getTimeMap() { 152 return new TimeMapImpl(this.getOriginalResource(), transaction, pSessionManager, resourceFactory); 153 } 154 155 @Override 156 public Instant getMementoDatetime() { 157 return mementoDatetime; 158 } 159 160 @Override 161 public boolean isMemento() { 162 return isMemento; 163 } 164 165 @Override 166 public boolean isAcl() { 167 return false; 168 } 169 170 @Override 171 public FedoraResource findMementoByDatetime(final Instant mementoDatetime) { 172 FedoraResource match = null; 173 long matchDiff = 0; 174 175 for (final var it = getTimeMap().getChildren().iterator(); it.hasNext();) { 176 final var current = it.next(); 177 // Negative if the memento is AFTER the requested datetime 178 // Positive if the memento is BEFORE the requested datetime 179 final var diff = Duration.between(current.getMementoDatetime(), mementoDatetime).toSeconds(); 180 181 if (match == null // Save the first memento examined 182 || (matchDiff < 0 && diff >= matchDiff) // Match is AFTER requested && current is closer 183 || (diff >= 0 && diff <= matchDiff)) { // Current memento EQUAL/BEFORE request && closer than match 184 match = current; 185 matchDiff = diff; 186 } 187 } 188 189 return match; 190 } 191 192 @Override 193 public FedoraResource getAcl() { 194 if (isAcl()) { 195 return this; 196 } 197 try { 198 final var aclId = fedoraId.asAcl(); 199 return getFedoraResource(aclId); 200 } catch (final PathNotFoundException e) { 201 return null; 202 } 203 } 204 205 @Override 206 public boolean hasProperty(final String relPath) { 207 // TODO Auto-generated method stub 208 return false; 209 } 210 211 @Override 212 public Instant getCreatedDate() { 213 return createdDate; 214 } 215 216 @Override 217 public Instant getLastModifiedDate() { 218 return lastModifiedDate; 219 } 220 221 @Override 222 public boolean hasType(final String type) { 223 return getTypes().contains(create(type)); 224 } 225 226 @Override 227 public List<URI> getTypes() { 228 if (types == null) { 229 types = new ArrayList<>(); 230 types.addAll(getSystemTypes(false)); 231 types.addAll(getUserTypes()); 232 } 233 return types; 234 } 235 236 @Override 237 public List<URI> getSystemTypes(final boolean forRdf) { 238 var types = resolveSystemTypes(forRdf); 239 240 if (types == null) { 241 types = new ArrayList<>(); 242 types.add(create(interactionModel)); 243 // ldp:Resource is on all resources 244 types.add(RESOURCE_URI); 245 types.add(FEDORA_RESOURCE_URI); 246 if (getFedoraId().isRepositoryRoot()) { 247 types.add(REPOSITORY_ROOT_URI); 248 } 249 if (!forRdf) { 250 // These types are not exposed as RDF triples. 251 if (isArchivalGroup) { 252 types.add(ARCHIVAL_GROUP_URI); 253 } 254 if (isMemento) { 255 types.add(MEMENTO_URI); 256 } else { 257 types.add(VERSIONED_RESOURCE_URI); 258 types.add(VERSIONING_TIMEGATE_URI); 259 } 260 } 261 262 if (forRdf) { 263 systemTypesForRdf = types; 264 } else { 265 systemTypes = types; 266 } 267 } 268 269 return types; 270 } 271 272 @Override 273 public List<URI> getUserTypes() { 274 if (userTypes == null) { 275 try { 276 final var description = getDescription(); 277 final var descId = description.getFedoraId().asResourceId(); 278 279 // Memento types are not cached 280 if (description.getMementoDatetime() == null && userTypesCache != null) { 281 userTypes = userTypesCache.getUserTypes(descId, 282 getSession().getId(), () -> getSession().getTriples(descId, null)); 283 } else { 284 final var triples = getSession().getTriples(descId, description.getMementoDatetime()); 285 userTypes = triples.filter(t -> t.predicateMatches(type.asNode())) 286 .map(Triple::getObject) 287 .map(t -> URI.create(t.toString())) 288 .collect(toList()); 289 } 290 } catch (final PersistentItemNotFoundException e) { 291 final var headers = getSession().getHeaders(getFedoraId().asResourceId(), getMementoDatetime()); 292 if (headers.isDeleted()) { 293 userTypes = Collections.emptyList(); 294 } else { 295 throw new ItemNotFoundException("Unable to retrieve triples for " + getId(), e); 296 } 297 } catch (final PersistentStorageException e) { 298 throw new RepositoryRuntimeException(e.getMessage(), e); 299 } 300 } 301 302 return userTypes; 303 } 304 305 @Override 306 public RdfStream getTriples() { 307 try { 308 final var subject = createURI(getId()); 309 final var triples = getSession().getTriples(getFedoraId().asResourceId(), getMementoDatetime()); 310 311 return new DefaultRdfStream(subject, triples); 312 } catch (final PersistentItemNotFoundException e) { 313 throw new ItemNotFoundException("Unable to retrieve triples for " + getId(), e); 314 } catch (final PersistentStorageException e) { 315 throw new RepositoryRuntimeException(e.getMessage(), e); 316 } 317 } 318 319 @Override 320 public String getEtagValue() { 321 return etag; 322 } 323 324 @Override 325 public String getStateToken() { 326 // TODO Auto-generated method stub 327 return stateToken; 328 } 329 330 @Override 331 public boolean isOriginalResource() { 332 return !isMemento(); 333 } 334 335 @Override 336 public FedoraResource getDescription() { 337 return this; 338 } 339 340 @Override 341 public FedoraResource getDescribedResource() { 342 return this; 343 } 344 345 protected PersistentStorageSession getSession() { 346 if (transaction.isOpen()) { 347 return pSessionManager.getSession(transaction); 348 } else { 349 return pSessionManager.getReadOnlySession(); 350 } 351 } 352 353 @Override 354 public FedoraResource getParent() throws PathNotFoundException { 355 return resourceFactory.getResource(transaction, parentId); 356 } 357 358 @Override 359 public FedoraId getParentId() { 360 return parentId; 361 } 362 363 @Override 364 public String getCreatedBy() { 365 return createdBy; 366 } 367 368 @Override 369 public String getLastModifiedBy() { 370 return lastModifiedBy; 371 } 372 373 @Override 374 public FedoraId getFedoraId() { 375 return this.fedoraId; 376 } 377 378 @Override 379 public String getInteractionModel() { 380 return this.interactionModel; 381 } 382 383 /** 384 * @param parentId the parentId to set 385 */ 386 protected void setParentId(final FedoraId parentId) { 387 this.parentId = parentId; 388 } 389 390 /** 391 * @param types the types to set 392 */ 393 protected void setTypes(final List<URI> types) { 394 this.types = types; 395 } 396 397 /** 398 * @param lastModifiedDate the lastModifiedDate to set 399 */ 400 protected void setLastModifiedDate(final Instant lastModifiedDate) { 401 this.lastModifiedDate = lastModifiedDate; 402 } 403 404 /** 405 * @param lastModifiedBy the lastModifiedBy to set 406 */ 407 protected void setLastModifiedBy(final String lastModifiedBy) { 408 this.lastModifiedBy = lastModifiedBy; 409 } 410 411 /** 412 * @param createdDate the createdDate to set 413 */ 414 protected void setCreatedDate(final Instant createdDate) { 415 this.createdDate = createdDate; 416 } 417 418 /** 419 * @param createdBy the createdBy to set 420 */ 421 protected void setCreatedBy(final String createdBy) { 422 this.createdBy = createdBy; 423 } 424 425 /** 426 * @param mementoDatetime the mementoDatetime to set 427 */ 428 protected void setMementoDatetime(final Instant mementoDatetime) { 429 this.mementoDatetime = mementoDatetime; 430 } 431 432 /** 433 * @param stateToken the stateToken to set 434 */ 435 protected void setStateToken(final String stateToken) { 436 this.stateToken = stateToken; 437 } 438 439 /** 440 * @param etag the etag to set 441 */ 442 protected void setEtag(final String etag) { 443 this.etag = etag; 444 } 445 446 /** 447 * @param isMemento indicates if the resource is a memento 448 */ 449 public void setIsMemento(final boolean isMemento) { 450 this.isMemento = isMemento; 451 } 452 453 /** 454 * @param isArchivalGroup true if the resource is an AG 455 */ 456 public void setIsArchivalGroup(final boolean isArchivalGroup) { 457 this.isArchivalGroup = isArchivalGroup; 458 } 459 460 /** 461 * @param interactionModel the resource's interaction model 462 */ 463 public void setInteractionModel(final String interactionModel) { 464 this.interactionModel = interactionModel; 465 } 466 467 protected List<URI> resolveSystemTypes(final boolean forRdf) { 468 return forRdf ? systemTypesForRdf : systemTypes; 469 } 470 471 /** 472 * @param archivalGroupId the FedoraId of the Archival Group for this resource 473 */ 474 public void setArchivalGroupId(final FedoraId archivalGroupId) { 475 this.archivalGroupId = archivalGroupId; 476 } 477 478 public Optional<FedoraId> getArchivalGroupId() { 479 return Optional.ofNullable(archivalGroupId); 480 } 481}