HashFileProductStorage.java

  1. package gov.usgs.earthquake.distribution;

  2. import gov.usgs.earthquake.product.ProductId;

  3. import java.io.File;
  4. import java.security.MessageDigest;
  5. import java.util.logging.Logger;

  6. /**
  7.  * A FileProductStorage that builds directory paths based on a SHA-1 hash of the
  8.  * product id.
  9.  *
  10.  * This helps overcome a limitation of the ext3 filesystem which limits the
  11.  * number of subdirectories any one directory may contain to 32000. This
  12.  * implementation should generate no more than 4096 (16 ^ 3) subdirectories of
  13.  * any one subdirectory.
  14.  *
  15.  * Note: no collision handling has been implemented, although hash collisions
  16.  * are not expected.
  17.  *
  18.  * Examples: <br>
  19.  * Product ID: urn:usgs-product:us:shakemap:abcd1234:1304116272636 <br>
  20.  * SHA-1 hash: dde7b3986ee2fda8a793b599b6ae725ab35df58b <br>
  21.  * Directory: shakemap/dde/7b3/986/ee2/fda/8a7/93b/599/b6a/e72/5ab/35d/f58/b <br>
  22.  * <br>
  23.  * Product ID: urn:usgs-product:us:shakemap2:efgh5678:1304116272711 <br>
  24.  * SHA-1 hash: 8174d0f8d961d48c8a94a6bd0ab2a882e01173c6 <br>
  25.  * Directory: shakemap2/817/4d0/f8d/961/d48/c8a/94a/6bd/0ab/2a8/82e/011/73c/6
  26.  *
  27.  * @deprecated
  28.  * @see FileProductStorage
  29.  */
  30. public class HashFileProductStorage extends FileProductStorage {

  31.     private static Logger LOGGER = Logger
  32.             .getLogger(HashFileProductStorage.class.getName());

  33.     // create this digest once, and clone it later
  34.     private static final MessageDigest SHA_DIGEST;
  35.     static {
  36.         MessageDigest digest = null;
  37.         try {
  38.             digest = MessageDigest.getInstance("SHA");
  39.         } catch (Exception e) {
  40.             LOGGER.warning("Unable to create SHA Digest for HashFileProductStorage");
  41.             digest = null;
  42.         }
  43.         SHA_DIGEST = digest;
  44.     }

  45.     /**
  46.      * This is chosen because 16^3 = 4096 &lt; 32000, which is the ext3
  47.      * subdirectory limit.
  48.      */
  49.     public static final int DIRECTORY_NAME_LENGTH = 3;

  50.     /**
  51.      * Basic Constructor
  52.      * Sets baseDirectory to FileProductsStorage' DEFAULT_DIRECTORY of 'Storage'
  53.      */
  54.     public HashFileProductStorage() {
  55.         super();
  56.     }

  57.     /**
  58.      * Constructor taking in specific File directory
  59.      * @param directory base directory for storage path
  60.      */
  61.     public HashFileProductStorage(final File directory) {
  62.         super(directory);
  63.     }

  64.     /**
  65.      * A method for subclasses to override the storage path.
  66.      *
  67.      * The returned path is appended to the base directory when storing and
  68.      * retrieving products.
  69.      *
  70.      * @param id
  71.      *            the product id to convert.
  72.      * @return the directory used to store id.
  73.      */
  74.     @Override
  75.     public String getProductPath(final ProductId id) {
  76.         try {
  77.             MessageDigest digest;
  78.             synchronized (SHA_DIGEST) {
  79.                 digest = ((MessageDigest) SHA_DIGEST.clone());
  80.             }

  81.             String hexDigest = toHexString(digest.digest(id.toString()
  82.                     .getBytes()));

  83.             StringBuffer buf = new StringBuffer();
  84.             // start with product type, to give idea of available products and
  85.             // disk usage when looking at filesystem
  86.             buf.append(id.getType());

  87.             // sub directories based on hash
  88.             int length = hexDigest.length();
  89.             for (int i = 0; i < length; i += DIRECTORY_NAME_LENGTH) {
  90.                 String part;
  91.                 if (i + DIRECTORY_NAME_LENGTH < length) {
  92.                     part = hexDigest.substring(i, i + DIRECTORY_NAME_LENGTH);
  93.                 } else {
  94.                     part = hexDigest.substring(i);
  95.                 }
  96.                 buf.append(File.separator);
  97.                 buf.append(part);
  98.             }

  99.             return buf.toString();
  100.         } catch (CloneNotSupportedException e) {
  101.             // fall back to parent class
  102.             return super.getProductPath(id);
  103.         }
  104.     }

  105.     /**
  106.      * Convert an array of bytes into a hex string. The string will always be
  107.      * twice as long as the input byte array, because bytes < 0x10 are zero
  108.      * padded.
  109.      *
  110.      * @param bytes
  111.      *            byte array to convert to hex.
  112.      * @return hex string equivalent of input byte array.
  113.      */
  114.     private String toHexString(final byte[] bytes) {
  115.         StringBuffer buf = new StringBuffer();
  116.         int length = bytes.length;
  117.         for (int i = 0; i < length; i++) {
  118.             String hex = Integer.toHexString(0xFF & bytes[i]);
  119.             if (hex.length() == 1) {
  120.                 buf.append('0');
  121.             }
  122.             buf.append(hex);
  123.         }
  124.         return buf.toString();
  125.     }

  126. }