private List<Record> read(List<RecordId> recordIds, List<FieldType> fields, FieldTypes fieldTypes)
      throws RepositoryException, InterruptedException {
    long before = System.currentTimeMillis();
    try {
      ArgumentValidator.notNull(recordIds, "recordIds");
      List<Record> records = new ArrayList<Record>();
      if (recordIds.isEmpty()) return records;

      Map<RecordId, Result> results = getRows(recordIds, fields);

      for (Entry<RecordId, Result> entry : results.entrySet()) {
        Long version = recdec.getLatestVersion(entry.getValue());
        records.add(
            recdec.decodeRecord(entry.getKey(), version, null, entry.getValue(), fieldTypes));
      }
      return records;
    } finally {
      if (metrics != null) metrics.report(Action.READ, System.currentTimeMillis() - before);
    }
  }
  protected Record read(
      RecordId recordId, Long requestedVersion, List<FieldType> fields, FieldTypes fieldTypes)
      throws RepositoryException, InterruptedException {
    long before = System.currentTimeMillis();
    try {
      ArgumentValidator.notNull(recordId, "recordId");

      Result result = getRow(recordId, requestedVersion, 1, fields);

      Long latestVersion = recdec.getLatestVersion(result);
      if (requestedVersion == null) {
        // Latest version can still be null if there are only non-versioned fields in the record
        requestedVersion = latestVersion;
      } else {
        if (latestVersion == null || latestVersion < requestedVersion) {
          // The requested version is higher than the highest existing version
          throw new VersionNotFoundException(recordId, requestedVersion);
        }
      }
      return recdec.decodeRecord(recordId, requestedVersion, null, result, fieldTypes);
    } finally {
      if (metrics != null) metrics.report(Action.READ, System.currentTimeMillis() - before);
    }
  }