@Override
  public List<TRowResult> getRowsWithColumnsTs(ByteBuffer tableName, List<ByteBuffer> rows,
    List<ByteBuffer> columns, long timestamp, Map<ByteBuffer, ByteBuffer> attributes)
    throws IOError {

    Table table = null;
    try {
      List<Get> gets = new ArrayList<>(rows.size());
      table = getTable(tableName);
      if (metrics != null) {
        metrics.incNumRowKeysInBatchGet(rows.size());
      }
      for (ByteBuffer row : rows) {
        Get get = new Get(getBytes(row));
        addAttributes(get, attributes);
        if (columns != null) {

          for (ByteBuffer column : columns) {
            byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
            if (famAndQf.length == 1) {
              get.addFamily(famAndQf[0]);
            } else {
              get.addColumn(famAndQf[0], famAndQf[1]);
            }
          }
        }
        get.setTimeRange(0, timestamp);
        gets.add(get);
      }
      Result[] result = table.get(gets);
      return ThriftUtilities.rowResultFromHBase(result);
    } catch (IOException e) {
      LOG.warn(e.getMessage(), e);
      throw getIOError(e);
    } finally {
      closeTable(table);
    }
  }
Note: this internal interface is slightly different from public APIs in regard to handling of
the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather, we
respect qual == null as a request for the entire column family. The caller (
{@link #getVerTs(ByteBuffer, ByteBuffer, ByteBuffer, long, int, Map)}) interface IS consistent
in that the column is parse like normal.
hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift/ThriftHBaseServiceHandler.java