private void internalReadFromLedger(LedgerHandle ledger, OpReadEntry opReadEntry) {
    // Perform the read
    long firstEntry = opReadEntry.readPosition.getEntryId();

    if (firstEntry > ledger.getLastAddConfirmed()) {
      log.debug(
          "[{}] No more messages to read from ledger={} lastEntry={} readEntry={}",
          va(name, ledger.getId(), ledger.getLastAddConfirmed(), firstEntry));

      if (ledger.getId() != currentLedger.getId()) {
        // Cursor was placed past the end of one ledger, move it to the
        // beginning of the next ledger
        Long nextLedgerId = ledgers.ceilingKey(ledger.getId() + 1);
        opReadEntry.nextReadPosition = new Position(nextLedgerId, 0);
      }

      opReadEntry.emptyResponse();
      return;
    }

    long lastEntry = min(firstEntry + opReadEntry.count - 1, ledger.getLastAddConfirmed());

    long expectedEntries = lastEntry - firstEntry + 1;
    opReadEntry.entries = Lists.newArrayListWithExpectedSize((int) expectedEntries);

    log.debug(
        "[{}] Reading entries from ledger {} - first={} last={}",
        va(name, ledger.getId(), firstEntry, lastEntry));
    ledger.asyncReadEntries(firstEntry, lastEntry, this, opReadEntry);
  }
  /*
   * (non-Javadoc)
   *
   * @see
   * org.apache.bookkeeper.client.AsyncCallback.OpenCallback#openComplete(int,
   * org.apache.bookkeeper.client.LedgerHandle, java.lang.Object)
   */
  @Override
  public void openComplete(int rc, LedgerHandle ledger, Object ctx) {
    OpReadEntry opReadEntry = (OpReadEntry) ctx;

    if (rc != BKException.Code.OK) {
      opReadEntry.failed(new ManagedLedgerException(BKException.create(rc)));
      return;
    }

    log.debug("[{}] Successfully opened ledger {} for reading", name, ledger.getId());
    internalReadFromLedger(ledger, opReadEntry);
  }
  protected synchronized void asyncReadEntries(OpReadEntry opReadEntry) {
    if (state == State.Fenced) {
      opReadEntry.failed(new ManagedLedgerFencedException());
      return;
    }

    LedgerHandle ledger = null;

    if (opReadEntry.readPosition.getLedgerId() == -1) {
      if (ledgers.isEmpty()) {
        // The ManagedLedger is completely empty
        opReadEntry.emptyResponse();
        return;
      }

      // Initialize the position on the first entry for the first ledger
      // in the set
      opReadEntry.readPosition = new Position(ledgers.firstKey(), 0);
    }

    long id = opReadEntry.readPosition.getLedgerId();

    if (id == currentLedger.getId()) {
      // Current writing ledger is not in the cache (since we don't want
      // it to be automatically evicted), and we cannot use 2 different
      // ledger handles (read & write)for the same ledger.
      ledger = currentLedger;
    } else {
      ledger = ledgerCache.getIfPresent(id);
      if (ledger == null) {
        // Open the ledger and cache the handle
        log.debug("[{}] Asynchronously opening ledger {} for read", name, id);
        bookKeeper.asyncOpenLedger(
            id, config.getDigestType(), config.getPassword(), this, opReadEntry);
        return;
      }
    }

    internalReadFromLedger(ledger, opReadEntry);
  }
  /*
   * (non-Javadoc)
   *
   * @see
   * org.apache.bookkeeper.client.AsyncCallback.ReadCallback#readComplete(int,
   * org.apache.bookkeeper.client.LedgerHandle, java.util.Enumeration,
   * java.lang.Object)
   */
  @Override
  public void readComplete(
      int rc, LedgerHandle lh, Enumeration<LedgerEntry> entriesEnum, Object ctx) {
    OpReadEntry opReadEntry = (OpReadEntry) ctx;

    if (rc != BKException.Code.OK) {
      log.warn(
          "[{}] read failed from ledger {} at position:{}",
          va(name, lh.getId(), opReadEntry.readPosition));
      opReadEntry.failed(new ManagedLedgerException(BKException.create(rc)));
      return;
    }

    List<Entry> entries = opReadEntry.entries;
    while (entriesEnum.hasMoreElements()) entries.add(new EntryImpl(entriesEnum.nextElement()));

    long lastEntry = entries.get(entries.size() - 1).getPosition().getEntryId();

    // Get the "next read position", we need to advance the position taking
    // care of ledgers boundaries
    Position nextReadPosition;
    if (lastEntry < lh.getLastAddConfirmed()) {
      nextReadPosition = new Position(lh.getId(), lastEntry + 1);
    } else {
      // Move to next ledger
      Long nextLedgerId = ledgers.ceilingKey(lh.getId() + 1);
      if (nextLedgerId == null) {
        // We are already in the last ledger
        nextReadPosition = new Position(lh.getId(), lastEntry + 1);
      } else {
        nextReadPosition = new Position(nextLedgerId, 0);
      }
    }

    opReadEntry.nextReadPosition = nextReadPosition;
    opReadEntry.succeeded();
  }