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