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}