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}