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.services;
007
008import org.fcrepo.kernel.api.Transaction;
009import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
010import org.fcrepo.kernel.api.identifiers.FedoraId;
011import org.fcrepo.kernel.api.models.ExternalContent;
012import org.fcrepo.kernel.api.operations.NonRdfSourceOperationBuilder;
013import org.fcrepo.kernel.api.operations.NonRdfSourceOperationFactory;
014import org.fcrepo.kernel.api.services.ReplaceBinariesService;
015import org.fcrepo.persistence.api.PersistentStorageSession;
016import org.fcrepo.persistence.api.PersistentStorageSessionManager;
017import org.fcrepo.persistence.api.exceptions.PersistentStorageException;
018import org.fcrepo.persistence.common.MultiDigestInputStreamWrapper;
019import org.slf4j.Logger;
020import org.springframework.stereotype.Component;
021
022import javax.inject.Inject;
023
024import java.io.InputStream;
025import java.net.URI;
026import java.util.Collection;
027import java.util.Collections;
028
029import static java.lang.String.format;
030import static org.slf4j.LoggerFactory.getLogger;
031
032/**
033 * Implementation of a service for replacing/updating binary resources
034 *
035 * @author bbpennel
036 */
037@Component
038public class ReplaceBinariesServiceImpl extends AbstractService implements ReplaceBinariesService {
039
040    private static final Logger LOGGER = getLogger(ReplaceBinariesServiceImpl.class);
041
042    @Inject
043    private PersistentStorageSessionManager psManager;
044
045    @Inject
046    private NonRdfSourceOperationFactory factory;
047
048    @Override
049    public void perform(final Transaction tx,
050                        final String userPrincipal,
051                        final FedoraId fedoraId,
052                        final String filename,
053                        final String contentType,
054                        final Collection<URI> digests,
055                        final InputStream contentBody,
056                        final long contentSize,
057                        final ExternalContent externalContent) {
058        try {
059            final PersistentStorageSession pSession = this.psManager.getSession(tx);
060
061            String mimeType = contentType;
062            long size = contentSize;
063            final NonRdfSourceOperationBuilder builder;
064            if (externalContent == null || externalContent.isCopy()) {
065                var contentInputStream = contentBody;
066                if (externalContent != null) {
067                    LOGGER.debug("External content COPY '{}', '{}'", fedoraId, externalContent.getURL());
068                    contentInputStream = externalContent.fetchExternalContent();
069                }
070
071                builder = factory.updateInternalBinaryBuilder(tx, fedoraId, contentInputStream);
072            } else {
073                builder = factory.updateExternalBinaryBuilder(tx, fedoraId,
074                        externalContent.getHandling(),
075                        externalContent.getURI());
076
077                if (contentSize == -1L) {
078                    size = externalContent.getContentSize();
079                }
080                if (!digests.isEmpty()) {
081                    final var multiDigestWrapper = new MultiDigestInputStreamWrapper(
082                            externalContent.fetchExternalContent(),
083                            digests,
084                            Collections.emptyList());
085                    multiDigestWrapper.checkFixity();
086                }
087            }
088
089            if (externalContent != null && externalContent.getContentType() != null) {
090                mimeType = externalContent.getContentType();
091            }
092
093            builder.mimeType(mimeType)
094                   .contentSize(size)
095                   .filename(filename)
096                   .contentDigests(digests)
097                   .userPrincipal(userPrincipal);
098            final var replaceOp = builder.build();
099
100            lockArchivalGroupResource(tx, pSession, fedoraId);
101            tx.lockResource(fedoraId);
102            // Descriptions are always under the binary, so just lock it.
103            tx.lockResource(fedoraId.asDescription());
104
105            pSession.persist(replaceOp);
106            this.searchIndex.addUpdateIndex(tx, pSession.getHeaders(fedoraId, null));
107            recordEvent(tx, fedoraId, replaceOp);
108        } catch (final PersistentStorageException ex) {
109            throw new RepositoryRuntimeException(format("failed to replace binary %s",
110                    fedoraId), ex);
111        }
112    }
113
114}