001/*
002 * (C) Copyright 2019 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.blob;
020
021import java.io.OutputStream;
022import java.security.DigestOutputStream;
023import java.security.MessageDigest;
024import java.security.NoSuchAlgorithmException;
025import java.util.Objects;
026import java.util.function.Consumer;
027import java.util.function.Supplier;
028
029import org.apache.commons.codec.binary.Hex;
030import org.apache.commons.lang3.mutable.MutableObject;
031import org.nuxeo.ecm.core.api.NuxeoException;
032
033/**
034 * Represents computation of blob keys based on a message digest.
035 *
036 * @since 11.1
037 */
038public class KeyStrategyDigest implements KeyStrategy {
039
040    public final String digestAlgorithm;
041
042    public KeyStrategyDigest(String digestAlgorithm) {
043        Objects.requireNonNull(digestAlgorithm);
044        this.digestAlgorithm = digestAlgorithm;
045    }
046
047    @Override
048    public boolean useDeDuplication() {
049        return true;
050    }
051
052    @Override
053    public String getDigestFromKey(String key) {
054        return key;
055    }
056
057    @Override
058    public BlobWriteContext getBlobWriteContext(BlobContext blobContext)  {
059        MutableObject<String> keyHolder = new MutableObject<>();
060        WriteObserver writeObserver = new WriteObserverDigest(digestAlgorithm, keyHolder::setValue);
061        Supplier<String> keyComputer = keyHolder::getValue;
062        return new BlobWriteContext(blobContext, writeObserver, keyComputer, this);
063    }
064
065    /**
066     * Write observer computing a digest. The final digest is made available to the key consumer.
067     *
068     * @since 11.1
069     */
070    public static class WriteObserverDigest implements WriteObserver {
071
072        protected final MessageDigest messageDigest;
073
074        protected final Consumer<String> keyConsumer;
075
076        protected DigestOutputStream dos;
077
078        public WriteObserverDigest(String digestAlgorithm, Consumer<String> keyConsumer) {
079            try {
080                messageDigest = MessageDigest.getInstance(digestAlgorithm);
081            } catch (NoSuchAlgorithmException e) {
082                throw new NuxeoException(e);
083            }
084            this.keyConsumer = keyConsumer;
085        }
086
087        @Override
088        public OutputStream wrap(OutputStream out) {
089            dos = new DigestOutputStream(out, messageDigest);
090            return dos;
091        }
092
093        @Override
094        public void done() {
095            String key = Hex.encodeHexString(dos.getMessageDigest().digest());
096            keyConsumer.accept(key);
097        }
098    }
099
100    @Override
101    public boolean equals(Object obj) {
102        if (!(obj instanceof KeyStrategyDigest)) {
103            return false;
104        }
105        KeyStrategyDigest other = (KeyStrategyDigest) obj;
106        return digestAlgorithm.equals(other.digestAlgorithm);
107    }
108
109    @Override
110    public int hashCode() {
111        return digestAlgorithm.hashCode();
112    }
113
114}