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