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}