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