/*
* (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume, jcarsique
*/
package org.nuxeo.ecm.core.blob.binary;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.nuxeo.common.xmap.XMap;
import org.nuxeo.ecm.core.api.Blob;
/**
* Abstract BinaryManager implementation that provides a few utilities
*
* @author Florent Guillaume
*/
public abstract class AbstractBinaryManager implements BinaryManager {
public static final String MD5_DIGEST = "MD5";
public static final String SHA1_DIGEST = "SHA-1";
public static final String SHA256_DIGEST = "SHA-256";
public static final int MD5_DIGEST_LENGTH = 32;
public static final int SHA1_DIGEST_LENGTH = 40;
public static final int SHA256_DIGEST_LENGTH = 64;
/**
* @since 7.4
*/
public static final HashMap<Integer, String> DIGESTS_BY_LENGTH = new HashMap<>();
public static final String DEFAULT_DIGEST = MD5_DIGEST; // SHA256_DIGEST
public static final int DEFAULT_DEPTH = 2;
protected String blobProviderId;
protected BinaryManagerRootDescriptor descriptor;
protected BinaryGarbageCollector garbageCollector;
@Override
public void initialize(String blobProviderId, Map<String, String> properties) throws IOException {
this.blobProviderId = blobProviderId;
DIGESTS_BY_LENGTH.put(MD5_DIGEST_LENGTH, MD5_DIGEST);
DIGESTS_BY_LENGTH.put(SHA1_DIGEST_LENGTH, SHA1_DIGEST);
DIGESTS_BY_LENGTH.put(SHA256_DIGEST_LENGTH, SHA256_DIGEST);
}
/**
* Creates a binary value from the given input stream.
*/
// not in the public API of BinaryManager anymore
abstract protected Binary getBinary(InputStream in) throws IOException;
/*
* This abstract implementation just opens the stream.
*/
@Override
public Binary getBinary(Blob blob) throws IOException {
if (blob instanceof BinaryBlob) {
Binary binary = ((BinaryBlob) blob).getBinary();
if (binary.getBlobProviderId().equals(blobProviderId)) {
return binary;
}
// don't reuse the binary if it comes from another blob provider
}
try (InputStream stream = blob.getStream()) {
return getBinary(stream);
}
}
@Override
abstract public Binary getBinary(String digest);
@Override
public void removeBinaries(Collection<String> digests) {
throw new UnsupportedOperationException();
}
/**
* Gets existing descriptor or creates a default one.
*/
protected BinaryManagerRootDescriptor getDescriptor(File configFile) throws IOException {
BinaryManagerRootDescriptor desc;
if (configFile.exists()) {
XMap xmap = new XMap();
xmap.register(BinaryManagerRootDescriptor.class);
desc = (BinaryManagerRootDescriptor) xmap.load(new FileInputStream(configFile));
} else {
desc = new BinaryManagerRootDescriptor();
// TODO fetch from repo descriptor
desc.digest = getDefaultDigestAlgorithm();
desc.depth = DEFAULT_DEPTH;
desc.write(configFile); // may throw IOException
}
return desc;
}
public static final int MIN_BUF_SIZE = 8 * 1024; // 8 kB
public static final int MAX_BUF_SIZE = 64 * 1024; // 64 kB
protected String storeAndDigest(InputStream in, OutputStream out) throws IOException {
MessageDigest digest;
try {
digest = MessageDigest.getInstance(getDigestAlgorithm());
} catch (NoSuchAlgorithmException e) {
throw (IOException) new IOException().initCause(e);
}
int size = in.available();
if (size == 0) {
size = MAX_BUF_SIZE;
} else if (size < MIN_BUF_SIZE) {
size = MIN_BUF_SIZE;
} else if (size > MAX_BUF_SIZE) {
size = MAX_BUF_SIZE;
}
byte[] buf = new byte[size];
/*
* Copy and digest.
*/
int n;
while ((n = in.read(buf)) != -1) {
digest.update(buf, 0, n);
out.write(buf, 0, n);
}
out.flush();
return toHexString(digest.digest());
}
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
public static String toHexString(byte[] data) {
StringBuilder buf = new StringBuilder(2 * data.length);
for (byte b : data) {
buf.append(HEX_DIGITS[(0xF0 & b) >> 4]);
buf.append(HEX_DIGITS[0x0F & b]);
}
return buf.toString();
}
@Override
public BinaryGarbageCollector getGarbageCollector() {
return garbageCollector;
}
@Override
public String getDigestAlgorithm() {
return descriptor.digest;
}
/**
* Gets the default message digest to use to hash binaries.
*
* @since 6.0
*/
protected String getDefaultDigestAlgorithm() {
return DEFAULT_DIGEST;
}
}