@Override
  public void mutateRowsTs(ByteBuffer tableName, List<BatchMutation> rowBatches, long timestamp,
    Map<ByteBuffer, ByteBuffer> attributes) throws IOError, IllegalArgument, TException {
    List<Put> puts = new ArrayList<>();
    List<Delete> deletes = new ArrayList<>();
    CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
    for (BatchMutation batch : rowBatches) {
      byte[] row = getBytes(batch.row);
      List<Mutation> mutations = batch.mutations;
      Delete delete = new Delete(row);
      addAttributes(delete, attributes);
      Put put = new Put(row, timestamp);
      addAttributes(put, attributes);
      for (Mutation m : mutations) {
        byte[][] famAndQf = CellUtil.parseColumn(getBytes(m.column));
        if (m.isDelete) {
          // no qualifier, family only.
          if (famAndQf.length == 1) {
            delete.addFamily(famAndQf[0], timestamp);
          } else {
            delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
          }
          delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
        } else {
          if (famAndQf.length == 1) {
            LOG.warn("No column qualifier specified. Delete is the only mutation supported "
              + "over the whole column family.");
          }
          if (famAndQf.length == 2) {
            try {
              put.add(builder.clear().setRow(put.getRow()).setFamily(famAndQf[0])
                .setQualifier(famAndQf[1]).setTimestamp(put.getTimestamp()).setType(Cell.Type.Put)
                .setValue(m.value != null ? getBytes(m.value) : HConstants.EMPTY_BYTE_ARRAY)
                .build());
            } catch (IOException e) {
              throw new IllegalArgumentException(e);
            }
          } else {
            throw new IllegalArgumentException("Invalid famAndQf provided.");
          }
          put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
        }
      }
      if (!delete.isEmpty()) {
        deletes.add(delete);
      }
      if (!put.isEmpty()) {
        puts.add(put);
      }
    }

    Table table = null;
    try {
      table = getTable(tableName);
      if (!puts.isEmpty()) {
        table.put(puts);
      }
      if (!deletes.isEmpty()) {
        table.delete(deletes);
      }
    } catch (IOException e) {
      LOG.warn(e.getMessage(), e);
      throw getIOError(e);
    } catch (IllegalArgumentException e) {
      LOG.warn(e.getMessage(), e);
      throw new IllegalArgument(Throwables.getStackTraceAsString(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