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