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.TIME_MAP.getURI()),
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 String txId,
082            final PersistentStorageSessionManager pSessionManager,
083            final ResourceFactory resourceFactory) {
084        super(originalResource.getFedoraId().asTimemap(), txId, 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(txId, 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}