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