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