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.rdf.model.ModelFactory; 009import org.apache.jena.rdf.model.Resource; 010import org.apache.jena.rdf.model.impl.StatementImpl; 011import org.fcrepo.kernel.api.RdfLexicon; 012import org.fcrepo.kernel.api.RdfStream; 013import org.fcrepo.kernel.api.Transaction; 014import org.fcrepo.kernel.api.exception.ItemNotFoundException; 015import org.fcrepo.kernel.api.exception.PathNotFoundException; 016import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException; 017import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 018import org.fcrepo.kernel.api.identifiers.FedoraId; 019import org.fcrepo.kernel.api.models.FedoraResource; 020import org.fcrepo.kernel.api.models.ResourceFactory; 021import org.fcrepo.kernel.api.models.TimeMap; 022import org.fcrepo.kernel.api.rdf.DefaultRdfStream; 023import org.fcrepo.persistence.api.PersistentStorageSessionManager; 024import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 025import org.fcrepo.persistence.api.exceptions.PersistentStorageException; 026 027import java.net.URI; 028import java.time.Instant; 029import java.util.Collections; 030import java.util.List; 031import java.util.stream.Collectors; 032import java.util.stream.Stream; 033 034import static java.net.URI.create; 035import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER; 036import static org.fcrepo.kernel.api.RdfLexicon.RDF_SOURCE; 037import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 038 039/** 040 * FedoraResource implementation that represents a Memento TimeMap of the base resource. 041 * 042 * @author pwinckles 043 */ 044public class TimeMapImpl extends FedoraResourceImpl implements TimeMap { 045 046 /** 047 * Types of this class that should be displayed 048 */ 049 private static final List<URI> HEADER_AND_RDF_TYPES = List.of( 050 create(RESOURCE.getURI()), 051 create(CONTAINER.getURI()), 052 create(RDF_SOURCE.getURI()) 053 ); 054 055 /** 056 * Above types but also types not to be displayed in RDF bodies. 057 */ 058 private static final List<URI> HEADER_ONLY_TYPES = Stream.concat(HEADER_AND_RDF_TYPES.stream(), 059 List.of( 060 create(RdfLexicon.VERSIONING_TIMEMAP.getURI()) 061 ).stream() 062 ).collect(Collectors.toList()); 063 064 private final FedoraResource originalResource; 065 private List<Instant> versions; 066 067 protected TimeMapImpl( 068 final FedoraResource originalResource, 069 final Transaction transaction, 070 final PersistentStorageSessionManager pSessionManager, 071 final ResourceFactory resourceFactory) { 072 super(originalResource.getFedoraId().asTimemap(), transaction, 073 pSessionManager, resourceFactory, null); 074 075 this.originalResource = originalResource; 076 setCreatedBy(originalResource.getCreatedBy()); 077 setCreatedDate(originalResource.getCreatedDate()); 078 setLastModifiedBy(originalResource.getLastModifiedBy()); 079 setLastModifiedDate(originalResource.getLastModifiedDate()); 080 setParentId(originalResource.getFedoraId().asResourceId()); 081 setEtag(originalResource.getEtagValue()); 082 setStateToken(originalResource.getStateToken()); 083 } 084 085 @Override 086 public RdfStream getTriples() { 087 final var timeMapResource = asResource(this); 088 final var model = ModelFactory.createDefaultModel(); 089 model.add(new StatementImpl(timeMapResource, RdfLexicon.MEMENTO_ORIGINAL_RESOURCE, 090 asResource(getOriginalResource()))); 091 getChildren().map(this::asResource).forEach(child -> { 092 model.add(new StatementImpl(timeMapResource, RdfLexicon.CONTAINS, child)); 093 }); 094 return DefaultRdfStream.fromModel(timeMapResource.asNode(), model); 095 } 096 097 @Override 098 public List<URI> getSystemTypes(final boolean forRdf) { 099 // TimeMaps don't have an on-disk representation so don't call super.getSystemTypes(). 100 if (forRdf) { 101 return HEADER_AND_RDF_TYPES; 102 } 103 return HEADER_ONLY_TYPES; 104 } 105 106 @Override 107 public List<URI> getUserTypes() { 108 // TimeMaps don't have user triples. 109 return Collections.emptyList(); 110 } 111 112 @Override 113 public Stream<FedoraResource> getChildren(final Boolean recursive) { 114 return getVersions().stream().map(version -> { 115 try { 116 final var fedoraId = getInstantFedoraId(version); 117 return resourceFactory.getResource(transaction, fedoraId); 118 } catch (final PathNotFoundException e) { 119 throw new PathNotFoundRuntimeException(e.getMessage(), e); 120 } 121 }); 122 } 123 124 @Override 125 public FedoraResource getOriginalResource() { 126 return originalResource; 127 } 128 129 @Override 130 public boolean isOriginalResource() { 131 return false; 132 } 133 134 @Override 135 public TimeMap getTimeMap() { 136 return this; 137 } 138 139 private List<Instant> getVersions() { 140 if (versions == null) { 141 try { 142 versions = getSession().listVersions(getFedoraId().asResourceId()); 143 } catch (final PersistentItemNotFoundException e) { 144 throw new ItemNotFoundException("Unable to retrieve versions for " + getId(), e); 145 } catch (final PersistentStorageException e) { 146 throw new RepositoryRuntimeException(e.getMessage(), e); 147 } 148 } 149 return versions; 150 } 151 152 private Resource asResource(final FedoraResource fedoraResource) { 153 return org.apache.jena.rdf.model.ResourceFactory.createResource(fedoraResource.getFedoraId().getFullId()); 154 } 155 156 /** 157 * Get a FedoraId for a memento with the specified version datetime. 158 * @param version The instant datetime. 159 * @return the new FedoraId for the current TimeMap and the version. 160 */ 161 private FedoraId getInstantFedoraId(final Instant version) { 162 return getFedoraId().asMemento(version); 163 } 164 165 @Override 166 public List<Instant> listMementoDatetimes() { 167 return getVersions(); 168 } 169 170 @Override 171 public String getInteractionModel() { 172 return CONTAINER.getURI(); 173 } 174}