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 static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER;
021import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER;
022import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI;
023import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_WEBAC_ACL_URI;
024import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER;
025import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE;
026import static org.slf4j.LoggerFactory.getLogger;
027
028import java.time.Instant;
029import java.util.stream.Stream;
030
031import javax.inject.Inject;
032
033import org.fcrepo.kernel.api.ContainmentIndex;
034import org.fcrepo.kernel.api.Transaction;
035import org.fcrepo.kernel.api.exception.PathNotFoundException;
036import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException;
037import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
038import org.fcrepo.kernel.api.exception.ResourceTypeException;
039import org.fcrepo.kernel.api.identifiers.FedoraId;
040import org.fcrepo.kernel.api.models.Binary;
041import org.fcrepo.kernel.api.models.FedoraResource;
042import org.fcrepo.kernel.api.models.ResourceFactory;
043import org.fcrepo.kernel.api.models.ResourceHeaders;
044import org.fcrepo.persistence.api.PersistentStorageSession;
045import org.fcrepo.persistence.api.PersistentStorageSessionManager;
046import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException;
047import org.fcrepo.persistence.api.exceptions.PersistentStorageException;
048
049import org.slf4j.Logger;
050import org.springframework.beans.factory.annotation.Autowired;
051import org.springframework.beans.factory.annotation.Qualifier;
052import org.springframework.stereotype.Component;
053
054/**
055 * Implementation of ResourceFactory interface.
056 *
057 * @author whikloj
058 * @since 2019-09-23
059 */
060@Component
061public class ResourceFactoryImpl implements ResourceFactory {
062
063    private static final Logger LOGGER = getLogger(ResourceFactoryImpl.class);
064
065    @Inject
066    private PersistentStorageSessionManager persistentStorageSessionManager;
067
068    @Autowired
069    @Qualifier("containmentIndex")
070    private ContainmentIndex containmentIndex;
071
072    @Override
073    public FedoraResource getResource(final Transaction transaction, final FedoraId fedoraID)
074            throws PathNotFoundException {
075        return instantiateResource(transaction, fedoraID);
076    }
077
078    @Override
079    public <T extends FedoraResource> T getResource(final Transaction transaction, final FedoraId identifier,
080                                                    final Class<T> clazz) throws PathNotFoundException {
081        return clazz.cast(getResource(transaction, identifier));
082    }
083
084
085    @Override
086    public FedoraResource getContainer(final Transaction transaction, final FedoraId resourceId) {
087        final String containerId = containmentIndex.getContainedBy(transaction, resourceId);
088        if (containerId == null) {
089            return null;
090        }
091        try {
092            return getResource(transaction, FedoraId.create(containerId));
093        } catch (final PathNotFoundException exc) {
094            return null;
095        }
096    }
097
098    /**
099     * Returns the appropriate FedoraResource class for an object based on the provided headers
100     *
101     * @param headers headers for the resource being constructed
102     * @return FedoraResource class
103     */
104    private Class<? extends FedoraResourceImpl> getClassForTypes(final ResourceHeaders headers) {
105        final var ixModel = headers.getInteractionModel();
106        if (BASIC_CONTAINER.getURI().equals(ixModel) || INDIRECT_CONTAINER.getURI().equals(ixModel)
107                || DIRECT_CONTAINER.getURI().equals(ixModel)) {
108            return ContainerImpl.class;
109        }
110        if (NON_RDF_SOURCE.getURI().equals(ixModel)) {
111            return BinaryImpl.class;
112        }
113        if (FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI.equals(ixModel)) {
114            return NonRdfSourceDescriptionImpl.class;
115        }
116        if (FEDORA_WEBAC_ACL_URI.equals(ixModel)) {
117            return WebacAclImpl.class;
118        }
119        // TODO add the rest of the types
120        throw new ResourceTypeException("Could not identify the resource type for interaction model " + ixModel);
121    }
122
123    /**
124     * Instantiates a new FedoraResource object of the given class.
125     *
126     * @param transaction the transaction id
127     * @param identifier    identifier for the new instance
128     * @return new FedoraResource instance
129     * @throws PathNotFoundException
130     */
131    private FedoraResource instantiateResource(final Transaction transaction,
132                                               final FedoraId identifier)
133            throws PathNotFoundException {
134        try {
135            // For descriptions and ACLs we need the actual endpoint.
136            final var psSession = getSession(transaction);
137            final Instant versionDateTime = identifier.isMemento() ? identifier.getMementoInstant() : null;
138
139            final ResourceHeaders headers = psSession.getHeaders(identifier, versionDateTime);
140
141            // Determine the appropriate class from headers
142            final var createClass = getClassForTypes(headers);
143
144            // Retrieve standard constructor
145            final var constructor = createClass.getConstructor(
146                    FedoraId.class,
147                    Transaction.class,
148                    PersistentStorageSessionManager.class,
149                    ResourceFactory.class);
150
151            // If identifier is to a TimeMap we need to avoid creating a original resource with a Timemap FedoraId
152            final var instantiationId = identifier.isTimemap() ?
153                    FedoraId.create(identifier.getResourceId()) : identifier;
154
155            final var rescImpl = constructor.newInstance(instantiationId, transaction,
156                    persistentStorageSessionManager, this);
157            populateResourceHeaders(rescImpl, headers, versionDateTime);
158
159            if (headers.isDeleted()) {
160                final var rootId = FedoraId.create(identifier.getBaseId());
161                final var tombstone = new TombstoneImpl(rootId, transaction, persistentStorageSessionManager,
162                        this, rescImpl);
163                tombstone.setLastModifiedDate(headers.getLastModifiedDate());
164                return tombstone;
165            } else if (identifier.isTimemap()) {
166                // If identifier is a TimeMap, now we can return the virtual resource.
167                return rescImpl.getTimeMap();
168            }
169            return rescImpl;
170        } catch (final SecurityException | ReflectiveOperationException e) {
171            throw new RepositoryRuntimeException("Unable to construct object", e);
172        } catch (final PersistentItemNotFoundException e) {
173            throw new PathNotFoundException(e.getMessage(), e);
174        } catch (final PersistentStorageException e) {
175            throw new RepositoryRuntimeException(e.getMessage(), e);
176        }
177    }
178
179    private void populateResourceHeaders(final FedoraResourceImpl resc,
180                                         final ResourceHeaders headers, final Instant version) {
181        resc.setCreatedBy(headers.getCreatedBy());
182        resc.setCreatedDate(headers.getCreatedDate());
183        resc.setLastModifiedBy(headers.getLastModifiedBy());
184        resc.setLastModifiedDate(headers.getLastModifiedDate());
185        resc.setParentId(headers.getParent());
186        resc.setEtag(headers.getStateToken());
187        resc.setStateToken(headers.getStateToken());
188        resc.setIsArchivalGroup(headers.isArchivalGroup());
189        resc.setInteractionModel(headers.getInteractionModel());
190
191        // If there's a version, then it's a memento
192        if (version != null) {
193            resc.setIsMemento(true);
194            resc.setMementoDatetime(version);
195        }
196
197        if (resc instanceof Binary) {
198            final var binary = (BinaryImpl) resc;
199            binary.setContentSize(headers.getContentSize());
200            binary.setExternalHandling(headers.getExternalHandling());
201            binary.setExternalUrl(headers.getExternalUrl());
202            binary.setDigests(headers.getDigests());
203            binary.setFilename(headers.getFilename());
204            binary.setMimeType(headers.getMimeType());
205        }
206    }
207
208    /**
209     * Get a session for this interaction.
210     *
211     * @param transaction The supplied transaction.
212     * @return a storage session.
213     */
214    private PersistentStorageSession getSession(final Transaction transaction) {
215        final PersistentStorageSession session;
216        if (transaction.isReadOnly() || !transaction.isOpen()) {
217            session = persistentStorageSessionManager.getReadOnlySession();
218        } else {
219            session = persistentStorageSessionManager.getSession(transaction);
220        }
221        return session;
222    }
223
224    @Override
225    public Stream<FedoraResource> getChildren(final Transaction transaction, final FedoraId resourceId) {
226        return containmentIndex.getContains(transaction, resourceId)
227            .map(childId -> {
228                try {
229                    return getResource(transaction, FedoraId.create(childId));
230                } catch (final PathNotFoundException e) {
231                    throw new PathNotFoundRuntimeException(e.getMessage(), e);
232                }
233            });
234    }
235}