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.Collections; 031import java.util.HashMap; 032import java.util.Map; 033import java.util.regex.Pattern; 034 035import org.apache.commons.codec.binary.Hex; 036import org.apache.commons.codec.digest.DigestUtils; 037import org.nuxeo.common.xmap.XMap; 038import org.nuxeo.ecm.core.api.Blob; 039 040/** 041 * Abstract BinaryManager implementation that provides a few utilities 042 * 043 * @author Florent Guillaume 044 */ 045public abstract class AbstractBinaryManager implements BinaryManager { 046 047 public static final String MD5_DIGEST = "MD5"; 048 049 /** @deprecated since 11.1, unused */ 050 @Deprecated 051 public static final String SHA1_DIGEST = "SHA-1"; 052 053 /** @deprecated since 11.1, unused */ 054 @Deprecated 055 public static final String SHA256_DIGEST = "SHA-256"; 056 057 /** @deprecated since 11.1, unused */ 058 @Deprecated 059 public static final int MD5_DIGEST_LENGTH = 32; 060 061 /** @deprecated since 11.1, unused */ 062 @Deprecated 063 public static final int SHA1_DIGEST_LENGTH = 40; 064 065 /** @deprecated since 11.1, unused */ 066 @Deprecated 067 public static final int SHA256_DIGEST_LENGTH = 64; 068 069 /** 070 * @since 7.4 071 */ 072 public static final Map<Integer, String> DIGESTS_BY_LENGTH; 073 074 static { 075 Map<Integer, String> map = new HashMap<>(); 076 map.put(32, "MD5"); 077 map.put(40, "SHA-1"); 078 map.put(56, "SHA-224"); 079 map.put(64, "SHA-256"); 080 map.put(96, "SHA-384"); 081 map.put(128, "SHA-512"); 082 // TODO new algorithms like SHA3-256 will collide, so this mechanism is brittle 083 DIGESTS_BY_LENGTH = Collections.unmodifiableMap(map); 084 } 085 086 public static final String DEFAULT_DIGEST = MD5_DIGEST; // SHA256_DIGEST 087 088 public static final int DEFAULT_DEPTH = 2; 089 090 public String blobProviderId; 091 092 protected Map<String, String> properties; 093 094 protected BinaryManagerRootDescriptor descriptor; 095 096 protected BinaryGarbageCollector garbageCollector; 097 098 protected Pattern digestPattern; 099 100 @Override 101 public void initialize(String blobProviderId, Map<String, String> properties) throws IOException { 102 this.blobProviderId = blobProviderId; 103 this.properties = properties; 104 } 105 106 protected void setDescriptor(BinaryManagerRootDescriptor descriptor) { 107 this.descriptor = descriptor; 108 computeDigestPattern(); 109 } 110 111 /** 112 * Creates a binary value from the given input stream. 113 */ 114 // not in the public API of BinaryManager anymore 115 abstract protected Binary getBinary(InputStream in) throws IOException; 116 117 /* 118 * This abstract implementation just opens the stream. 119 */ 120 @Override 121 public Binary getBinary(Blob blob) throws IOException { 122 if (blob instanceof BinaryBlob) { 123 Binary binary = ((BinaryBlob) blob).getBinary(); 124 if (binary.getBlobProviderId().equals(blobProviderId)) { 125 return binary; 126 } 127 // don't reuse the binary if it comes from another blob provider 128 } 129 try (InputStream stream = blob.getStream()) { 130 return getBinary(stream); 131 } 132 } 133 134 @Override 135 abstract public Binary getBinary(String digest); 136 137 @Override 138 public void removeBinaries(Collection<String> digests) { 139 throw new UnsupportedOperationException(); 140 } 141 142 /** 143 * Gets existing descriptor or creates a default one. 144 */ 145 protected BinaryManagerRootDescriptor getDescriptor(File configFile) throws IOException { 146 BinaryManagerRootDescriptor desc; 147 if (configFile.exists()) { 148 XMap xmap = new XMap(); 149 xmap.register(BinaryManagerRootDescriptor.class); 150 try (InputStream in = new FileInputStream(configFile)) { 151 desc = (BinaryManagerRootDescriptor) xmap.load(in); 152 } 153 } else { 154 desc = new BinaryManagerRootDescriptor(); 155 // TODO fetch from repo descriptor 156 desc.digest = getDefaultDigestAlgorithm(); 157 desc.depth = DEFAULT_DEPTH; 158 desc.write(configFile); // may throw IOException 159 } 160 return desc; 161 } 162 163 public static final int MIN_BUF_SIZE = 8 * 1024; // 8 kB 164 165 public static final int MAX_BUF_SIZE = 64 * 1024; // 64 kB 166 167 protected String storeAndDigest(InputStream in, OutputStream out) throws IOException { 168 MessageDigest digest; 169 try { 170 digest = MessageDigest.getInstance(getDigestAlgorithm()); 171 } catch (NoSuchAlgorithmException e) { 172 throw (IOException) new IOException().initCause(e); 173 } 174 175 int size = in.available(); 176 if (size == 0) { 177 size = MAX_BUF_SIZE; 178 } else if (size < MIN_BUF_SIZE) { 179 size = MIN_BUF_SIZE; 180 } else if (size > MAX_BUF_SIZE) { 181 size = MAX_BUF_SIZE; 182 } 183 byte[] buf = new byte[size]; 184 185 /* 186 * Copy and digest. 187 */ 188 int n; 189 while ((n = in.read(buf)) != -1) { 190 digest.update(buf, 0, n); 191 out.write(buf, 0, n); 192 } 193 out.flush(); 194 195 return Hex.encodeHexString(digest.digest()); 196 } 197 198 /** @deprecated since 11.1, use {@link Hex#encodeHexString} directly */ 199 @Deprecated 200 public static String toHexString(byte[] data) { 201 return Hex.encodeHexString(data); 202 } 203 204 protected void computeDigestPattern() { 205 // compute a dummy digest (from 0-length input) to know its length and derive a regexp 206 int len = new DigestUtils(getDigestAlgorithm()).digestAsHex(new byte[0]).length(); 207 digestPattern = Pattern.compile("[0-9a-f]{" + len + "}"); 208 } 209 210 public boolean isValidDigest(String digest) { 211 return digestPattern.matcher(digest).matches(); 212 } 213 214 @Override 215 public BinaryGarbageCollector getGarbageCollector() { 216 return garbageCollector; 217 } 218 219 @Override 220 public String getDigestAlgorithm() { 221 return descriptor.digest; 222 } 223 224 /** 225 * Gets the default message digest to use to hash binaries. 226 * 227 * @since 6.0 228 */ 229 protected String getDefaultDigestAlgorithm() { 230 return DEFAULT_DIGEST; 231 } 232 233}