001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.thrift;
019
020import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD;
021import static org.apache.hadoop.hbase.HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD;
022import static org.apache.hadoop.hbase.thrift.Constants.COALESCE_INC_KEY;
023import static org.apache.hadoop.hbase.util.Bytes.getBytes;
024
025import java.io.IOException;
026import java.nio.ByteBuffer;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.TreeMap;
033import java.util.concurrent.TimeUnit;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.hbase.Cell;
036import org.apache.hadoop.hbase.CellBuilder;
037import org.apache.hadoop.hbase.CellBuilderFactory;
038import org.apache.hadoop.hbase.CellBuilderType;
039import org.apache.hadoop.hbase.CellUtil;
040import org.apache.hadoop.hbase.DoNotRetryIOException;
041import org.apache.hadoop.hbase.HColumnDescriptor;
042import org.apache.hadoop.hbase.HConstants;
043import org.apache.hadoop.hbase.HRegionLocation;
044import org.apache.hadoop.hbase.HTableDescriptor;
045import org.apache.hadoop.hbase.KeyValue;
046import org.apache.hadoop.hbase.MetaTableAccessor;
047import org.apache.hadoop.hbase.ServerName;
048import org.apache.hadoop.hbase.TableName;
049import org.apache.hadoop.hbase.TableNotFoundException;
050import org.apache.hadoop.hbase.client.Append;
051import org.apache.hadoop.hbase.client.Delete;
052import org.apache.hadoop.hbase.client.Durability;
053import org.apache.hadoop.hbase.client.Get;
054import org.apache.hadoop.hbase.client.Increment;
055import org.apache.hadoop.hbase.client.OperationWithAttributes;
056import org.apache.hadoop.hbase.client.Put;
057import org.apache.hadoop.hbase.client.RegionInfo;
058import org.apache.hadoop.hbase.client.RegionLocator;
059import org.apache.hadoop.hbase.client.Result;
060import org.apache.hadoop.hbase.client.ResultScanner;
061import org.apache.hadoop.hbase.client.Scan;
062import org.apache.hadoop.hbase.client.Table;
063import org.apache.hadoop.hbase.filter.Filter;
064import org.apache.hadoop.hbase.filter.ParseFilter;
065import org.apache.hadoop.hbase.filter.PrefixFilter;
066import org.apache.hadoop.hbase.filter.WhileMatchFilter;
067import org.apache.hadoop.hbase.security.UserProvider;
068import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
069import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
070import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
071import org.apache.hadoop.hbase.thrift.generated.Hbase;
072import org.apache.hadoop.hbase.thrift.generated.IOError;
073import org.apache.hadoop.hbase.thrift.generated.IllegalArgument;
074import org.apache.hadoop.hbase.thrift.generated.Mutation;
075import org.apache.hadoop.hbase.thrift.generated.TAppend;
076import org.apache.hadoop.hbase.thrift.generated.TCell;
077import org.apache.hadoop.hbase.thrift.generated.TIncrement;
078import org.apache.hadoop.hbase.thrift.generated.TRegionInfo;
079import org.apache.hadoop.hbase.thrift.generated.TRowResult;
080import org.apache.hadoop.hbase.thrift.generated.TScan;
081import org.apache.hadoop.hbase.thrift.generated.TThriftServerType;
082import org.apache.hadoop.hbase.util.Bytes;
083import org.apache.thrift.TException;
084import org.apache.yetus.audience.InterfaceAudience;
085import org.slf4j.Logger;
086import org.slf4j.LoggerFactory;
087
088import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
089import org.apache.hbase.thirdparty.com.google.common.cache.Cache;
090import org.apache.hbase.thirdparty.com.google.common.cache.CacheBuilder;
091
092/**
093 * The HBaseServiceHandler is a glue object that connects Thrift RPC calls to the HBase client API
094 * primarily defined in the Admin and Table objects.
095 */
096@InterfaceAudience.Private
097@SuppressWarnings("deprecation")
098public class ThriftHBaseServiceHandler extends HBaseServiceHandler implements Hbase.Iface {
099  private static final Logger LOG = LoggerFactory.getLogger(ThriftHBaseServiceHandler.class);
100
101  public static final int HREGION_VERSION = 1;
102
103  // nextScannerId and scannerMap are used to manage scanner state
104  private int nextScannerId = 0;
105  private Cache<Integer, ResultScannerWrapper> scannerMap;
106  IncrementCoalescer coalescer;
107
108  /**
109   * Returns a list of all the column families for a given Table.
110   * @param table table n
111   */
112  byte[][] getAllColumns(Table table) throws IOException {
113    HColumnDescriptor[] cds = table.getTableDescriptor().getColumnFamilies();
114    byte[][] columns = new byte[cds.length][];
115    for (int i = 0; i < cds.length; i++) {
116      columns[i] = Bytes.add(cds[i].getName(), KeyValue.COLUMN_FAMILY_DELIM_ARRAY);
117    }
118    return columns;
119  }
120
121  /**
122   * Assigns a unique ID to the scanner and adds the mapping to an internal hash-map.
123   * @param scanner the {@link ResultScanner} to add
124   * @return integer scanner id
125   */
126  protected synchronized int addScanner(ResultScanner scanner, boolean sortColumns) {
127    int id = nextScannerId++;
128    ResultScannerWrapper resultScannerWrapper = new ResultScannerWrapper(scanner, sortColumns);
129    scannerMap.put(id, resultScannerWrapper);
130    return id;
131  }
132
133  /**
134   * Returns the scanner associated with the specified ID.
135   * @param id the ID of the scanner to get
136   * @return a Scanner, or null if ID was invalid.
137   */
138  private synchronized ResultScannerWrapper getScanner(int id) {
139    return scannerMap.getIfPresent(id);
140  }
141
142  /**
143   * Removes the scanner associated with the specified ID from the internal id-&gt;scanner hash-map.
144   * @param id the ID of the scanner to remove
145   */
146  private synchronized void removeScanner(int id) {
147    scannerMap.invalidate(id);
148  }
149
150  protected ThriftHBaseServiceHandler(final Configuration c, final UserProvider userProvider)
151    throws IOException {
152    super(c, userProvider);
153    long cacheTimeout =
154      c.getLong(HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, DEFAULT_HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD)
155        * 2;
156
157    scannerMap =
158      CacheBuilder.newBuilder().expireAfterAccess(cacheTimeout, TimeUnit.MILLISECONDS).build();
159
160    this.coalescer = new IncrementCoalescer(this);
161  }
162
163  @Override
164  public void enableTable(ByteBuffer tableName) throws IOError {
165    try {
166      getAdmin().enableTable(getTableName(tableName));
167    } catch (IOException e) {
168      LOG.warn(e.getMessage(), e);
169      throw getIOError(e);
170    }
171  }
172
173  @Override
174  public void disableTable(ByteBuffer tableName) throws IOError {
175    try {
176      getAdmin().disableTable(getTableName(tableName));
177    } catch (IOException e) {
178      LOG.warn(e.getMessage(), e);
179      throw getIOError(e);
180    }
181  }
182
183  @Override
184  public boolean isTableEnabled(ByteBuffer tableName) throws IOError {
185    try {
186      return this.connectionCache.getAdmin().isTableEnabled(getTableName(tableName));
187    } catch (IOException e) {
188      LOG.warn(e.getMessage(), e);
189      throw getIOError(e);
190    }
191  }
192
193  @Override
194  public Map<ByteBuffer, Boolean> getTableNamesWithIsTableEnabled() throws IOError {
195    try {
196      HashMap<ByteBuffer, Boolean> tables = new HashMap<>();
197      for (ByteBuffer tableName : this.getTableNames()) {
198        tables.put(tableName, this.isTableEnabled(tableName));
199      }
200      return tables;
201    } catch (IOError e) {
202      LOG.warn(e.getMessage(), e);
203      throw getIOError(e);
204    }
205  }
206
207  // ThriftServerRunner.compact should be deprecated and replaced with methods specific to
208  // table and region.
209  @Override
210  public void compact(ByteBuffer tableNameOrRegionName) throws IOError {
211    try {
212      try {
213        getAdmin().compactRegion(getBytes(tableNameOrRegionName));
214      } catch (IllegalArgumentException e) {
215        // Invalid region, try table
216        getAdmin().compact(TableName.valueOf(getBytes(tableNameOrRegionName)));
217      }
218    } catch (IOException e) {
219      LOG.warn(e.getMessage(), e);
220      throw getIOError(e);
221    }
222  }
223
224  // ThriftServerRunner.majorCompact should be deprecated and replaced with methods specific
225  // to table and region.
226  @Override
227  public void majorCompact(ByteBuffer tableNameOrRegionName) throws IOError {
228    try {
229      try {
230        getAdmin().compactRegion(getBytes(tableNameOrRegionName));
231      } catch (IllegalArgumentException e) {
232        // Invalid region, try table
233        getAdmin().compact(TableName.valueOf(getBytes(tableNameOrRegionName)));
234      }
235    } catch (IOException e) {
236      LOG.warn(e.getMessage(), e);
237      throw getIOError(e);
238    }
239  }
240
241  @Override
242  public List<ByteBuffer> getTableNames() throws IOError {
243    try {
244      TableName[] tableNames = this.getAdmin().listTableNames();
245      ArrayList<ByteBuffer> list = new ArrayList<>(tableNames.length);
246      for (TableName tableName : tableNames) {
247        list.add(ByteBuffer.wrap(tableName.getName()));
248      }
249      return list;
250    } catch (IOException e) {
251      LOG.warn(e.getMessage(), e);
252      throw getIOError(e);
253    }
254  }
255
256  /**
257   * Returns the list of regions in the given table, or an empty list if the table does not exist
258   */
259  @Override
260  public List<TRegionInfo> getTableRegions(ByteBuffer tableName) throws IOError {
261    try (RegionLocator locator = connectionCache.getRegionLocator(getBytes(tableName))) {
262      List<HRegionLocation> regionLocations = locator.getAllRegionLocations();
263      List<TRegionInfo> results = new ArrayList<>(regionLocations.size());
264      for (HRegionLocation regionLocation : regionLocations) {
265        RegionInfo info = regionLocation.getRegionInfo();
266        ServerName serverName = regionLocation.getServerName();
267        TRegionInfo region = new TRegionInfo();
268        region.serverName = ByteBuffer.wrap(Bytes.toBytes(serverName.getHostname()));
269        region.port = serverName.getPort();
270        region.startKey = ByteBuffer.wrap(info.getStartKey());
271        region.endKey = ByteBuffer.wrap(info.getEndKey());
272        region.id = info.getRegionId();
273        region.name = ByteBuffer.wrap(info.getRegionName());
274        region.version = HREGION_VERSION; // HRegion now not versioned, PB encoding used
275        results.add(region);
276      }
277      return results;
278    } catch (TableNotFoundException e) {
279      // Return empty list for non-existing table
280      return Collections.emptyList();
281    } catch (IOException e) {
282      LOG.warn(e.getMessage(), e);
283      throw getIOError(e);
284    }
285  }
286
287  @Override
288  public List<TCell> get(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
289    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
290    byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
291    if (famAndQf.length == 1) {
292      return get(tableName, row, famAndQf[0], null, attributes);
293    }
294    if (famAndQf.length == 2) {
295      return get(tableName, row, famAndQf[0], famAndQf[1], attributes);
296    }
297    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
298  }
299
300  /**
301   * Note: this internal interface is slightly different from public APIs in regard to handling of
302   * the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather, we
303   * respect qual == null as a request for the entire column family. The caller (
304   * {@link #get(ByteBuffer, ByteBuffer, ByteBuffer, Map)}) interface IS consistent in that the
305   * column is parse like normal.
306   */
307  protected List<TCell> get(ByteBuffer tableName, ByteBuffer row, byte[] family, byte[] qualifier,
308    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
309    Table table = null;
310    try {
311      table = getTable(tableName);
312      Get get = new Get(getBytes(row));
313      addAttributes(get, attributes);
314      if (qualifier == null) {
315        get.addFamily(family);
316      } else {
317        get.addColumn(family, qualifier);
318      }
319      Result result = table.get(get);
320      return ThriftUtilities.cellFromHBase(result.rawCells());
321    } catch (IOException e) {
322      LOG.warn(e.getMessage(), e);
323      throw getIOError(e);
324    } finally {
325      closeTable(table);
326    }
327  }
328
329  @Override
330  public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
331    int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
332    byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
333    if (famAndQf.length == 1) {
334      return getVer(tableName, row, famAndQf[0], null, numVersions, attributes);
335    }
336    if (famAndQf.length == 2) {
337      return getVer(tableName, row, famAndQf[0], famAndQf[1], numVersions, attributes);
338    }
339    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
340
341  }
342
343  /**
344   * Note: this public interface is slightly different from public Java APIs in regard to handling
345   * of the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather, we
346   * respect qual == null as a request for the entire column family. If you want to access the
347   * entire column family, use {@link #getVer(ByteBuffer, ByteBuffer, ByteBuffer, int, Map)} with a
348   * {@code column} value that lacks a {@code ':'}.
349   */
350  public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, byte[] family, byte[] qualifier,
351    int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
352
353    Table table = null;
354    try {
355      table = getTable(tableName);
356      Get get = new Get(getBytes(row));
357      addAttributes(get, attributes);
358      if (null == qualifier) {
359        get.addFamily(family);
360      } else {
361        get.addColumn(family, qualifier);
362      }
363      get.setMaxVersions(numVersions);
364      Result result = table.get(get);
365      return ThriftUtilities.cellFromHBase(result.rawCells());
366    } catch (IOException e) {
367      LOG.warn(e.getMessage(), e);
368      throw getIOError(e);
369    } finally {
370      closeTable(table);
371    }
372  }
373
374  @Override
375  public List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
376    long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
377    byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
378    if (famAndQf.length == 1) {
379      return getVerTs(tableName, row, famAndQf[0], null, timestamp, numVersions, attributes);
380    }
381    if (famAndQf.length == 2) {
382      return getVerTs(tableName, row, famAndQf[0], famAndQf[1], timestamp, numVersions, attributes);
383    }
384    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
385  }
386
387  /**
388   * Note: this internal interface is slightly different from public APIs in regard to handling of
389   * the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather, we
390   * respect qual == null as a request for the entire column family. The caller (
391   * {@link #getVerTs(ByteBuffer, ByteBuffer, ByteBuffer, long, int, Map)}) interface IS consistent
392   * in that the column is parse like normal.
393   */
394  protected List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, byte[] family,
395    byte[] qualifier, long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes)
396    throws IOError {
397
398    Table table = null;
399    try {
400      table = getTable(tableName);
401      Get get = new Get(getBytes(row));
402      addAttributes(get, attributes);
403      if (null == qualifier) {
404        get.addFamily(family);
405      } else {
406        get.addColumn(family, qualifier);
407      }
408      get.setTimeRange(0, timestamp);
409      get.setMaxVersions(numVersions);
410      Result result = table.get(get);
411      return ThriftUtilities.cellFromHBase(result.rawCells());
412    } catch (IOException e) {
413      LOG.warn(e.getMessage(), e);
414      throw getIOError(e);
415    } finally {
416      closeTable(table);
417    }
418  }
419
420  @Override
421  public List<TRowResult> getRow(ByteBuffer tableName, ByteBuffer row,
422    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
423    return getRowWithColumnsTs(tableName, row, null, HConstants.LATEST_TIMESTAMP, attributes);
424  }
425
426  @Override
427  public List<TRowResult> getRowWithColumns(ByteBuffer tableName, ByteBuffer row,
428    List<ByteBuffer> columns, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
429    return getRowWithColumnsTs(tableName, row, columns, HConstants.LATEST_TIMESTAMP, attributes);
430  }
431
432  @Override
433  public List<TRowResult> getRowTs(ByteBuffer tableName, ByteBuffer row, long timestamp,
434    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
435    return getRowWithColumnsTs(tableName, row, null, timestamp, attributes);
436  }
437
438  @Override
439  public List<TRowResult> getRowWithColumnsTs(ByteBuffer tableName, ByteBuffer row,
440    List<ByteBuffer> columns, long timestamp, Map<ByteBuffer, ByteBuffer> attributes)
441    throws IOError {
442
443    Table table = null;
444    try {
445      table = getTable(tableName);
446      if (columns == null) {
447        Get get = new Get(getBytes(row));
448        addAttributes(get, attributes);
449        get.setTimeRange(0, timestamp);
450        Result result = table.get(get);
451        return ThriftUtilities.rowResultFromHBase(result);
452      }
453      Get get = new Get(getBytes(row));
454      addAttributes(get, attributes);
455      for (ByteBuffer column : columns) {
456        byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
457        if (famAndQf.length == 1) {
458          get.addFamily(famAndQf[0]);
459        } else {
460          get.addColumn(famAndQf[0], famAndQf[1]);
461        }
462      }
463      get.setTimeRange(0, timestamp);
464      Result result = table.get(get);
465      return ThriftUtilities.rowResultFromHBase(result);
466    } catch (IOException e) {
467      LOG.warn(e.getMessage(), e);
468      throw getIOError(e);
469    } finally {
470      closeTable(table);
471    }
472  }
473
474  @Override
475  public List<TRowResult> getRows(ByteBuffer tableName, List<ByteBuffer> rows,
476    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
477    return getRowsWithColumnsTs(tableName, rows, null, HConstants.LATEST_TIMESTAMP, attributes);
478  }
479
480  @Override
481  public List<TRowResult> getRowsWithColumns(ByteBuffer tableName, List<ByteBuffer> rows,
482    List<ByteBuffer> columns, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
483    return getRowsWithColumnsTs(tableName, rows, columns, HConstants.LATEST_TIMESTAMP, attributes);
484  }
485
486  @Override
487  public List<TRowResult> getRowsTs(ByteBuffer tableName, List<ByteBuffer> rows, long timestamp,
488    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
489    return getRowsWithColumnsTs(tableName, rows, null, timestamp, attributes);
490  }
491
492  @Override
493  public List<TRowResult> getRowsWithColumnsTs(ByteBuffer tableName, List<ByteBuffer> rows,
494    List<ByteBuffer> columns, long timestamp, Map<ByteBuffer, ByteBuffer> attributes)
495    throws IOError {
496
497    Table table = null;
498    try {
499      List<Get> gets = new ArrayList<>(rows.size());
500      table = getTable(tableName);
501      if (metrics != null) {
502        metrics.incNumRowKeysInBatchGet(rows.size());
503      }
504      for (ByteBuffer row : rows) {
505        Get get = new Get(getBytes(row));
506        addAttributes(get, attributes);
507        if (columns != null) {
508
509          for (ByteBuffer column : columns) {
510            byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
511            if (famAndQf.length == 1) {
512              get.addFamily(famAndQf[0]);
513            } else {
514              get.addColumn(famAndQf[0], famAndQf[1]);
515            }
516          }
517        }
518        get.setTimeRange(0, timestamp);
519        gets.add(get);
520      }
521      Result[] result = table.get(gets);
522      return ThriftUtilities.rowResultFromHBase(result);
523    } catch (IOException e) {
524      LOG.warn(e.getMessage(), e);
525      throw getIOError(e);
526    } finally {
527      closeTable(table);
528    }
529  }
530
531  @Override
532  public void deleteAll(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
533    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
534    deleteAllTs(tableName, row, column, HConstants.LATEST_TIMESTAMP, attributes);
535  }
536
537  @Override
538  public void deleteAllTs(ByteBuffer tableName, ByteBuffer row, ByteBuffer column, long timestamp,
539    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
540    Table table = null;
541    try {
542      table = getTable(tableName);
543      Delete delete = new Delete(getBytes(row));
544      addAttributes(delete, attributes);
545      byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
546      if (famAndQf.length == 1) {
547        delete.addFamily(famAndQf[0], timestamp);
548      } else {
549        delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
550      }
551      table.delete(delete);
552
553    } catch (IOException e) {
554      LOG.warn(e.getMessage(), e);
555      throw getIOError(e);
556    } finally {
557      closeTable(table);
558    }
559  }
560
561  @Override
562  public void deleteAllRow(ByteBuffer tableName, ByteBuffer row,
563    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
564    deleteAllRowTs(tableName, row, HConstants.LATEST_TIMESTAMP, attributes);
565  }
566
567  @Override
568  public void deleteAllRowTs(ByteBuffer tableName, ByteBuffer row, long timestamp,
569    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
570    Table table = null;
571    try {
572      table = getTable(tableName);
573      Delete delete = new Delete(getBytes(row), timestamp);
574      addAttributes(delete, attributes);
575      table.delete(delete);
576    } catch (IOException e) {
577      LOG.warn(e.getMessage(), e);
578      throw getIOError(e);
579    } finally {
580      closeTable(table);
581    }
582  }
583
584  @Override
585  public void createTable(ByteBuffer in_tableName, List<ColumnDescriptor> columnFamilies)
586    throws IOError, IllegalArgument, AlreadyExists {
587    TableName tableName = getTableName(in_tableName);
588    try {
589      if (getAdmin().tableExists(tableName)) {
590        throw new AlreadyExists("table name already in use");
591      }
592      HTableDescriptor desc = new HTableDescriptor(tableName);
593      for (ColumnDescriptor col : columnFamilies) {
594        HColumnDescriptor colDesc = ThriftUtilities.colDescFromThrift(col);
595        desc.addFamily(colDesc);
596      }
597      getAdmin().createTable(desc);
598    } catch (IOException e) {
599      LOG.warn(e.getMessage(), e);
600      throw getIOError(e);
601    } catch (IllegalArgumentException e) {
602      LOG.warn(e.getMessage(), e);
603      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
604    }
605  }
606
607  private static TableName getTableName(ByteBuffer buffer) {
608    return TableName.valueOf(getBytes(buffer));
609  }
610
611  @Override
612  public void deleteTable(ByteBuffer in_tableName) throws IOError {
613    TableName tableName = getTableName(in_tableName);
614    if (LOG.isDebugEnabled()) {
615      LOG.debug("deleteTable: table={}", tableName);
616    }
617    try {
618      if (!getAdmin().tableExists(tableName)) {
619        throw new IOException("table does not exist");
620      }
621      getAdmin().deleteTable(tableName);
622    } catch (IOException e) {
623      LOG.warn(e.getMessage(), e);
624      throw getIOError(e);
625    }
626  }
627
628  @Override
629  public void mutateRow(ByteBuffer tableName, ByteBuffer row, List<Mutation> mutations,
630    Map<ByteBuffer, ByteBuffer> attributes) throws IOError, IllegalArgument {
631    mutateRowTs(tableName, row, mutations, HConstants.LATEST_TIMESTAMP, attributes);
632  }
633
634  @Override
635  public void mutateRowTs(ByteBuffer tableName, ByteBuffer row, List<Mutation> mutations,
636    long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError, IllegalArgument {
637    Table table = null;
638    try {
639      table = getTable(tableName);
640      Put put = new Put(getBytes(row), timestamp);
641      addAttributes(put, attributes);
642
643      Delete delete = new Delete(getBytes(row));
644      addAttributes(delete, attributes);
645      if (metrics != null) {
646        metrics.incNumRowKeysInBatchMutate(mutations.size());
647      }
648
649      // I apologize for all this mess :)
650      CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
651      for (Mutation m : mutations) {
652        byte[][] famAndQf = CellUtil.parseColumn(getBytes(m.column));
653        if (m.isDelete) {
654          if (famAndQf.length == 1) {
655            delete.addFamily(famAndQf[0], timestamp);
656          } else {
657            delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
658          }
659          delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
660        } else {
661          if (famAndQf.length == 1) {
662            LOG.warn("No column qualifier specified. Delete is the only mutation supported "
663              + "over the whole column family.");
664          } else {
665            put.add(builder.clear().setRow(put.getRow()).setFamily(famAndQf[0])
666              .setQualifier(famAndQf[1]).setTimestamp(put.getTimestamp()).setType(Cell.Type.Put)
667              .setValue(m.value != null ? getBytes(m.value) : HConstants.EMPTY_BYTE_ARRAY).build());
668          }
669          put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
670        }
671      }
672      if (!delete.isEmpty()) {
673        table.delete(delete);
674      }
675      if (!put.isEmpty()) {
676        table.put(put);
677      }
678    } catch (IOException e) {
679      LOG.warn(e.getMessage(), e);
680      throw getIOError(e);
681    } catch (IllegalArgumentException e) {
682      LOG.warn(e.getMessage(), e);
683      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
684    } finally {
685      closeTable(table);
686    }
687  }
688
689  @Override
690  public void mutateRows(ByteBuffer tableName, List<BatchMutation> rowBatches,
691    Map<ByteBuffer, ByteBuffer> attributes) throws IOError, IllegalArgument, TException {
692    mutateRowsTs(tableName, rowBatches, HConstants.LATEST_TIMESTAMP, attributes);
693  }
694
695  @Override
696  public void mutateRowsTs(ByteBuffer tableName, List<BatchMutation> rowBatches, long timestamp,
697    Map<ByteBuffer, ByteBuffer> attributes) throws IOError, IllegalArgument, TException {
698    List<Put> puts = new ArrayList<>();
699    List<Delete> deletes = new ArrayList<>();
700    CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
701    for (BatchMutation batch : rowBatches) {
702      byte[] row = getBytes(batch.row);
703      List<Mutation> mutations = batch.mutations;
704      Delete delete = new Delete(row);
705      addAttributes(delete, attributes);
706      Put put = new Put(row, timestamp);
707      addAttributes(put, attributes);
708      for (Mutation m : mutations) {
709        byte[][] famAndQf = CellUtil.parseColumn(getBytes(m.column));
710        if (m.isDelete) {
711          // no qualifier, family only.
712          if (famAndQf.length == 1) {
713            delete.addFamily(famAndQf[0], timestamp);
714          } else {
715            delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
716          }
717          delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
718        } else {
719          if (famAndQf.length == 1) {
720            LOG.warn("No column qualifier specified. Delete is the only mutation supported "
721              + "over the whole column family.");
722          }
723          if (famAndQf.length == 2) {
724            try {
725              put.add(builder.clear().setRow(put.getRow()).setFamily(famAndQf[0])
726                .setQualifier(famAndQf[1]).setTimestamp(put.getTimestamp()).setType(Cell.Type.Put)
727                .setValue(m.value != null ? getBytes(m.value) : HConstants.EMPTY_BYTE_ARRAY)
728                .build());
729            } catch (IOException e) {
730              throw new IllegalArgumentException(e);
731            }
732          } else {
733            throw new IllegalArgumentException("Invalid famAndQf provided.");
734          }
735          put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
736        }
737      }
738      if (!delete.isEmpty()) {
739        deletes.add(delete);
740      }
741      if (!put.isEmpty()) {
742        puts.add(put);
743      }
744    }
745
746    Table table = null;
747    try {
748      table = getTable(tableName);
749      if (!puts.isEmpty()) {
750        table.put(puts);
751      }
752      if (!deletes.isEmpty()) {
753        table.delete(deletes);
754      }
755    } catch (IOException e) {
756      LOG.warn(e.getMessage(), e);
757      throw getIOError(e);
758    } catch (IllegalArgumentException e) {
759      LOG.warn(e.getMessage(), e);
760      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
761    } finally {
762      closeTable(table);
763    }
764  }
765
766  @Override
767  public long atomicIncrement(ByteBuffer tableName, ByteBuffer row, ByteBuffer column, long amount)
768    throws IOError, IllegalArgument, TException {
769    byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
770    if (famAndQf.length == 1) {
771      return atomicIncrement(tableName, row, famAndQf[0], HConstants.EMPTY_BYTE_ARRAY, amount);
772    }
773    return atomicIncrement(tableName, row, famAndQf[0], famAndQf[1], amount);
774  }
775
776  protected long atomicIncrement(ByteBuffer tableName, ByteBuffer row, byte[] family,
777    byte[] qualifier, long amount) throws IOError, IllegalArgument, TException {
778    Table table = null;
779    try {
780      table = getTable(tableName);
781      return table.incrementColumnValue(getBytes(row), family, qualifier, amount);
782    } catch (IOException e) {
783      LOG.warn(e.getMessage(), e);
784      throw getIOError(e);
785    } finally {
786      closeTable(table);
787    }
788  }
789
790  @Override
791  public void scannerClose(int id) throws IOError, IllegalArgument {
792    LOG.debug("scannerClose: id={}", id);
793    ResultScannerWrapper resultScannerWrapper = getScanner(id);
794    if (resultScannerWrapper == null) {
795      LOG.warn("scanner ID is invalid");
796      throw new IllegalArgument("scanner ID is invalid");
797    }
798    resultScannerWrapper.getScanner().close();
799    removeScanner(id);
800  }
801
802  @Override
803  public List<TRowResult> scannerGetList(int id, int nbRows) throws IllegalArgument, IOError {
804    LOG.debug("scannerGetList: id={}", id);
805    ResultScannerWrapper resultScannerWrapper = getScanner(id);
806    if (null == resultScannerWrapper) {
807      String message = "scanner ID is invalid";
808      LOG.warn(message);
809      throw new IllegalArgument("scanner ID is invalid");
810    }
811
812    Result[] results;
813    try {
814      results = resultScannerWrapper.getScanner().next(nbRows);
815      if (null == results) {
816        return new ArrayList<>();
817      }
818    } catch (IOException e) {
819      LOG.warn(e.getMessage(), e);
820      throw getIOError(e);
821    } finally {
822      // Add scanner back to scannerMap; protects against case
823      // where scanner expired during processing of request.
824      scannerMap.put(id, resultScannerWrapper);
825    }
826    return ThriftUtilities.rowResultFromHBase(results, resultScannerWrapper.isColumnSorted());
827  }
828
829  @Override
830  public List<TRowResult> scannerGet(int id) throws IllegalArgument, IOError {
831    return scannerGetList(id, 1);
832  }
833
834  @Override
835  public int scannerOpenWithScan(ByteBuffer tableName, TScan tScan,
836    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
837
838    Table table = null;
839    try {
840      table = getTable(tableName);
841      Scan scan = new Scan();
842      addAttributes(scan, attributes);
843      if (tScan.isSetStartRow()) {
844        scan.setStartRow(tScan.getStartRow());
845      }
846      if (tScan.isSetStopRow()) {
847        scan.setStopRow(tScan.getStopRow());
848      }
849      if (tScan.isSetTimestamp()) {
850        scan.setTimeRange(0, tScan.getTimestamp());
851      }
852      if (tScan.isSetCaching()) {
853        scan.setCaching(tScan.getCaching());
854      }
855      if (tScan.isSetBatchSize()) {
856        scan.setBatch(tScan.getBatchSize());
857      }
858      if (tScan.isSetColumns() && !tScan.getColumns().isEmpty()) {
859        for (ByteBuffer column : tScan.getColumns()) {
860          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
861          if (famQf.length == 1) {
862            scan.addFamily(famQf[0]);
863          } else {
864            scan.addColumn(famQf[0], famQf[1]);
865          }
866        }
867      }
868      if (tScan.isSetFilterString()) {
869        ParseFilter parseFilter = new ParseFilter();
870        scan.setFilter(parseFilter.parseFilterString(tScan.getFilterString()));
871      }
872      if (tScan.isSetReversed()) {
873        scan.setReversed(tScan.isReversed());
874      }
875      if (tScan.isSetCacheBlocks()) {
876        scan.setCacheBlocks(tScan.isCacheBlocks());
877      }
878      return addScanner(table.getScanner(scan), tScan.sortColumns);
879    } catch (IOException e) {
880      LOG.warn(e.getMessage(), e);
881      throw getIOError(e);
882    } finally {
883      closeTable(table);
884    }
885  }
886
887  @Override
888  public int scannerOpen(ByteBuffer tableName, ByteBuffer startRow, List<ByteBuffer> columns,
889    Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
890
891    Table table = null;
892    try {
893      table = getTable(tableName);
894      Scan scan = new Scan(getBytes(startRow));
895      addAttributes(scan, attributes);
896      if (columns != null && !columns.isEmpty()) {
897        for (ByteBuffer column : columns) {
898          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
899          if (famQf.length == 1) {
900            scan.addFamily(famQf[0]);
901          } else {
902            scan.addColumn(famQf[0], famQf[1]);
903          }
904        }
905      }
906      return addScanner(table.getScanner(scan), false);
907    } catch (IOException e) {
908      LOG.warn(e.getMessage(), e);
909      throw getIOError(e);
910    } finally {
911      closeTable(table);
912    }
913  }
914
915  @Override
916  public int scannerOpenWithStop(ByteBuffer tableName, ByteBuffer startRow, ByteBuffer stopRow,
917    List<ByteBuffer> columns, Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
918
919    Table table = null;
920    try {
921      table = getTable(tableName);
922      Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
923      addAttributes(scan, attributes);
924      if (columns != null && !columns.isEmpty()) {
925        for (ByteBuffer column : columns) {
926          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
927          if (famQf.length == 1) {
928            scan.addFamily(famQf[0]);
929          } else {
930            scan.addColumn(famQf[0], famQf[1]);
931          }
932        }
933      }
934      return addScanner(table.getScanner(scan), false);
935    } catch (IOException e) {
936      LOG.warn(e.getMessage(), e);
937      throw getIOError(e);
938    } finally {
939      closeTable(table);
940    }
941  }
942
943  @Override
944  public int scannerOpenWithPrefix(ByteBuffer tableName, ByteBuffer startAndPrefix,
945    List<ByteBuffer> columns, Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
946
947    Table table = null;
948    try {
949      table = getTable(tableName);
950      Scan scan = new Scan(getBytes(startAndPrefix));
951      addAttributes(scan, attributes);
952      Filter f = new WhileMatchFilter(new PrefixFilter(getBytes(startAndPrefix)));
953      scan.setFilter(f);
954      if (columns != null && !columns.isEmpty()) {
955        for (ByteBuffer column : columns) {
956          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
957          if (famQf.length == 1) {
958            scan.addFamily(famQf[0]);
959          } else {
960            scan.addColumn(famQf[0], famQf[1]);
961          }
962        }
963      }
964      return addScanner(table.getScanner(scan), false);
965    } catch (IOException e) {
966      LOG.warn(e.getMessage(), e);
967      throw getIOError(e);
968    } finally {
969      closeTable(table);
970    }
971  }
972
973  @Override
974  public int scannerOpenTs(ByteBuffer tableName, ByteBuffer startRow, List<ByteBuffer> columns,
975    long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
976
977    Table table = null;
978    try {
979      table = getTable(tableName);
980      Scan scan = new Scan(getBytes(startRow));
981      addAttributes(scan, attributes);
982      scan.setTimeRange(0, timestamp);
983      if (columns != null && !columns.isEmpty()) {
984        for (ByteBuffer column : columns) {
985          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
986          if (famQf.length == 1) {
987            scan.addFamily(famQf[0]);
988          } else {
989            scan.addColumn(famQf[0], famQf[1]);
990          }
991        }
992      }
993      return addScanner(table.getScanner(scan), false);
994    } catch (IOException e) {
995      LOG.warn(e.getMessage(), e);
996      throw getIOError(e);
997    } finally {
998      closeTable(table);
999    }
1000  }
1001
1002  @Override
1003  public int scannerOpenWithStopTs(ByteBuffer tableName, ByteBuffer startRow, ByteBuffer stopRow,
1004    List<ByteBuffer> columns, long timestamp, Map<ByteBuffer, ByteBuffer> attributes)
1005    throws IOError, TException {
1006
1007    Table table = null;
1008    try {
1009      table = getTable(tableName);
1010      Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1011      addAttributes(scan, attributes);
1012      scan.setTimeRange(0, timestamp);
1013      if (columns != null && !columns.isEmpty()) {
1014        for (ByteBuffer column : columns) {
1015          byte[][] famQf = CellUtil.parseColumn(getBytes(column));
1016          if (famQf.length == 1) {
1017            scan.addFamily(famQf[0]);
1018          } else {
1019            scan.addColumn(famQf[0], famQf[1]);
1020          }
1021        }
1022      }
1023      scan.setTimeRange(0, timestamp);
1024      return addScanner(table.getScanner(scan), false);
1025    } catch (IOException e) {
1026      LOG.warn(e.getMessage(), e);
1027      throw getIOError(e);
1028    } finally {
1029      closeTable(table);
1030    }
1031  }
1032
1033  @Override
1034  public Map<ByteBuffer, ColumnDescriptor> getColumnDescriptors(ByteBuffer tableName)
1035    throws IOError, TException {
1036
1037    Table table = null;
1038    try {
1039      TreeMap<ByteBuffer, ColumnDescriptor> columns = new TreeMap<>();
1040
1041      table = getTable(tableName);
1042      HTableDescriptor desc = table.getTableDescriptor();
1043
1044      for (HColumnDescriptor e : desc.getFamilies()) {
1045        ColumnDescriptor col = ThriftUtilities.colDescFromHbase(e);
1046        columns.put(col.name, col);
1047      }
1048      return columns;
1049    } catch (IOException e) {
1050      LOG.warn(e.getMessage(), e);
1051      throw getIOError(e);
1052    } finally {
1053      closeTable(table);
1054    }
1055  }
1056
1057  private void closeTable(Table table) throws IOError {
1058    try {
1059      if (table != null) {
1060        table.close();
1061      }
1062    } catch (IOException e) {
1063      LOG.error(e.getMessage(), e);
1064      throw getIOError(e);
1065    }
1066  }
1067
1068  @Override
1069  public TRegionInfo getRegionInfo(ByteBuffer searchRow) throws IOError {
1070    try {
1071      byte[] row = getBytes(searchRow);
1072      Result startRowResult =
1073        getReverseScanResult(TableName.META_TABLE_NAME.getName(), row, HConstants.CATALOG_FAMILY);
1074
1075      if (startRowResult == null) {
1076        throw new IOException(
1077          "Cannot find row in " + TableName.META_TABLE_NAME + ", row=" + Bytes.toStringBinary(row));
1078      }
1079
1080      // find region start and end keys
1081      RegionInfo regionInfo = MetaTableAccessor.getRegionInfo(startRowResult);
1082      if (regionInfo == null) {
1083        throw new IOException("RegionInfo REGIONINFO was null or " + " empty in Meta for row="
1084          + Bytes.toStringBinary(row));
1085      }
1086      TRegionInfo region = new TRegionInfo();
1087      region.setStartKey(regionInfo.getStartKey());
1088      region.setEndKey(regionInfo.getEndKey());
1089      region.id = regionInfo.getRegionId();
1090      region.setName(regionInfo.getRegionName());
1091      region.version = HREGION_VERSION; // version not used anymore, PB encoding used.
1092
1093      // find region assignment to server
1094      ServerName serverName = MetaTableAccessor.getServerName(startRowResult, 0);
1095      if (serverName != null) {
1096        region.setServerName(Bytes.toBytes(serverName.getHostname()));
1097        region.port = serverName.getPort();
1098      }
1099      return region;
1100    } catch (IOException e) {
1101      LOG.warn(e.getMessage(), e);
1102      throw getIOError(e);
1103    }
1104  }
1105
1106  private Result getReverseScanResult(byte[] tableName, byte[] row, byte[] family)
1107    throws IOException {
1108    Scan scan = new Scan(row);
1109    scan.setReversed(true);
1110    scan.addFamily(family);
1111    scan.setStartRow(row);
1112    try (Table table = getTable(tableName); ResultScanner scanner = table.getScanner(scan)) {
1113      return scanner.next();
1114    }
1115  }
1116
1117  @Override
1118  public void increment(TIncrement tincrement) throws IOError, TException {
1119
1120    if (tincrement.getRow().length == 0 || tincrement.getTable().length == 0) {
1121      throw new TException("Must supply a table and a row key; can't increment");
1122    }
1123
1124    if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1125      this.coalescer.queueIncrement(tincrement);
1126      return;
1127    }
1128
1129    Table table = null;
1130    try {
1131      table = getTable(tincrement.getTable());
1132      Increment inc = ThriftUtilities.incrementFromThrift(tincrement);
1133      table.increment(inc);
1134    } catch (IOException e) {
1135      LOG.warn(e.getMessage(), e);
1136      throw getIOError(e);
1137    } finally {
1138      closeTable(table);
1139    }
1140  }
1141
1142  @Override
1143  public void incrementRows(List<TIncrement> tincrements) throws IOError, TException {
1144    if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1145      this.coalescer.queueIncrements(tincrements);
1146      return;
1147    }
1148    for (TIncrement tinc : tincrements) {
1149      increment(tinc);
1150    }
1151  }
1152
1153  @Override
1154  public List<TCell> append(TAppend tappend) throws IOError, TException {
1155    if (tappend.getRow().length == 0 || tappend.getTable().length == 0) {
1156      throw new TException("Must supply a table and a row key; can't append");
1157    }
1158
1159    Table table = null;
1160    try {
1161      table = getTable(tappend.getTable());
1162      Append append = ThriftUtilities.appendFromThrift(tappend);
1163      Result result = table.append(append);
1164      return ThriftUtilities.cellFromHBase(result.rawCells());
1165    } catch (IOException e) {
1166      LOG.warn(e.getMessage(), e);
1167      throw getIOError(e);
1168    } finally {
1169      closeTable(table);
1170    }
1171  }
1172
1173  @Override
1174  public boolean checkAndPut(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
1175    ByteBuffer value, Mutation mput, Map<ByteBuffer, ByteBuffer> attributes)
1176    throws IOError, IllegalArgument, TException {
1177    Put put;
1178    try {
1179      put = new Put(getBytes(row), HConstants.LATEST_TIMESTAMP);
1180      addAttributes(put, attributes);
1181
1182      byte[][] famAndQf = CellUtil.parseColumn(getBytes(mput.column));
1183      put.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY).setRow(put.getRow())
1184        .setFamily(famAndQf[0]).setQualifier(famAndQf[1]).setTimestamp(put.getTimestamp())
1185        .setType(Cell.Type.Put)
1186        .setValue(mput.value != null ? getBytes(mput.value) : HConstants.EMPTY_BYTE_ARRAY).build());
1187      put.setDurability(mput.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1188    } catch (IOException | IllegalArgumentException e) {
1189      LOG.warn(e.getMessage(), e);
1190      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1191    }
1192
1193    Table table = null;
1194    try {
1195      table = getTable(tableName);
1196      byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
1197      Table.CheckAndMutateBuilder mutateBuilder =
1198        table.checkAndMutate(getBytes(row), famAndQf[0]).qualifier(famAndQf[1]);
1199      if (value != null) {
1200        return mutateBuilder.ifEquals(getBytes(value)).thenPut(put);
1201      } else {
1202        return mutateBuilder.ifNotExists().thenPut(put);
1203      }
1204    } catch (IOException e) {
1205      LOG.warn(e.getMessage(), e);
1206      throw getIOError(e);
1207    } catch (IllegalArgumentException e) {
1208      LOG.warn(e.getMessage(), e);
1209      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1210    } finally {
1211      closeTable(table);
1212    }
1213  }
1214
1215  @Override
1216  public TThriftServerType getThriftServerType() {
1217    return TThriftServerType.ONE;
1218  }
1219
1220  @Override
1221  public String getClusterId() throws TException {
1222    return connectionCache.getClusterId();
1223  }
1224
1225  private static IOError getIOError(Throwable throwable) {
1226    IOError error = new IOErrorWithCause(throwable);
1227    error.setCanRetry(!(throwable instanceof DoNotRetryIOException));
1228    error.setMessage(Throwables.getStackTraceAsString(throwable));
1229    return error;
1230  }
1231
1232  /**
1233   * Adds all the attributes into the Operation object
1234   */
1235  private static void addAttributes(OperationWithAttributes op,
1236    Map<ByteBuffer, ByteBuffer> attributes) {
1237    if (attributes == null || attributes.isEmpty()) {
1238      return;
1239    }
1240    for (Map.Entry<ByteBuffer, ByteBuffer> entry : attributes.entrySet()) {
1241      String name = Bytes.toStringBinary(getBytes(entry.getKey()));
1242      byte[] value = getBytes(entry.getValue());
1243      op.setAttribute(name, value);
1244    }
1245  }
1246
1247  protected static class ResultScannerWrapper {
1248
1249    private final ResultScanner scanner;
1250    private final boolean sortColumns;
1251
1252    public ResultScannerWrapper(ResultScanner resultScanner, boolean sortResultColumns) {
1253      scanner = resultScanner;
1254      sortColumns = sortResultColumns;
1255    }
1256
1257    public ResultScanner getScanner() {
1258      return scanner;
1259    }
1260
1261    public boolean isColumnSorted() {
1262      return sortColumns;
1263    }
1264  }
1265
1266  public static class IOErrorWithCause extends IOError {
1267    private final Throwable cause;
1268
1269    public IOErrorWithCause(Throwable cause) {
1270      this.cause = cause;
1271    }
1272
1273    @Override
1274    public synchronized Throwable getCause() {
1275      return cause;
1276    }
1277
1278    @Override
1279    public boolean equals(Object other) {
1280      if (super.equals(other) && other instanceof IOErrorWithCause) {
1281        Throwable otherCause = ((IOErrorWithCause) other).getCause();
1282        if (this.getCause() != null) {
1283          return otherCause != null && this.getCause().equals(otherCause);
1284        } else {
1285          return otherCause == null;
1286        }
1287      }
1288      return false;
1289    }
1290
1291    @Override
1292    public int hashCode() {
1293      int result = super.hashCode();
1294      result = 31 * result + (cause != null ? cause.hashCode() : 0);
1295      return result;
1296    }
1297  }
1298
1299}