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}