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