@Override
  public int scannerOpenTs(ByteBuffer tableName, ByteBuffer startRow, List<ByteBuffer> columns,
    long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {

    Table table = null;
    try {
      table = getTable(tableName);
      Scan scan = new Scan().withStartRow(getBytes(startRow));
      addAttributes(scan, attributes);
      scan.setTimeRange(0, timestamp);
      if (columns != null && !columns.isEmpty()) {
        for (ByteBuffer column : columns) {
          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
          if (famQf.length == 1) {
            scan.addFamily(famQf[0]);
          } else {
            scan.addColumn(famQf[0], famQf[1]);
          }
        }
      }
      return addScanner(table.getScanner(scan), false);
    } 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