@Override
  public String getMimeType(BinaryValue binary, String name)
      throws IOException, RepositoryException {
    if (detector == null) {
      return null;
    }

    String detectedMimeType = detector.mimeTypeOf(name, binary);
    if (binary instanceof InMemoryBinaryValue) {
      return detectedMimeType;
    }

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      Map.Entry<String, BinaryStore> entry = it.next();

      final String binaryStoreKey = entry.getKey();
      BinaryStore bs = entry.getValue();

      try {
        if (bs.hasBinary(binary.getKey())) {
          return bs.getMimeType(binary, name);
        }
      } catch (BinaryStoreException e) {
        logger.debug(e, "The named store " + binaryStoreKey + " raised exception");
        if (!it.hasNext()) {
          throw e;
        }
      }
    }

    throw new BinaryStoreException(JcrI18n.unableToFindBinaryValue.text(binary.getKey(), this));
  }
  /**
   * Move a value from one named store to another store
   *
   * @param key Binary key to transfer from the source store to the destination store
   * @param source a hint for discovering the source repository; may be null
   * @param destination a hint for discovering the destination repository
   * @return the {@link BinaryKey} value of the moved binary, never {@code null}
   * @throws BinaryStoreException if a source store cannot be found or the source store does not
   *     contain the binary key
   */
  public BinaryKey moveValue(BinaryKey key, String source, String destination)
      throws BinaryStoreException {
    final BinaryStore sourceStore;

    if (source == null) {
      sourceStore = findBinaryStoreContainingKey(key);
    } else {
      sourceStore = selectBinaryStore(source);
    }

    // could not find source store, or
    if (sourceStore == null || !sourceStore.hasBinary(key)) {
      throw new BinaryStoreException(JcrI18n.unableToFindBinaryValue.text(key, sourceStore));
    }

    BinaryStore destinationStore = selectBinaryStore(destination);

    // key is already in the destination store
    if (sourceStore.equals(destinationStore)) {
      return key;
    }

    final BinaryValue binaryValue = storeValue(sourceStore.getInputStream(key), destination);
    sourceStore.markAsUnused(java.util.Collections.singleton(key));

    return binaryValue.getKey();
  }
  @Override
  public String getText(BinaryValue binary) throws BinaryStoreException {

    if (binary instanceof InMemoryBinaryValue) {
      if (extractors == null || !extractors.extractionEnabled()) {
        return null;
      }

      // The extracted text will never be stored, so try directly using the text extractors ...
      return extractors.extract((InMemoryBinaryValue) binary, new TextExtractorContext(detector));
    }

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      Map.Entry<String, BinaryStore> entry = it.next();

      final String binaryStoreKey = entry.getKey();
      BinaryStore bs = entry.getValue();
      try {
        if (bs.hasBinary(binary.getKey())) {
          return bs.getText(binary);
        }
      } catch (BinaryStoreException e) {
        logger.debug(e, "The named store " + binaryStoreKey + " raised exception");
        if (!it.hasNext()) {
          throw e;
        }
      }
    }

    throw new BinaryStoreException(JcrI18n.unableToFindBinaryValue.text(binary.getKey(), this));
  }
 @Override
 public BinaryValue storeValue(InputStream stream, String hint) throws BinaryStoreException {
   BinaryStore binaryStore = selectBinaryStore(hint);
   BinaryValue bv = binaryStore.storeValue(stream);
   logger.debug("Stored binary " + bv.getKey() + " into binary store " + binaryStore);
   return bv;
 }
  /** Shut down all the named stores */
  @Override
  public void shutdown() {
    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      bs.shutdown();
    }
  }
  @Override
  public void setMinimumBinarySizeInBytes(long minSizeInBytes) {

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      bs.setMinimumBinarySizeInBytes(minSizeInBytes);
    }
  }
  @Override
  public void setMimeTypeDetector(MimeTypeDetector mimeTypeDetector) {
    this.detector = mimeTypeDetector != null ? mimeTypeDetector : NullMimeTypeDetector.INSTANCE;

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      bs.setMimeTypeDetector(mimeTypeDetector);
    }
  }
  @Override
  public void setTextExtractors(TextExtractors textExtractors) {
    CheckArg.isNotNull(textExtractors, "textExtractors");
    this.extractors = textExtractors;

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();
    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      bs.setTextExtractors(textExtractors);
    }
  }
  /**
   * Get the named binary store that contains the key
   *
   * @param key the key to the binary content; never null
   * @return the BinaryStore that contains the given key
   */
  public BinaryStore findBinaryStoreContainingKey(BinaryKey key) {
    Iterator<Map.Entry<String, BinaryStore>> binaryStoreIterator = getNamedStoreIterator();

    while (binaryStoreIterator.hasNext()) {
      BinaryStore bs = binaryStoreIterator.next().getValue();
      if (bs.hasBinary(key)) {
        return bs;
      }
    }

    return null;
  }
  @Override
  public boolean hasBinary(BinaryKey key) {
    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      if (bs.hasBinary(key)) {
        return true;
      }
    }

    return false;
  }
  @Override
  public Iterable<BinaryKey> getAllBinaryKeys() throws BinaryStoreException {

    Iterable<BinaryKey> generatedIterable = new HashSet<BinaryKey>();
    Iterator<Map.Entry<String, BinaryStore>> binaryStoreIterator = getNamedStoreIterator();

    while (binaryStoreIterator.hasNext()) {
      BinaryStore bs = binaryStoreIterator.next().getValue();

      generatedIterable = Collections.concat(generatedIterable, bs.getAllBinaryKeys());
    }

    return generatedIterable;
  }
  @Override
  public long getMinimumBinarySizeInBytes() {
    long minimumBinarySize = Long.MAX_VALUE;

    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      BinaryStore bs = it.next().getValue();
      if (minimumBinarySize > bs.getMinimumBinarySizeInBytes()) {
        minimumBinarySize = bs.getMinimumBinarySizeInBytes();
      }
    }

    return minimumBinarySize;
  }
  /**
   * Select a named binary store for the given hint
   *
   * @param hint a hint to a binary store; possibly null
   * @return a named BinaryStore from the hint, or the default store
   */
  private BinaryStore selectBinaryStore(String hint) {

    BinaryStore namedBinaryStore = null;

    if (hint != null) {
      logger.trace("Selecting named binary store for hint: " + hint);
      namedBinaryStore = namedStores.get(hint);
    }

    if (namedBinaryStore == null) {
      namedBinaryStore = getDefaultBinaryStore();
    }

    logger.trace("Selected binary store: " + namedBinaryStore.toString());

    return namedBinaryStore;
  }
  @SuppressWarnings("unused")
  @Override
  public void markAsUnused(Iterable<BinaryKey> keys) throws BinaryStoreException {
    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      Map.Entry<String, BinaryStore> entry = it.next();

      final String binaryStoreKey = entry.getKey();
      BinaryStore bs = entry.getValue();

      try {
        bs.markAsUnused(keys);
      } catch (BinaryStoreException e) {
        logger.debug(e, "The named store " + binaryStoreKey + " raised exception");
      }
    }
  }
  @SuppressWarnings("unused")
  @Override
  public void removeValuesUnusedLongerThan(long minimumAge, TimeUnit unit)
      throws BinaryStoreException {
    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      Map.Entry<String, BinaryStore> entry = it.next();

      final String binaryStoreKey = entry.getKey();
      BinaryStore bs = entry.getValue();

      try {
        bs.removeValuesUnusedLongerThan(minimumAge, unit);
      } catch (BinaryStoreException e) {
        logger.debug(e, "The named store " + binaryStoreKey + " raised exception");
      }
    }
  }
  @Override
  public InputStream getInputStream(BinaryKey key) throws BinaryStoreException {
    Iterator<Map.Entry<String, BinaryStore>> it = getNamedStoreIterator();

    while (it.hasNext()) {
      final Map.Entry<String, BinaryStore> entry = it.next();

      final String binaryStoreKey = entry.getKey();

      BinaryStore binaryStore = entry.getValue();
      logger.trace("Checking binary store " + binaryStoreKey + " for key " + key);
      try {
        return binaryStore.getInputStream(key);
      } catch (BinaryStoreException e) {
        // this exception is "normal", and is thrown
        logger.trace(e, "The named store " + binaryStoreKey + " raised exception");
      }
    }

    throw new BinaryStoreException(JcrI18n.unableToFindBinaryValue.text(key, this.toString()));
  }