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 Stream<FedoraResource> getChildren(final Boolean recursive) { 127 return Stream.empty(); 128 } 129 130 @Override 131 public FedoraResource getContainer() { 132 return resourceFactory.getContainer(txId, fedoraId); 133 } 134 135 @Override 136 public FedoraResource getOriginalResource() { 137 if (isMemento()) { 138 try { 139 // We are in a memento so we need to create a FedoraId for just the original resource. 140 final var fedoraId = FedoraId.create(getFedoraId().getResourceId()); 141 return getFedoraResource(fedoraId); 142 } catch (final PathNotFoundException e) { 143 throw new PathNotFoundRuntimeException(e.getMessage(), e); 144 } 145 } 146 return this; 147 } 148 149 private FedoraResource getFedoraResource(final FedoraId fedoraId) throws PathNotFoundException { 150 return resourceFactory.getResource(txId, fedoraId); 151 } 152 153 @Override 154 public TimeMap getTimeMap() { 155 return new TimeMapImpl(this.getOriginalResource(), txId, pSessionManager, resourceFactory); 156 } 157 158 @Override 159 public Instant getMementoDatetime() { 160 return mementoDatetime; 161 } 162 163 @Override 164 public boolean isMemento() { 165 return isMemento; 166 } 167 168 @Override 169 public boolean isAcl() { 170 return false; 171 } 172 173 @Override 174 public FedoraResource findMementoByDatetime(final Instant mementoDatetime) { 175 FedoraResource match = null; 176 long matchDiff = 0; 177 178 for (final var it = getTimeMap().getChildren().iterator(); it.hasNext();) { 179 final var current = it.next(); 180 // Negative if the memento is AFTER the requested datetime 181 // Positive if the memento is BEFORE the requested datetime 182 final var diff = Duration.between(current.getMementoDatetime(), mementoDatetime).toSeconds(); 183 184 if (match == null // Save the first memento examined 185 || (matchDiff < 0 && diff >= matchDiff) // Match is AFTER requested && current is closer 186 || (diff >= 0 && diff <= matchDiff)) { // Current memento EQUAL/BEFORE request && closer than match 187 match = current; 188 matchDiff = diff; 189 } 190 } 191 192 return match; 193 } 194 195 @Override 196 public FedoraResource getAcl() { 197 if (isAcl()) { 198 return this; 199 } 200 try { 201 final var aclId = fedoraId.asAcl(); 202 return getFedoraResource(aclId); 203 } catch (final PathNotFoundException e) { 204 return null; 205 } 206 } 207 208 @Override 209 public boolean hasProperty(final String relPath) { 210 // TODO Auto-generated method stub 211 return false; 212 } 213 214 @Override 215 public Instant getCreatedDate() { 216 return createdDate; 217 } 218 219 @Override 220 public Instant getLastModifiedDate() { 221 return lastModifiedDate; 222 } 223 224 @Override 225 public boolean hasType(final String type) { 226 return getTypes().contains(create(type)); 227 } 228 229 @Override 230 public List<URI> getTypes() { 231 if (types == null) { 232 types = new ArrayList<>(); 233 types.addAll(getSystemTypes(false)); 234 types.addAll(getUserTypes()); 235 } 236 return types; 237 } 238 239 @Override 240 public List<URI> getSystemTypes(final boolean forRdf) { 241 var types = resolveSystemTypes(forRdf); 242 243 if (types == null) { 244 types = new ArrayList<>(); 245 types.add(create(interactionModel)); 246 // ldp:Resource is on all resources 247 types.add(RESOURCE_URI); 248 types.add(FEDORA_RESOURCE_URI); 249 if (getFedoraId().isRepositoryRoot()) { 250 types.add(REPOSITORY_ROOT_URI); 251 } 252 if (!forRdf) { 253 // These types are not exposed as RDF triples. 254 if (isArchivalGroup) { 255 types.add(ARCHIVAL_GROUP_URI); 256 } 257 if (isMemento) { 258 types.add(MEMENTO_URI); 259 } else { 260 types.add(VERSIONED_RESOURCE_URI); 261 types.add(VERSIONING_TIMEGATE_URI); 262 } 263 } 264 265 if (forRdf) { 266 systemTypesForRdf = types; 267 } else { 268 systemTypes = types; 269 } 270 } 271 272 return types; 273 } 274 275 @Override 276 public List<URI> getUserTypes() { 277 if (userTypes == null) { 278 userTypes = new ArrayList<>(); 279 try { 280 final var description = getDescription(); 281 final var triples = getSession().getTriples(description.getFedoraId().asResourceId(), 282 description.getMementoDatetime()); 283 userTypes = triples.filter(t -> t.predicateMatches(type.asNode())).map(Triple::getObject) 284 .map(t -> URI.create(t.toString())).collect(toList()); 285 } catch (final PersistentItemNotFoundException e) { 286 final var headers = getSession().getHeaders(getFedoraId().asResourceId(), getMementoDatetime()); 287 if (headers.isDeleted()) { 288 userTypes = Collections.emptyList(); 289 } else { 290 throw new ItemNotFoundException("Unable to retrieve triples for " + getId(), e); 291 } 292 } catch (final PersistentStorageException e) { 293 throw new RepositoryRuntimeException(e.getMessage(), e); 294 } 295 } 296 297 return userTypes; 298 } 299 300 @Override 301 public RdfStream getTriples() { 302 try { 303 final var subject = createURI(getId()); 304 final var triples = getSession().getTriples(getFedoraId().asResourceId(), getMementoDatetime()); 305 306 return new DefaultRdfStream(subject, triples); 307 } catch (final PersistentItemNotFoundException e) { 308 throw new ItemNotFoundException("Unable to retrieve triples for " + getId(), e); 309 } catch (final PersistentStorageException e) { 310 throw new RepositoryRuntimeException(e.getMessage(), e); 311 } 312 } 313 314 @Override 315 public String getEtagValue() { 316 return etag; 317 } 318 319 @Override 320 public String getStateToken() { 321 // TODO Auto-generated method stub 322 return stateToken; 323 } 324 325 @Override 326 public boolean isOriginalResource() { 327 return !isMemento(); 328 } 329 330 @Override 331 public FedoraResource getDescription() { 332 return this; 333 } 334 335 @Override 336 public FedoraResource getDescribedResource() { 337 return this; 338 } 339 340 protected PersistentStorageSession getSession() { 341 if (txId == null) { 342 return pSessionManager.getReadOnlySession(); 343 } else { 344 return pSessionManager.getSession(txId); 345 } 346 } 347 348 @Override 349 public FedoraResource getParent() throws PathNotFoundException { 350 return resourceFactory.getResource(txId, parentId); 351 } 352 353 @Override 354 public String getCreatedBy() { 355 return createdBy; 356 } 357 358 @Override 359 public String getLastModifiedBy() { 360 return lastModifiedBy; 361 } 362 363 @Override 364 public FedoraId getFedoraId() { 365 return this.fedoraId; 366 } 367 368 @Override 369 public String getInteractionModel() { 370 return this.interactionModel; 371 } 372 373 /** 374 * @param parentId the parentId to set 375 */ 376 protected void setParentId(final FedoraId parentId) { 377 this.parentId = parentId; 378 } 379 380 /** 381 * @param types the types to set 382 */ 383 protected void setTypes(final List<URI> types) { 384 this.types = types; 385 } 386 387 /** 388 * @param lastModifiedDate the lastModifiedDate to set 389 */ 390 protected void setLastModifiedDate(final Instant lastModifiedDate) { 391 this.lastModifiedDate = lastModifiedDate; 392 } 393 394 /** 395 * @param lastModifiedBy the lastModifiedBy to set 396 */ 397 protected void setLastModifiedBy(final String lastModifiedBy) { 398 this.lastModifiedBy = lastModifiedBy; 399 } 400 401 /** 402 * @param createdDate the createdDate to set 403 */ 404 protected void setCreatedDate(final Instant createdDate) { 405 this.createdDate = createdDate; 406 } 407 408 /** 409 * @param createdBy the createdBy to set 410 */ 411 protected void setCreatedBy(final String createdBy) { 412 this.createdBy = createdBy; 413 } 414 415 /** 416 * @param mementoDatetime the mementoDatetime to set 417 */ 418 protected void setMementoDatetime(final Instant mementoDatetime) { 419 this.mementoDatetime = mementoDatetime; 420 } 421 422 /** 423 * @param stateToken the stateToken to set 424 */ 425 protected void setStateToken(final String stateToken) { 426 this.stateToken = stateToken; 427 } 428 429 /** 430 * @param etag the etag to set 431 */ 432 protected void setEtag(final String etag) { 433 this.etag = etag; 434 } 435 436 /** 437 * @param isMemento indicates if the resource is a memento 438 */ 439 public void setIsMemento(final boolean isMemento) { 440 this.isMemento = isMemento; 441 } 442 443 /** 444 * @param isArchivalGroup true if the resource is an AG 445 */ 446 public void setIsArchivalGroup(final boolean isArchivalGroup) { 447 this.isArchivalGroup = isArchivalGroup; 448 } 449 450 /** 451 * @param interactionModel the resource's interaction model 452 */ 453 public void setInteractionModel(final String interactionModel) { 454 this.interactionModel = interactionModel; 455 } 456 457 protected List<URI> resolveSystemTypes(final boolean forRdf) { 458 return forRdf ? systemTypesForRdf : systemTypes; 459 } 460}