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}