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}