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