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 static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
009import static org.apache.jena.rdf.model.ResourceFactory.createTypedLiteral;
010import static org.apache.jena.vocabulary.RDF.type;
011import static org.fcrepo.kernel.api.RdfLexicon.HAS_FIXITY_RESULT;
012import static org.fcrepo.kernel.api.RdfLexicon.HAS_FIXITY_STATE;
013import static org.fcrepo.kernel.api.RdfLexicon.HAS_MESSAGE_DIGEST;
014import static org.fcrepo.kernel.api.RdfLexicon.HAS_SIZE;
015import static org.fcrepo.kernel.api.RdfLexicon.PREMIS_EVENT_OUTCOME_DETAIL;
016import static org.fcrepo.kernel.api.RdfLexicon.PREMIS_FIXITY;
017
018import org.apache.jena.rdf.model.Literal;
019import org.apache.jena.rdf.model.Model;
020import org.apache.jena.rdf.model.Resource;
021import org.fcrepo.kernel.api.RdfStream;
022import org.fcrepo.kernel.api.exception.InvalidChecksumException;
023import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
024import org.fcrepo.kernel.api.exception.UnsupportedAlgorithmException;
025import org.fcrepo.kernel.api.models.Binary;
026import org.fcrepo.kernel.api.rdf.DefaultRdfStream;
027import org.fcrepo.kernel.api.services.FixityService;
028import org.fcrepo.config.DigestAlgorithm;
029import org.fcrepo.persistence.common.MultiDigestInputStreamWrapper;
030import org.springframework.stereotype.Component;
031
032import java.io.IOException;
033import java.net.URI;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.List;
037import java.util.UUID;
038import java.util.stream.Collectors;
039
040/**
041 * Implementation of {@link org.fcrepo.kernel.api.services.FixityService}
042 *
043 * @author dbernstein
044 * @author whikloj
045 */
046@Component
047public class FixityServiceImpl extends AbstractService implements FixityService {
048
049    private static final Literal successResource = createTypedLiteral("SUCCESS");
050    private static final Literal badChecksumResource = createTypedLiteral("BAD_CHECKSUM");
051
052    @Override
053    public Collection<URI> getFixity(final Binary binary, final Collection<String> algorithms)
054            throws UnsupportedAlgorithmException {
055        final var digestAlgs = algorithms.stream()
056                .map(DigestAlgorithm::fromAlgorithm)
057                .collect(Collectors.toList());
058
059        try (final var content = binary.getContent()) {
060            final MultiDigestInputStreamWrapper digestWrapper = new MultiDigestInputStreamWrapper(content, null,
061                    digestAlgs);
062            return digestWrapper.getDigests();
063        } catch (final IOException e) {
064            // input stream closed prematurely.
065            throw new RepositoryRuntimeException("Problem reading content stream from " + binary.getId(), e);
066        }
067    }
068
069    @Override
070    public RdfStream checkFixity(final Binary binary)
071            throws InvalidChecksumException {
072        final Model model = createDefaultModel();
073        final Resource subject = model.createResource(binary.getId());
074        final Resource fixityResult = model.createResource(
075                binary.getFedoraId().resolve("#" + UUID.randomUUID().toString()).getFullId());
076        model.add(subject, HAS_FIXITY_RESULT, fixityResult);
077        model.add(fixityResult, type, PREMIS_FIXITY);
078        model.add(fixityResult, type, PREMIS_EVENT_OUTCOME_DETAIL);
079        model.add(fixityResult, HAS_SIZE, createTypedLiteral(binary.getContentSize()));
080
081        // Built for more than one digest in anticipation of FCREPO-3419
082        final List<URI> existingDigestList = new ArrayList<>();
083        existingDigestList.addAll(binary.getContentDigests());
084
085        final var digestAlgs = existingDigestList.stream()
086                .map(URI::toString)
087                .map(a -> a.replace("urn:", "").split(":")[0])
088                .map(DigestAlgorithm::fromAlgorithm)
089                .collect(Collectors.toList());
090
091        try (final var content = binary.getContent()) {
092            final MultiDigestInputStreamWrapper digestWrapper = new MultiDigestInputStreamWrapper(content,
093                    existingDigestList, digestAlgs);
094            digestWrapper.getDigests().forEach(d ->
095                    model.add(fixityResult, HAS_MESSAGE_DIGEST, model.createResource(d.toString())));
096            digestWrapper.checkFixity();
097            model.add(fixityResult, HAS_FIXITY_STATE, successResource);
098        } catch (final IOException e) {
099            // input stream closed prematurely.
100            throw new RepositoryRuntimeException("Problem reading content stream from " + binary.getId(), e);
101        } catch (final InvalidChecksumException e) {
102            model.add(fixityResult, HAS_FIXITY_STATE, badChecksumResource);
103        }
104        return DefaultRdfStream.fromModel(subject.asNode(), model);
105    }
106}