HashFileProductStorage.java
package gov.usgs.earthquake.distribution;
import gov.usgs.earthquake.product.ProductId;
import java.io.File;
import java.security.MessageDigest;
import java.util.logging.Logger;
/**
* A FileProductStorage that builds directory paths based on a SHA-1 hash of the
* product id.
*
* This helps overcome a limitation of the ext3 filesystem which limits the
* number of subdirectories any one directory may contain to 32000. This
* implementation should generate no more than 4096 (16 ^ 3) subdirectories of
* any one subdirectory.
*
* Note: no collision handling has been implemented, although hash collisions
* are not expected.
*
* Examples: <br>
* Product ID: urn:usgs-product:us:shakemap:abcd1234:1304116272636 <br>
* SHA-1 hash: dde7b3986ee2fda8a793b599b6ae725ab35df58b <br>
* Directory: shakemap/dde/7b3/986/ee2/fda/8a7/93b/599/b6a/e72/5ab/35d/f58/b <br>
* <br>
* Product ID: urn:usgs-product:us:shakemap2:efgh5678:1304116272711 <br>
* SHA-1 hash: 8174d0f8d961d48c8a94a6bd0ab2a882e01173c6 <br>
* Directory: shakemap2/817/4d0/f8d/961/d48/c8a/94a/6bd/0ab/2a8/82e/011/73c/6
*
* @deprecated
* @see FileProductStorage
*/
public class HashFileProductStorage extends FileProductStorage {
private static Logger LOGGER = Logger
.getLogger(HashFileProductStorage.class.getName());
// create this digest once, and clone it later
private static final MessageDigest SHA_DIGEST;
static {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA");
} catch (Exception e) {
LOGGER.warning("Unable to create SHA Digest for HashFileProductStorage");
digest = null;
}
SHA_DIGEST = digest;
}
/**
* This is chosen because 16^3 = 4096 < 32000, which is the ext3
* subdirectory limit.
*/
public static final int DIRECTORY_NAME_LENGTH = 3;
/**
* Basic Constructor
* Sets baseDirectory to FileProductsStorage' DEFAULT_DIRECTORY of 'Storage'
*/
public HashFileProductStorage() {
super();
}
/**
* Constructor taking in specific File directory
* @param directory base directory for storage path
*/
public HashFileProductStorage(final File directory) {
super(directory);
}
/**
* A method for subclasses to override the storage path.
*
* The returned path is appended to the base directory when storing and
* retrieving products.
*
* @param id
* the product id to convert.
* @return the directory used to store id.
*/
@Override
public String getProductPath(final ProductId id) {
try {
MessageDigest digest;
synchronized (SHA_DIGEST) {
digest = ((MessageDigest) SHA_DIGEST.clone());
}
String hexDigest = toHexString(digest.digest(id.toString()
.getBytes()));
StringBuffer buf = new StringBuffer();
// start with product type, to give idea of available products and
// disk usage when looking at filesystem
buf.append(id.getType());
// sub directories based on hash
int length = hexDigest.length();
for (int i = 0; i < length; i += DIRECTORY_NAME_LENGTH) {
String part;
if (i + DIRECTORY_NAME_LENGTH < length) {
part = hexDigest.substring(i, i + DIRECTORY_NAME_LENGTH);
} else {
part = hexDigest.substring(i);
}
buf.append(File.separator);
buf.append(part);
}
return buf.toString();
} catch (CloneNotSupportedException e) {
// fall back to parent class
return super.getProductPath(id);
}
}
/**
* Convert an array of bytes into a hex string. The string will always be
* twice as long as the input byte array, because bytes < 0x10 are zero
* padded.
*
* @param bytes
* byte array to convert to hex.
* @return hex string equivalent of input byte array.
*/
private String toHexString(final byte[] bytes) {
StringBuffer buf = new StringBuffer();
int length = bytes.length;
for (int i = 0; i < length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
buf.append('0');
}
buf.append(hex);
}
return buf.toString();
}
}