001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (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, jcarsique
018 */
019
020package org.nuxeo.ecm.core.blob.binary;
021
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027import java.security.MessageDigest;
028import java.security.NoSuchAlgorithmException;
029import java.util.Collection;
030import java.util.HashMap;
031import java.util.Map;
032
033import org.nuxeo.common.xmap.XMap;
034import org.nuxeo.ecm.core.api.Blob;
035
036/**
037 * Abstract BinaryManager implementation that provides a few utilities
038 *
039 * @author Florent Guillaume
040 */
041public abstract class AbstractBinaryManager implements BinaryManager {
042
043    public static final String MD5_DIGEST = "MD5";
044
045    public static final String SHA1_DIGEST = "SHA-1";
046
047    public static final String SHA256_DIGEST = "SHA-256";
048
049    public static final int MD5_DIGEST_LENGTH = 32;
050
051    public static final int SHA1_DIGEST_LENGTH = 40;
052
053    public static final int SHA256_DIGEST_LENGTH = 64;
054
055    /**
056     * @since 7.4
057     */
058    public static final HashMap<Integer, String> DIGESTS_BY_LENGTH = new HashMap<>();
059
060    public static final String DEFAULT_DIGEST = MD5_DIGEST; // SHA256_DIGEST
061
062    public static final int DEFAULT_DEPTH = 2;
063
064    protected String blobProviderId;
065
066    protected BinaryManagerRootDescriptor descriptor;
067
068    protected BinaryGarbageCollector garbageCollector;
069
070    @Override
071    public void initialize(String blobProviderId, Map<String, String> properties) throws IOException {
072        this.blobProviderId = blobProviderId;
073        DIGESTS_BY_LENGTH.put(MD5_DIGEST_LENGTH, MD5_DIGEST);
074        DIGESTS_BY_LENGTH.put(SHA1_DIGEST_LENGTH, SHA1_DIGEST);
075        DIGESTS_BY_LENGTH.put(SHA256_DIGEST_LENGTH, SHA256_DIGEST);
076    }
077
078    /**
079     * Creates a binary value from the given input stream.
080     */
081    // not in the public API of BinaryManager anymore
082    abstract protected Binary getBinary(InputStream in) throws IOException;
083
084    /*
085     * This abstract implementation just opens the stream.
086     */
087    @Override
088    public Binary getBinary(Blob blob) throws IOException {
089        if (blob instanceof BinaryBlob) {
090            Binary binary = ((BinaryBlob) blob).getBinary();
091            if (binary.getBlobProviderId().equals(blobProviderId)) {
092                return binary;
093            }
094            // don't reuse the binary if it comes from another blob provider
095        }
096        try (InputStream stream = blob.getStream()) {
097            return getBinary(stream);
098        }
099    }
100
101    @Override
102    abstract public Binary getBinary(String digest);
103
104    @Override
105    public void removeBinaries(Collection<String> digests) {
106        throw new UnsupportedOperationException();
107    }
108
109    /**
110     * Gets existing descriptor or creates a default one.
111     */
112    protected BinaryManagerRootDescriptor getDescriptor(File configFile) throws IOException {
113        BinaryManagerRootDescriptor desc;
114        if (configFile.exists()) {
115            XMap xmap = new XMap();
116            xmap.register(BinaryManagerRootDescriptor.class);
117            desc = (BinaryManagerRootDescriptor) xmap.load(new FileInputStream(configFile));
118        } else {
119            desc = new BinaryManagerRootDescriptor();
120            // TODO fetch from repo descriptor
121            desc.digest = getDefaultDigestAlgorithm();
122            desc.depth = DEFAULT_DEPTH;
123            desc.write(configFile); // may throw IOException
124        }
125        return desc;
126    }
127
128    public static final int MIN_BUF_SIZE = 8 * 1024; // 8 kB
129
130    public static final int MAX_BUF_SIZE = 64 * 1024; // 64 kB
131
132    protected String storeAndDigest(InputStream in, OutputStream out) throws IOException {
133        MessageDigest digest;
134        try {
135            digest = MessageDigest.getInstance(getDigestAlgorithm());
136        } catch (NoSuchAlgorithmException e) {
137            throw (IOException) new IOException().initCause(e);
138        }
139
140        int size = in.available();
141        if (size == 0) {
142            size = MAX_BUF_SIZE;
143        } else if (size < MIN_BUF_SIZE) {
144            size = MIN_BUF_SIZE;
145        } else if (size > MAX_BUF_SIZE) {
146            size = MAX_BUF_SIZE;
147        }
148        byte[] buf = new byte[size];
149
150        /*
151         * Copy and digest.
152         */
153        int n;
154        while ((n = in.read(buf)) != -1) {
155            digest.update(buf, 0, n);
156            out.write(buf, 0, n);
157        }
158        out.flush();
159
160        return toHexString(digest.digest());
161    }
162
163    private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
164
165    public static String toHexString(byte[] data) {
166        StringBuilder buf = new StringBuilder(2 * data.length);
167        for (byte b : data) {
168            buf.append(HEX_DIGITS[(0xF0 & b) >> 4]);
169            buf.append(HEX_DIGITS[0x0F & b]);
170        }
171        return buf.toString();
172    }
173
174    @Override
175    public BinaryGarbageCollector getGarbageCollector() {
176        return garbageCollector;
177    }
178
179    @Override
180    public String getDigestAlgorithm() {
181        return descriptor.digest;
182    }
183
184    /**
185     * Gets the default message digest to use to hash binaries.
186     *
187     * @since 6.0
188     */
189    protected String getDefaultDigestAlgorithm() {
190        return DEFAULT_DIGEST;
191    }
192
193}