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