001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.client;
020
021// DO NOT MAKE USE OF THESE IMPORTS! THEY ARE HERE FOR COPROCESSOR ENDPOINTS ONLY.
022// Internally, we use shaded protobuf. This below are part of our public API.
023//SEE ABOVE NOTE!
024import com.google.protobuf.Descriptors;
025import com.google.protobuf.Message;
026import com.google.protobuf.Service;
027import com.google.protobuf.ServiceException;
028
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.Cell;
031import org.apache.hadoop.hbase.CompareOperator;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.HRegionLocation;
034import org.apache.hadoop.hbase.HTableDescriptor;
035import org.apache.hadoop.hbase.KeyValueUtil;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.io.TimeRange;
038import org.apache.yetus.audience.InterfaceAudience;
039import org.apache.yetus.audience.InterfaceStability;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042import org.apache.hadoop.hbase.client.coprocessor.Batch;
043import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
044import org.apache.hadoop.hbase.filter.BinaryComparator;
045import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
046import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
047import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
048import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
049import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
050import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
051import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
052import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter;
053import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
054import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MultiRequest;
055import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MutateRequest;
056import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.MutateResponse;
057import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.RegionAction;
058import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.CompareType;
059import org.apache.hadoop.hbase.util.Bytes;
060import org.apache.hadoop.hbase.util.Pair;
061import org.apache.hadoop.hbase.util.ReflectionUtils;
062import org.apache.hadoop.hbase.util.Threads;
063
064import java.io.IOException;
065import java.io.InterruptedIOException;
066import java.util.ArrayList;
067import java.util.Collections;
068import java.util.List;
069import java.util.Map;
070import java.util.TreeMap;
071import java.util.concurrent.Callable;
072import java.util.concurrent.ExecutionException;
073import java.util.concurrent.ExecutorService;
074import java.util.concurrent.Future;
075import java.util.concurrent.SynchronousQueue;
076import java.util.concurrent.ThreadPoolExecutor;
077import java.util.concurrent.TimeUnit;
078
079import static org.apache.hadoop.hbase.client.ConnectionUtils.checkHasFamilies;
080
081/**
082 * An implementation of {@link Table}. Used to communicate with a single HBase table.
083 * Lightweight. Get as needed and just close when done.
084 * Instances of this class SHOULD NOT be constructed directly.
085 * Obtain an instance via {@link Connection}. See {@link ConnectionFactory}
086 * class comment for an example of how.
087 *
088 * <p>This class is thread safe since 2.0.0 if not invoking any of the setter methods.
089 * All setters are moved into {@link TableBuilder} and reserved here only for keeping
090 * backward compatibility, and TODO will be removed soon.
091 *
092 * <p>HTable is no longer a client API. Use {@link Table} instead. It is marked
093 * InterfaceAudience.Private indicating that this is an HBase-internal class as defined in
094 * <a href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/InterfaceClassification.html">Hadoop
095 * Interface Classification</a>
096 * There are no guarantees for backwards source / binary compatibility and methods or class can
097 * change or go away without deprecation.
098 *
099 * @see Table
100 * @see Admin
101 * @see Connection
102 * @see ConnectionFactory
103 */
104@InterfaceAudience.Private
105@InterfaceStability.Stable
106public class HTable implements Table {
107  private static final Logger LOG = LoggerFactory.getLogger(HTable.class);
108  private static final Consistency DEFAULT_CONSISTENCY = Consistency.STRONG;
109  private final ClusterConnection connection;
110  private final TableName tableName;
111  private final Configuration configuration;
112  private final ConnectionConfiguration connConfiguration;
113  private boolean closed = false;
114  private final int scannerCaching;
115  private final long scannerMaxResultSize;
116  private final ExecutorService pool;  // For Multi & Scan
117  private int operationTimeoutMs; // global timeout for each blocking method with retrying rpc
118  private final int rpcTimeoutMs; // FIXME we should use this for rpc like batch and checkAndXXX
119  private int readRpcTimeoutMs; // timeout for each read rpc request
120  private int writeRpcTimeoutMs; // timeout for each write rpc request
121  private final boolean cleanupPoolOnClose; // shutdown the pool in close()
122  private final HRegionLocator locator;
123
124  /** The Async process for batch */
125  @VisibleForTesting
126  AsyncProcess multiAp;
127  private final RpcRetryingCallerFactory rpcCallerFactory;
128  private final RpcControllerFactory rpcControllerFactory;
129
130  // Marked Private @since 1.0
131  @InterfaceAudience.Private
132  public static ThreadPoolExecutor getDefaultExecutor(Configuration conf) {
133    int maxThreads = conf.getInt("hbase.htable.threads.max", Integer.MAX_VALUE);
134    if (maxThreads == 0) {
135      maxThreads = 1; // is there a better default?
136    }
137    int corePoolSize = conf.getInt("hbase.htable.threads.coresize", 1);
138    long keepAliveTime = conf.getLong("hbase.htable.threads.keepalivetime", 60);
139
140    // Using the "direct handoff" approach, new threads will only be created
141    // if it is necessary and will grow unbounded. This could be bad but in HCM
142    // we only create as many Runnables as there are region servers. It means
143    // it also scales when new region servers are added.
144    ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maxThreads, keepAliveTime,
145      TimeUnit.SECONDS, new SynchronousQueue<>(), Threads.newDaemonThreadFactory("htable"));
146    pool.allowCoreThreadTimeOut(true);
147    return pool;
148  }
149
150  /**
151   * Creates an object to access a HBase table.
152   * Used by HBase internally.  DO NOT USE. See {@link ConnectionFactory} class comment for how to
153   * get a {@link Table} instance (use {@link Table} instead of {@link HTable}).
154   * @param connection Connection to be used.
155   * @param builder The table builder
156   * @param rpcCallerFactory The RPC caller factory
157   * @param rpcControllerFactory The RPC controller factory
158   * @param pool ExecutorService to be used.
159   */
160  @InterfaceAudience.Private
161  protected HTable(final ClusterConnection connection,
162      final TableBuilderBase builder,
163      final RpcRetryingCallerFactory rpcCallerFactory,
164      final RpcControllerFactory rpcControllerFactory,
165      final ExecutorService pool) {
166    this.connection = Preconditions.checkNotNull(connection, "connection is null");
167    this.configuration = connection.getConfiguration();
168    this.connConfiguration = connection.getConnectionConfiguration();
169    if (pool == null) {
170      this.pool = getDefaultExecutor(this.configuration);
171      this.cleanupPoolOnClose = true;
172    } else {
173      this.pool = pool;
174      this.cleanupPoolOnClose = false;
175    }
176    if (rpcCallerFactory == null) {
177      this.rpcCallerFactory = connection.getNewRpcRetryingCallerFactory(configuration);
178    } else {
179      this.rpcCallerFactory = rpcCallerFactory;
180    }
181
182    if (rpcControllerFactory == null) {
183      this.rpcControllerFactory = RpcControllerFactory.instantiate(configuration);
184    } else {
185      this.rpcControllerFactory = rpcControllerFactory;
186    }
187
188    this.tableName = builder.tableName;
189    this.operationTimeoutMs = builder.operationTimeout;
190    this.rpcTimeoutMs = builder.rpcTimeout;
191    this.readRpcTimeoutMs = builder.readRpcTimeout;
192    this.writeRpcTimeoutMs = builder.writeRpcTimeout;
193    this.scannerCaching = connConfiguration.getScannerCaching();
194    this.scannerMaxResultSize = connConfiguration.getScannerMaxResultSize();
195
196    // puts need to track errors globally due to how the APIs currently work.
197    multiAp = this.connection.getAsyncProcess();
198    this.locator = new HRegionLocator(tableName, connection);
199  }
200
201  /**
202   * @return maxKeyValueSize from configuration.
203   */
204  public static int getMaxKeyValueSize(Configuration conf) {
205    return conf.getInt(ConnectionConfiguration.MAX_KEYVALUE_SIZE_KEY, -1);
206  }
207
208  @Override
209  public Configuration getConfiguration() {
210    return configuration;
211  }
212
213  @Override
214  public TableName getName() {
215    return tableName;
216  }
217
218  /**
219   * <em>INTERNAL</em> Used by unit tests and tools to do low-level
220   * manipulations.
221   * @return A Connection instance.
222   */
223  @VisibleForTesting
224  protected Connection getConnection() {
225    return this.connection;
226  }
227
228  @Override
229  @Deprecated
230  public HTableDescriptor getTableDescriptor() throws IOException {
231    HTableDescriptor htd = HBaseAdmin.getHTableDescriptor(tableName, connection, rpcCallerFactory,
232      rpcControllerFactory, operationTimeoutMs, readRpcTimeoutMs);
233    if (htd != null) {
234      return new ImmutableHTableDescriptor(htd);
235    }
236    return null;
237  }
238
239  @Override
240  public TableDescriptor getDescriptor() throws IOException {
241    return HBaseAdmin.getTableDescriptor(tableName, connection, rpcCallerFactory,
242      rpcControllerFactory, operationTimeoutMs, readRpcTimeoutMs);
243  }
244
245  /**
246   * Get the corresponding start keys and regions for an arbitrary range of
247   * keys.
248   * <p>
249   * @param startKey Starting row in range, inclusive
250   * @param endKey Ending row in range
251   * @param includeEndKey true if endRow is inclusive, false if exclusive
252   * @return A pair of list of start keys and list of HRegionLocations that
253   *         contain the specified range
254   * @throws IOException if a remote or network exception occurs
255   */
256  private Pair<List<byte[]>, List<HRegionLocation>> getKeysAndRegionsInRange(
257      final byte[] startKey, final byte[] endKey, final boolean includeEndKey)
258      throws IOException {
259    return getKeysAndRegionsInRange(startKey, endKey, includeEndKey, false);
260  }
261
262  /**
263   * Get the corresponding start keys and regions for an arbitrary range of
264   * keys.
265   * <p>
266   * @param startKey Starting row in range, inclusive
267   * @param endKey Ending row in range
268   * @param includeEndKey true if endRow is inclusive, false if exclusive
269   * @param reload true to reload information or false to use cached information
270   * @return A pair of list of start keys and list of HRegionLocations that
271   *         contain the specified range
272   * @throws IOException if a remote or network exception occurs
273   */
274  private Pair<List<byte[]>, List<HRegionLocation>> getKeysAndRegionsInRange(
275      final byte[] startKey, final byte[] endKey, final boolean includeEndKey,
276      final boolean reload) throws IOException {
277    final boolean endKeyIsEndOfTable = Bytes.equals(endKey,HConstants.EMPTY_END_ROW);
278    if ((Bytes.compareTo(startKey, endKey) > 0) && !endKeyIsEndOfTable) {
279      throw new IllegalArgumentException(
280        "Invalid range: " + Bytes.toStringBinary(startKey) +
281        " > " + Bytes.toStringBinary(endKey));
282    }
283    List<byte[]> keysInRange = new ArrayList<>();
284    List<HRegionLocation> regionsInRange = new ArrayList<>();
285    byte[] currentKey = startKey;
286    do {
287      HRegionLocation regionLocation = getRegionLocator().getRegionLocation(currentKey, reload);
288      keysInRange.add(currentKey);
289      regionsInRange.add(regionLocation);
290      currentKey = regionLocation.getRegionInfo().getEndKey();
291    } while (!Bytes.equals(currentKey, HConstants.EMPTY_END_ROW)
292        && (endKeyIsEndOfTable || Bytes.compareTo(currentKey, endKey) < 0
293            || (includeEndKey && Bytes.compareTo(currentKey, endKey) == 0)));
294    return new Pair<>(keysInRange, regionsInRange);
295  }
296
297  /**
298   * The underlying {@link HTable} must not be closed.
299   * {@link Table#getScanner(Scan)} has other usage details.
300   */
301  @Override
302  public ResultScanner getScanner(Scan scan) throws IOException {
303    if (scan.getCaching() <= 0) {
304      scan.setCaching(scannerCaching);
305    }
306    if (scan.getMaxResultSize() <= 0) {
307      scan.setMaxResultSize(scannerMaxResultSize);
308    }
309    if (scan.getMvccReadPoint() > 0) {
310      // it is not supposed to be set by user, clear
311      scan.resetMvccReadPoint();
312    }
313    Boolean async = scan.isAsyncPrefetch();
314    if (async == null) {
315      async = connConfiguration.isClientScannerAsyncPrefetch();
316    }
317
318    if (scan.isReversed()) {
319      return new ReversedClientScanner(getConfiguration(), scan, getName(),
320        this.connection, this.rpcCallerFactory, this.rpcControllerFactory,
321        pool, connConfiguration.getReplicaCallTimeoutMicroSecondScan());
322    } else {
323      if (async) {
324        return new ClientAsyncPrefetchScanner(getConfiguration(), scan, getName(), this.connection,
325            this.rpcCallerFactory, this.rpcControllerFactory,
326            pool, connConfiguration.getReplicaCallTimeoutMicroSecondScan());
327      } else {
328        return new ClientSimpleScanner(getConfiguration(), scan, getName(), this.connection,
329            this.rpcCallerFactory, this.rpcControllerFactory,
330            pool, connConfiguration.getReplicaCallTimeoutMicroSecondScan());
331      }
332    }
333  }
334
335  /**
336   * The underlying {@link HTable} must not be closed.
337   * {@link Table#getScanner(byte[])} has other usage details.
338   */
339  @Override
340  public ResultScanner getScanner(byte [] family) throws IOException {
341    Scan scan = new Scan();
342    scan.addFamily(family);
343    return getScanner(scan);
344  }
345
346  /**
347   * The underlying {@link HTable} must not be closed.
348   * {@link Table#getScanner(byte[], byte[])} has other usage details.
349   */
350  @Override
351  public ResultScanner getScanner(byte [] family, byte [] qualifier)
352  throws IOException {
353    Scan scan = new Scan();
354    scan.addColumn(family, qualifier);
355    return getScanner(scan);
356  }
357
358  @Override
359  public Result get(final Get get) throws IOException {
360    return get(get, get.isCheckExistenceOnly());
361  }
362
363  private Result get(Get get, final boolean checkExistenceOnly) throws IOException {
364    // if we are changing settings to the get, clone it.
365    if (get.isCheckExistenceOnly() != checkExistenceOnly || get.getConsistency() == null) {
366      get = ReflectionUtils.newInstance(get.getClass(), get);
367      get.setCheckExistenceOnly(checkExistenceOnly);
368      if (get.getConsistency() == null){
369        get.setConsistency(DEFAULT_CONSISTENCY);
370      }
371    }
372
373    if (get.getConsistency() == Consistency.STRONG) {
374      final Get configuredGet = get;
375      ClientServiceCallable<Result> callable = new ClientServiceCallable<Result>(this.connection, getName(),
376          get.getRow(), this.rpcControllerFactory.newController(), get.getPriority()) {
377        @Override
378        protected Result rpcCall() throws Exception {
379          ClientProtos.GetRequest request = RequestConverter.buildGetRequest(
380              getLocation().getRegionInfo().getRegionName(), configuredGet);
381          ClientProtos.GetResponse response = doGet(request);
382          return response == null? null:
383            ProtobufUtil.toResult(response.getResult(), getRpcControllerCellScanner());
384        }
385      };
386      return rpcCallerFactory.<Result>newCaller(readRpcTimeoutMs).callWithRetries(callable,
387          this.operationTimeoutMs);
388    }
389
390    // Call that takes into account the replica
391    RpcRetryingCallerWithReadReplicas callable = new RpcRetryingCallerWithReadReplicas(
392        rpcControllerFactory, tableName, this.connection, get, pool,
393        connConfiguration.getRetriesNumber(), operationTimeoutMs, readRpcTimeoutMs,
394        connConfiguration.getPrimaryCallTimeoutMicroSecond());
395    return callable.call(operationTimeoutMs);
396  }
397
398  @Override
399  public Result[] get(List<Get> gets) throws IOException {
400    if (gets.size() == 1) {
401      return new Result[]{get(gets.get(0))};
402    }
403    try {
404      Object[] r1 = new Object[gets.size()];
405      batch((List<? extends Row>)gets, r1, readRpcTimeoutMs);
406      // Translate.
407      Result [] results = new Result[r1.length];
408      int i = 0;
409      for (Object obj: r1) {
410        // Batch ensures if there is a failure we get an exception instead
411        results[i++] = (Result)obj;
412      }
413      return results;
414    } catch (InterruptedException e) {
415      throw (InterruptedIOException)new InterruptedIOException().initCause(e);
416    }
417  }
418
419  @Override
420  public void batch(final List<? extends Row> actions, final Object[] results)
421      throws InterruptedException, IOException {
422    int rpcTimeout = writeRpcTimeoutMs;
423    boolean hasRead = false;
424    boolean hasWrite = false;
425    for (Row action : actions) {
426      if (action instanceof Mutation) {
427        hasWrite = true;
428      } else {
429        hasRead = true;
430      }
431      if (hasRead && hasWrite) {
432        break;
433      }
434    }
435    if (hasRead && !hasWrite) {
436      rpcTimeout = readRpcTimeoutMs;
437    }
438    batch(actions, results, rpcTimeout);
439  }
440
441  public void batch(final List<? extends Row> actions, final Object[] results, int rpcTimeout)
442      throws InterruptedException, IOException {
443    AsyncProcessTask task = AsyncProcessTask.newBuilder()
444            .setPool(pool)
445            .setTableName(tableName)
446            .setRowAccess(actions)
447            .setResults(results)
448            .setRpcTimeout(rpcTimeout)
449            .setOperationTimeout(operationTimeoutMs)
450            .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
451            .build();
452    AsyncRequestFuture ars = multiAp.submit(task);
453    ars.waitUntilDone();
454    if (ars.hasError()) {
455      throw ars.getErrors();
456    }
457  }
458
459  @Override
460  public <R> void batchCallback(
461    final List<? extends Row> actions, final Object[] results, final Batch.Callback<R> callback)
462    throws IOException, InterruptedException {
463    doBatchWithCallback(actions, results, callback, connection, pool, tableName);
464  }
465
466  public static <R> void doBatchWithCallback(List<? extends Row> actions, Object[] results,
467    Callback<R> callback, ClusterConnection connection, ExecutorService pool, TableName tableName)
468    throws InterruptedIOException, RetriesExhaustedWithDetailsException {
469    int operationTimeout = connection.getConnectionConfiguration().getOperationTimeout();
470    int writeTimeout = connection.getConfiguration().getInt(HConstants.HBASE_RPC_WRITE_TIMEOUT_KEY,
471        connection.getConfiguration().getInt(HConstants.HBASE_RPC_TIMEOUT_KEY,
472            HConstants.DEFAULT_HBASE_RPC_TIMEOUT));
473    AsyncProcessTask<R> task = AsyncProcessTask.newBuilder(callback)
474            .setPool(pool)
475            .setTableName(tableName)
476            .setRowAccess(actions)
477            .setResults(results)
478            .setOperationTimeout(operationTimeout)
479            .setRpcTimeout(writeTimeout)
480            .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
481            .build();
482    AsyncRequestFuture ars = connection.getAsyncProcess().submit(task);
483    ars.waitUntilDone();
484    if (ars.hasError()) {
485      throw ars.getErrors();
486    }
487  }
488
489  @Override
490  public void delete(final Delete delete)
491  throws IOException {
492    CancellableRegionServerCallable<SingleResponse> callable =
493        new CancellableRegionServerCallable<SingleResponse>(
494            connection, getName(), delete.getRow(), this.rpcControllerFactory.newController(),
495            writeRpcTimeoutMs, new RetryingTimeTracker().start(), delete.getPriority()) {
496      @Override
497      protected SingleResponse rpcCall() throws Exception {
498        MutateRequest request = RequestConverter.buildMutateRequest(
499          getLocation().getRegionInfo().getRegionName(), delete);
500        MutateResponse response = doMutate(request);
501        return ResponseConverter.getResult(request, response, getRpcControllerCellScanner());
502      }
503    };
504    List<Delete> rows = Collections.singletonList(delete);
505    AsyncProcessTask task = AsyncProcessTask.newBuilder()
506            .setPool(pool)
507            .setTableName(tableName)
508            .setRowAccess(rows)
509            .setCallable(callable)
510            .setRpcTimeout(writeRpcTimeoutMs)
511            .setOperationTimeout(operationTimeoutMs)
512            .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
513            .build();
514    AsyncRequestFuture ars = multiAp.submit(task);
515    ars.waitUntilDone();
516    if (ars.hasError()) {
517      throw ars.getErrors();
518    }
519  }
520
521  @Override
522  public void delete(final List<Delete> deletes)
523  throws IOException {
524    Object[] results = new Object[deletes.size()];
525    try {
526      batch(deletes, results, writeRpcTimeoutMs);
527    } catch (InterruptedException e) {
528      throw (InterruptedIOException)new InterruptedIOException().initCause(e);
529    } finally {
530      // TODO: to be consistent with batch put(), do not modify input list
531      // mutate list so that it is empty for complete success, or contains only failed records
532      // results are returned in the same order as the requests in list walk the list backwards,
533      // so we can remove from list without impacting the indexes of earlier members
534      for (int i = results.length - 1; i>=0; i--) {
535        // if result is not null, it succeeded
536        if (results[i] instanceof Result) {
537          deletes.remove(i);
538        }
539      }
540    }
541  }
542
543  @Override
544  public void put(final Put put) throws IOException {
545    validatePut(put);
546    ClientServiceCallable<Void> callable =
547        new ClientServiceCallable<Void>(this.connection, getName(), put.getRow(),
548            this.rpcControllerFactory.newController(), put.getPriority()) {
549      @Override
550      protected Void rpcCall() throws Exception {
551        MutateRequest request =
552            RequestConverter.buildMutateRequest(getLocation().getRegionInfo().getRegionName(), put);
553        doMutate(request);
554        return null;
555      }
556    };
557    rpcCallerFactory.<Void> newCaller(this.writeRpcTimeoutMs).callWithRetries(callable,
558        this.operationTimeoutMs);
559  }
560
561  @Override
562  public void put(final List<Put> puts) throws IOException {
563    for (Put put : puts) {
564      validatePut(put);
565    }
566    Object[] results = new Object[puts.size()];
567    try {
568      batch(puts, results, writeRpcTimeoutMs);
569    } catch (InterruptedException e) {
570      throw (InterruptedIOException) new InterruptedIOException().initCause(e);
571    }
572  }
573
574  @Override
575  public void mutateRow(final RowMutations rm) throws IOException {
576    CancellableRegionServerCallable<MultiResponse> callable =
577      new CancellableRegionServerCallable<MultiResponse>(this.connection, getName(), rm.getRow(),
578          rpcControllerFactory.newController(), writeRpcTimeoutMs,
579          new RetryingTimeTracker().start(), rm.getMaxPriority()) {
580      @Override
581      protected MultiResponse rpcCall() throws Exception {
582        RegionAction.Builder regionMutationBuilder = RequestConverter.buildRegionAction(
583            getLocation().getRegionInfo().getRegionName(), rm);
584        regionMutationBuilder.setAtomic(true);
585        MultiRequest request =
586            MultiRequest.newBuilder().addRegionAction(regionMutationBuilder.build()).build();
587        ClientProtos.MultiResponse response = doMulti(request);
588        ClientProtos.RegionActionResult res = response.getRegionActionResultList().get(0);
589        if (res.hasException()) {
590          Throwable ex = ProtobufUtil.toException(res.getException());
591          if (ex instanceof IOException) {
592            throw (IOException) ex;
593          }
594          throw new IOException("Failed to mutate row: " + Bytes.toStringBinary(rm.getRow()), ex);
595        }
596        return ResponseConverter.getResults(request, response, getRpcControllerCellScanner());
597      }
598    };
599    AsyncProcessTask task = AsyncProcessTask.newBuilder()
600            .setPool(pool)
601            .setTableName(tableName)
602            .setRowAccess(rm.getMutations())
603            .setCallable(callable)
604            .setRpcTimeout(writeRpcTimeoutMs)
605            .setOperationTimeout(operationTimeoutMs)
606            .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
607            .build();
608    AsyncRequestFuture ars = multiAp.submit(task);
609    ars.waitUntilDone();
610    if (ars.hasError()) {
611      throw ars.getErrors();
612    }
613  }
614
615  @Override
616  public Result append(final Append append) throws IOException {
617    checkHasFamilies(append);
618    NoncedRegionServerCallable<Result> callable =
619        new NoncedRegionServerCallable<Result>(this.connection, getName(), append.getRow(),
620            this.rpcControllerFactory.newController(), append.getPriority()) {
621      @Override
622      protected Result rpcCall() throws Exception {
623        MutateRequest request = RequestConverter.buildMutateRequest(
624          getLocation().getRegionInfo().getRegionName(), append, getNonceGroup(), getNonce());
625        MutateResponse response = doMutate(request);
626        if (!response.hasResult()) return null;
627        return ProtobufUtil.toResult(response.getResult(), getRpcControllerCellScanner());
628      }
629    };
630    return rpcCallerFactory.<Result> newCaller(this.writeRpcTimeoutMs).
631        callWithRetries(callable, this.operationTimeoutMs);
632  }
633
634  @Override
635  public Result increment(final Increment increment) throws IOException {
636    checkHasFamilies(increment);
637    NoncedRegionServerCallable<Result> callable =
638        new NoncedRegionServerCallable<Result>(this.connection, getName(), increment.getRow(),
639            this.rpcControllerFactory.newController(), increment.getPriority()) {
640      @Override
641      protected Result rpcCall() throws Exception {
642        MutateRequest request = RequestConverter.buildMutateRequest(
643          getLocation().getRegionInfo().getRegionName(), increment, getNonceGroup(), getNonce());
644        MutateResponse response = doMutate(request);
645        // Should this check for null like append does?
646        return ProtobufUtil.toResult(response.getResult(), getRpcControllerCellScanner());
647      }
648    };
649    return rpcCallerFactory.<Result> newCaller(writeRpcTimeoutMs).callWithRetries(callable,
650        this.operationTimeoutMs);
651  }
652
653  @Override
654  public long incrementColumnValue(final byte [] row, final byte [] family,
655      final byte [] qualifier, final long amount)
656  throws IOException {
657    return incrementColumnValue(row, family, qualifier, amount, Durability.SYNC_WAL);
658  }
659
660  @Override
661  public long incrementColumnValue(final byte [] row, final byte [] family,
662      final byte [] qualifier, final long amount, final Durability durability)
663  throws IOException {
664    NullPointerException npe = null;
665    if (row == null) {
666      npe = new NullPointerException("row is null");
667    } else if (family == null) {
668      npe = new NullPointerException("family is null");
669    }
670    if (npe != null) {
671      throw new IOException(
672          "Invalid arguments to incrementColumnValue", npe);
673    }
674
675    NoncedRegionServerCallable<Long> callable =
676        new NoncedRegionServerCallable<Long>(this.connection, getName(), row,
677            this.rpcControllerFactory.newController(), HConstants.PRIORITY_UNSET) {
678      @Override
679      protected Long rpcCall() throws Exception {
680        MutateRequest request = RequestConverter.buildIncrementRequest(
681          getLocation().getRegionInfo().getRegionName(), row, family,
682          qualifier, amount, durability, getNonceGroup(), getNonce());
683        MutateResponse response = doMutate(request);
684        Result result = ProtobufUtil.toResult(response.getResult(), getRpcControllerCellScanner());
685        return Long.valueOf(Bytes.toLong(result.getValue(family, qualifier)));
686      }
687    };
688    return rpcCallerFactory.<Long> newCaller(this.writeRpcTimeoutMs).
689        callWithRetries(callable, this.operationTimeoutMs);
690  }
691
692  @Override
693  @Deprecated
694  public boolean checkAndPut(final byte [] row, final byte [] family, final byte [] qualifier,
695      final byte [] value, final Put put) throws IOException {
696    return doCheckAndPut(row, family, qualifier, CompareOperator.EQUAL.name(), value, null, put);
697  }
698
699  @Override
700  @Deprecated
701  public boolean checkAndPut(final byte [] row, final byte [] family, final byte [] qualifier,
702      final CompareOp compareOp, final byte [] value, final Put put) throws IOException {
703    return doCheckAndPut(row, family, qualifier, compareOp.name(), value, null, put);
704  }
705
706  @Override
707  @Deprecated
708  public boolean checkAndPut(final byte [] row, final byte [] family, final byte [] qualifier,
709      final CompareOperator op, final byte [] value, final Put put) throws IOException {
710    // The name of the operators in CompareOperator are intentionally those of the
711    // operators in the filter's CompareOp enum.
712    return doCheckAndPut(row, family, qualifier, op.name(), value, null, put);
713  }
714
715  private boolean doCheckAndPut(final byte[] row, final byte[] family, final byte[] qualifier,
716    final String opName, final byte[] value, final TimeRange timeRange, final Put put)
717    throws IOException {
718    ClientServiceCallable<Boolean> callable =
719        new ClientServiceCallable<Boolean>(this.connection, getName(), row,
720            this.rpcControllerFactory.newController(), put.getPriority()) {
721      @Override
722      protected Boolean rpcCall() throws Exception {
723        CompareType compareType = CompareType.valueOf(opName);
724        MutateRequest request = RequestConverter.buildMutateRequest(
725            getLocation().getRegionInfo().getRegionName(), row, family, qualifier,
726            new BinaryComparator(value), compareType, timeRange, put);
727        MutateResponse response = doMutate(request);
728        return Boolean.valueOf(response.getProcessed());
729      }
730    };
731    return rpcCallerFactory.<Boolean> newCaller(this.writeRpcTimeoutMs)
732        .callWithRetries(callable, this.operationTimeoutMs);
733  }
734
735  @Override
736  @Deprecated
737  public boolean checkAndDelete(final byte[] row, final byte[] family, final byte[] qualifier,
738    final byte[] value, final Delete delete) throws IOException {
739    return doCheckAndDelete(row, family, qualifier, CompareOperator.EQUAL.name(), value, null,
740      delete);
741  }
742
743  @Override
744  @Deprecated
745  public boolean checkAndDelete(final byte[] row, final byte[] family, final byte[] qualifier,
746    final CompareOp compareOp, final byte[] value, final Delete delete) throws IOException {
747    return doCheckAndDelete(row, family, qualifier, compareOp.name(), value, null, delete);
748  }
749
750  @Override
751  @Deprecated
752  public boolean checkAndDelete(final byte[] row, final byte[] family, final byte[] qualifier,
753    final CompareOperator op, final byte[] value, final Delete delete) throws IOException {
754    return doCheckAndDelete(row, family, qualifier, op.name(), value, null, delete);
755  }
756
757  private boolean doCheckAndDelete(final byte[] row, final byte[] family, final byte[] qualifier,
758    final String opName, final byte[] value, final TimeRange timeRange, final Delete delete)
759    throws IOException {
760    CancellableRegionServerCallable<SingleResponse> callable =
761      new CancellableRegionServerCallable<SingleResponse>(this.connection, getName(), row,
762        this.rpcControllerFactory.newController(), writeRpcTimeoutMs,
763        new RetryingTimeTracker().start(), delete.getPriority()) {
764        @Override
765        protected SingleResponse rpcCall() throws Exception {
766          CompareType compareType = CompareType.valueOf(opName);
767          MutateRequest request = RequestConverter
768            .buildMutateRequest(getLocation().getRegionInfo().getRegionName(), row, family,
769              qualifier, new BinaryComparator(value), compareType, timeRange, delete);
770          MutateResponse response = doMutate(request);
771          return ResponseConverter.getResult(request, response, getRpcControllerCellScanner());
772        }
773      };
774    List<Delete> rows = Collections.singletonList(delete);
775    Object[] results = new Object[1];
776    AsyncProcessTask task =
777      AsyncProcessTask.newBuilder().setPool(pool).setTableName(tableName).setRowAccess(rows)
778        .setCallable(callable)
779        // TODO any better timeout?
780        .setRpcTimeout(Math.max(readRpcTimeoutMs, writeRpcTimeoutMs))
781        .setOperationTimeout(operationTimeoutMs)
782        .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL).setResults(results).build();
783    AsyncRequestFuture ars = multiAp.submit(task);
784    ars.waitUntilDone();
785    if (ars.hasError()) {
786      throw ars.getErrors();
787    }
788    return ((SingleResponse.Entry) results[0]).isProcessed();
789  }
790
791  @Override
792  public CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) {
793    return new CheckAndMutateBuilderImpl(row, family);
794  }
795
796  private boolean doCheckAndMutate(final byte[] row, final byte[] family, final byte[] qualifier,
797    final String opName, final byte[] value, final TimeRange timeRange, final RowMutations rm)
798    throws IOException {
799    CancellableRegionServerCallable<MultiResponse> callable =
800    new CancellableRegionServerCallable<MultiResponse>(connection, getName(), rm.getRow(),
801    rpcControllerFactory.newController(), writeRpcTimeoutMs, new RetryingTimeTracker().start(),
802        rm.getMaxPriority()) {
803      @Override
804      protected MultiResponse rpcCall() throws Exception {
805        CompareType compareType = CompareType.valueOf(opName);
806        MultiRequest request = RequestConverter
807          .buildMutateRequest(getLocation().getRegionInfo().getRegionName(), row, family, qualifier,
808            new BinaryComparator(value), compareType, timeRange, rm);
809        ClientProtos.MultiResponse response = doMulti(request);
810        ClientProtos.RegionActionResult res = response.getRegionActionResultList().get(0);
811        if (res.hasException()) {
812          Throwable ex = ProtobufUtil.toException(res.getException());
813          if (ex instanceof IOException) {
814            throw (IOException) ex;
815          }
816          throw new IOException(
817            "Failed to checkAndMutate row: " + Bytes.toStringBinary(rm.getRow()), ex);
818        }
819        return ResponseConverter.getResults(request, response, getRpcControllerCellScanner());
820      }
821    };
822
823    /**
824     *  Currently, we use one array to store 'processed' flag which is returned by server.
825     *  It is excessive to send such a large array, but that is required by the framework right now
826     * */
827    Object[] results = new Object[rm.getMutations().size()];
828    AsyncProcessTask task = AsyncProcessTask.newBuilder()
829    .setPool(pool)
830    .setTableName(tableName)
831    .setRowAccess(rm.getMutations())
832    .setResults(results)
833    .setCallable(callable)
834    // TODO any better timeout?
835    .setRpcTimeout(Math.max(readRpcTimeoutMs, writeRpcTimeoutMs))
836    .setOperationTimeout(operationTimeoutMs)
837    .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
838    .build();
839    AsyncRequestFuture ars = multiAp.submit(task);
840    ars.waitUntilDone();
841    if (ars.hasError()) {
842      throw ars.getErrors();
843    }
844
845    return ((Result)results[0]).getExists();
846  }
847
848  @Override
849  @Deprecated
850  public boolean checkAndMutate(final byte [] row, final byte [] family, final byte [] qualifier,
851    final CompareOp compareOp, final byte [] value, final RowMutations rm)
852  throws IOException {
853    return doCheckAndMutate(row, family, qualifier, compareOp.name(), value, null, rm);
854  }
855
856  @Override
857  @Deprecated
858  public boolean checkAndMutate(final byte [] row, final byte [] family, final byte [] qualifier,
859      final CompareOperator op, final byte [] value, final RowMutations rm) throws IOException {
860    return doCheckAndMutate(row, family, qualifier, op.name(), value, null, rm);
861  }
862
863  @Override
864  public boolean exists(final Get get) throws IOException {
865    Result r = get(get, true);
866    assert r.getExists() != null;
867    return r.getExists();
868  }
869
870  @Override
871  public boolean[] exists(List<Get> gets) throws IOException {
872    if (gets.isEmpty()) return new boolean[]{};
873    if (gets.size() == 1) return new boolean[]{exists(gets.get(0))};
874
875    ArrayList<Get> exists = new ArrayList<>(gets.size());
876    for (Get g: gets){
877      Get ge = new Get(g);
878      ge.setCheckExistenceOnly(true);
879      exists.add(ge);
880    }
881
882    Object[] r1= new Object[exists.size()];
883    try {
884      batch(exists, r1, readRpcTimeoutMs);
885    } catch (InterruptedException e) {
886      throw (InterruptedIOException)new InterruptedIOException().initCause(e);
887    }
888
889    // translate.
890    boolean[] results = new boolean[r1.length];
891    int i = 0;
892    for (Object o : r1) {
893      // batch ensures if there is a failure we get an exception instead
894      results[i++] = ((Result)o).getExists();
895    }
896
897    return results;
898  }
899
900  /**
901   * Process a mixed batch of Get, Put and Delete actions. All actions for a
902   * RegionServer are forwarded in one RPC call. Queries are executed in parallel.
903   *
904   * @param list The collection of actions.
905   * @param results An empty array, same size as list. If an exception is thrown,
906   *   you can test here for partial results, and to determine which actions
907   *   processed successfully.
908   * @throws IOException if there are problems talking to META. Per-item
909   *   exceptions are stored in the results array.
910   */
911  public <R> void processBatchCallback(
912    final List<? extends Row> list, final Object[] results, final Batch.Callback<R> callback)
913    throws IOException, InterruptedException {
914    this.batchCallback(list, results, callback);
915  }
916
917  @Override
918  public void close() throws IOException {
919    if (this.closed) {
920      return;
921    }
922    if (cleanupPoolOnClose) {
923      this.pool.shutdown();
924      try {
925        boolean terminated = false;
926        do {
927          // wait until the pool has terminated
928          terminated = this.pool.awaitTermination(60, TimeUnit.SECONDS);
929        } while (!terminated);
930      } catch (InterruptedException e) {
931        this.pool.shutdownNow();
932        LOG.warn("waitForTermination interrupted");
933      }
934    }
935    this.closed = true;
936  }
937
938  // validate for well-formedness
939  public void validatePut(final Put put) throws IllegalArgumentException {
940    validatePut(put, connConfiguration.getMaxKeyValueSize());
941  }
942
943  // validate for well-formedness
944  public static void validatePut(Put put, int maxKeyValueSize) throws IllegalArgumentException {
945    if (put.isEmpty()) {
946      throw new IllegalArgumentException("No columns to insert");
947    }
948    if (maxKeyValueSize > 0) {
949      for (List<Cell> list : put.getFamilyCellMap().values()) {
950        for (Cell cell : list) {
951          if (KeyValueUtil.length(cell) > maxKeyValueSize) {
952            throw new IllegalArgumentException("KeyValue size too large");
953          }
954        }
955      }
956    }
957  }
958
959  /**
960   * The pool is used for mutli requests for this HTable
961   * @return the pool used for mutli
962   */
963  ExecutorService getPool() {
964    return this.pool;
965  }
966
967  /**
968   * Explicitly clears the region cache to fetch the latest value from META.
969   * This is a power user function: avoid unless you know the ramifications.
970   */
971  public void clearRegionCache() {
972    this.connection.clearRegionCache();
973  }
974
975  @Override
976  public CoprocessorRpcChannel coprocessorService(byte[] row) {
977    return new RegionCoprocessorRpcChannel(connection, tableName, row);
978  }
979
980  @Override
981  public <T extends Service, R> Map<byte[],R> coprocessorService(final Class<T> service,
982      byte[] startKey, byte[] endKey, final Batch.Call<T,R> callable)
983      throws ServiceException, Throwable {
984    final Map<byte[],R> results =  Collections.synchronizedMap(
985        new TreeMap<byte[], R>(Bytes.BYTES_COMPARATOR));
986    coprocessorService(service, startKey, endKey, callable, new Batch.Callback<R>() {
987      @Override
988      public void update(byte[] region, byte[] row, R value) {
989        if (region != null) {
990          results.put(region, value);
991        }
992      }
993    });
994    return results;
995  }
996
997  @Override
998  public <T extends Service, R> void coprocessorService(final Class<T> service,
999      byte[] startKey, byte[] endKey, final Batch.Call<T,R> callable,
1000      final Batch.Callback<R> callback) throws ServiceException, Throwable {
1001    // get regions covered by the row range
1002    List<byte[]> keys = getStartKeysInRange(startKey, endKey);
1003    Map<byte[],Future<R>> futures = new TreeMap<>(Bytes.BYTES_COMPARATOR);
1004    for (final byte[] r : keys) {
1005      final RegionCoprocessorRpcChannel channel =
1006          new RegionCoprocessorRpcChannel(connection, tableName, r);
1007      Future<R> future = pool.submit(new Callable<R>() {
1008        @Override
1009        public R call() throws Exception {
1010          T instance =
1011              org.apache.hadoop.hbase.protobuf.ProtobufUtil.newServiceStub(service, channel);
1012          R result = callable.call(instance);
1013          byte[] region = channel.getLastRegion();
1014          if (callback != null) {
1015            callback.update(region, r, result);
1016          }
1017          return result;
1018        }
1019      });
1020      futures.put(r, future);
1021    }
1022    for (Map.Entry<byte[],Future<R>> e : futures.entrySet()) {
1023      try {
1024        e.getValue().get();
1025      } catch (ExecutionException ee) {
1026        LOG.warn("Error calling coprocessor service " + service.getName() + " for row "
1027            + Bytes.toStringBinary(e.getKey()), ee);
1028        throw ee.getCause();
1029      } catch (InterruptedException ie) {
1030        throw new InterruptedIOException("Interrupted calling coprocessor service "
1031            + service.getName() + " for row " + Bytes.toStringBinary(e.getKey())).initCause(ie);
1032      }
1033    }
1034  }
1035
1036  private List<byte[]> getStartKeysInRange(byte[] start, byte[] end)
1037  throws IOException {
1038    if (start == null) {
1039      start = HConstants.EMPTY_START_ROW;
1040    }
1041    if (end == null) {
1042      end = HConstants.EMPTY_END_ROW;
1043    }
1044    return getKeysAndRegionsInRange(start, end, true).getFirst();
1045  }
1046
1047  @Override
1048  public long getRpcTimeout(TimeUnit unit) {
1049    return unit.convert(rpcTimeoutMs, TimeUnit.MILLISECONDS);
1050  }
1051
1052  @Override
1053  @Deprecated
1054  public int getRpcTimeout() {
1055    return rpcTimeoutMs;
1056  }
1057
1058  @Override
1059  @Deprecated
1060  public void setRpcTimeout(int rpcTimeout) {
1061    setReadRpcTimeout(rpcTimeout);
1062    setWriteRpcTimeout(rpcTimeout);
1063  }
1064
1065  @Override
1066  public long getReadRpcTimeout(TimeUnit unit) {
1067    return unit.convert(readRpcTimeoutMs, TimeUnit.MILLISECONDS);
1068  }
1069
1070  @Override
1071  @Deprecated
1072  public int getReadRpcTimeout() {
1073    return readRpcTimeoutMs;
1074  }
1075
1076  @Override
1077  @Deprecated
1078  public void setReadRpcTimeout(int readRpcTimeout) {
1079    this.readRpcTimeoutMs = readRpcTimeout;
1080  }
1081
1082  @Override
1083  public long getWriteRpcTimeout(TimeUnit unit) {
1084    return unit.convert(writeRpcTimeoutMs, TimeUnit.MILLISECONDS);
1085  }
1086
1087  @Override
1088  @Deprecated
1089  public int getWriteRpcTimeout() {
1090    return writeRpcTimeoutMs;
1091  }
1092
1093  @Override
1094  @Deprecated
1095  public void setWriteRpcTimeout(int writeRpcTimeout) {
1096    this.writeRpcTimeoutMs = writeRpcTimeout;
1097  }
1098
1099  @Override
1100  public long getOperationTimeout(TimeUnit unit) {
1101    return unit.convert(operationTimeoutMs, TimeUnit.MILLISECONDS);
1102  }
1103
1104  @Override
1105  @Deprecated
1106  public int getOperationTimeout() {
1107    return operationTimeoutMs;
1108  }
1109
1110  @Override
1111  @Deprecated
1112  public void setOperationTimeout(int operationTimeout) {
1113    this.operationTimeoutMs = operationTimeout;
1114  }
1115
1116  @Override
1117  public String toString() {
1118    return tableName + ";" + connection;
1119  }
1120
1121  @Override
1122  public <R extends Message> Map<byte[], R> batchCoprocessorService(
1123      Descriptors.MethodDescriptor methodDescriptor, Message request,
1124      byte[] startKey, byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
1125    final Map<byte[], R> results = Collections.synchronizedMap(new TreeMap<byte[], R>(
1126        Bytes.BYTES_COMPARATOR));
1127    batchCoprocessorService(methodDescriptor, request, startKey, endKey, responsePrototype,
1128        new Callback<R>() {
1129      @Override
1130      public void update(byte[] region, byte[] row, R result) {
1131        if (region != null) {
1132          results.put(region, result);
1133        }
1134      }
1135    });
1136    return results;
1137  }
1138
1139  @Override
1140  public <R extends Message> void batchCoprocessorService(
1141      final Descriptors.MethodDescriptor methodDescriptor, final Message request,
1142      byte[] startKey, byte[] endKey, final R responsePrototype, final Callback<R> callback)
1143      throws ServiceException, Throwable {
1144
1145    if (startKey == null) {
1146      startKey = HConstants.EMPTY_START_ROW;
1147    }
1148    if (endKey == null) {
1149      endKey = HConstants.EMPTY_END_ROW;
1150    }
1151    // get regions covered by the row range
1152    Pair<List<byte[]>, List<HRegionLocation>> keysAndRegions =
1153        getKeysAndRegionsInRange(startKey, endKey, true);
1154    List<byte[]> keys = keysAndRegions.getFirst();
1155    List<HRegionLocation> regions = keysAndRegions.getSecond();
1156
1157    // check if we have any calls to make
1158    if (keys.isEmpty()) {
1159      LOG.info("No regions were selected by key range start=" + Bytes.toStringBinary(startKey) +
1160          ", end=" + Bytes.toStringBinary(endKey));
1161      return;
1162    }
1163
1164    List<RegionCoprocessorServiceExec> execs = new ArrayList<>(keys.size());
1165    final Map<byte[], RegionCoprocessorServiceExec> execsByRow = new TreeMap<>(Bytes.BYTES_COMPARATOR);
1166    for (int i = 0; i < keys.size(); i++) {
1167      final byte[] rowKey = keys.get(i);
1168      final byte[] region = regions.get(i).getRegionInfo().getRegionName();
1169      RegionCoprocessorServiceExec exec =
1170          new RegionCoprocessorServiceExec(region, rowKey, methodDescriptor, request);
1171      execs.add(exec);
1172      execsByRow.put(rowKey, exec);
1173    }
1174
1175    // tracking for any possible deserialization errors on success callback
1176    // TODO: it would be better to be able to reuse AsyncProcess.BatchErrors here
1177    final List<Throwable> callbackErrorExceptions = new ArrayList<>();
1178    final List<Row> callbackErrorActions = new ArrayList<>();
1179    final List<String> callbackErrorServers = new ArrayList<>();
1180    Object[] results = new Object[execs.size()];
1181
1182    AsyncProcess asyncProcess = new AsyncProcess(connection, configuration,
1183        RpcRetryingCallerFactory.instantiate(configuration, connection.getStatisticsTracker()),
1184        RpcControllerFactory.instantiate(configuration));
1185
1186    Callback<ClientProtos.CoprocessorServiceResult> resultsCallback
1187    = (byte[] region, byte[] row, ClientProtos.CoprocessorServiceResult serviceResult) -> {
1188      if (LOG.isTraceEnabled()) {
1189        LOG.trace("Received result for endpoint " + methodDescriptor.getFullName() +
1190            ": region=" + Bytes.toStringBinary(region) +
1191            ", row=" + Bytes.toStringBinary(row) +
1192            ", value=" + serviceResult.getValue().getValue());
1193      }
1194      try {
1195        Message.Builder builder = responsePrototype.newBuilderForType();
1196        org.apache.hadoop.hbase.protobuf.ProtobufUtil.mergeFrom(builder,
1197            serviceResult.getValue().getValue().toByteArray());
1198        callback.update(region, row, (R) builder.build());
1199      } catch (IOException e) {
1200        LOG.error("Unexpected response type from endpoint " + methodDescriptor.getFullName(),
1201            e);
1202        callbackErrorExceptions.add(e);
1203        callbackErrorActions.add(execsByRow.get(row));
1204        callbackErrorServers.add("null");
1205      }
1206    };
1207    AsyncProcessTask<ClientProtos.CoprocessorServiceResult> task =
1208        AsyncProcessTask.newBuilder(resultsCallback)
1209            .setPool(pool)
1210            .setTableName(tableName)
1211            .setRowAccess(execs)
1212            .setResults(results)
1213            .setRpcTimeout(readRpcTimeoutMs)
1214            .setOperationTimeout(operationTimeoutMs)
1215            .setSubmittedRows(AsyncProcessTask.SubmittedRows.ALL)
1216            .build();
1217    AsyncRequestFuture future = asyncProcess.submit(task);
1218    future.waitUntilDone();
1219
1220    if (future.hasError()) {
1221      throw future.getErrors();
1222    } else if (!callbackErrorExceptions.isEmpty()) {
1223      throw new RetriesExhaustedWithDetailsException(callbackErrorExceptions, callbackErrorActions,
1224          callbackErrorServers);
1225    }
1226  }
1227
1228  public RegionLocator getRegionLocator() {
1229    return this.locator;
1230  }
1231
1232  private class CheckAndMutateBuilderImpl implements CheckAndMutateBuilder {
1233
1234    private final byte[] row;
1235    private final byte[] family;
1236    private byte[] qualifier;
1237    private TimeRange timeRange;
1238    private CompareOperator op;
1239    private byte[] value;
1240
1241    CheckAndMutateBuilderImpl(byte[] row, byte[] family) {
1242      this.row = Preconditions.checkNotNull(row, "row is null");
1243      this.family = Preconditions.checkNotNull(family, "family is null");
1244    }
1245
1246    @Override
1247    public CheckAndMutateBuilder qualifier(byte[] qualifier) {
1248      this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null. Consider using" +
1249          " an empty byte array, or just do not call this method if you want a null qualifier");
1250      return this;
1251    }
1252
1253    @Override
1254    public CheckAndMutateBuilder timeRange(TimeRange timeRange) {
1255      this.timeRange = timeRange;
1256      return this;
1257    }
1258
1259    @Override
1260    public CheckAndMutateBuilder ifNotExists() {
1261      this.op = CompareOperator.EQUAL;
1262      this.value = null;
1263      return this;
1264    }
1265
1266    @Override
1267    public CheckAndMutateBuilder ifMatches(CompareOperator compareOp, byte[] value) {
1268      this.op = Preconditions.checkNotNull(compareOp, "compareOp is null");
1269      this.value = Preconditions.checkNotNull(value, "value is null");
1270      return this;
1271    }
1272
1273    private void preCheck() {
1274      Preconditions.checkNotNull(op, "condition is null. You need to specify the condition by" +
1275          " calling ifNotExists/ifEquals/ifMatches before executing the request");
1276    }
1277
1278    @Override
1279    public boolean thenPut(Put put) throws IOException {
1280      preCheck();
1281      return doCheckAndPut(row, family, qualifier, op.name(), value, timeRange, put);
1282    }
1283
1284    @Override
1285    public boolean thenDelete(Delete delete) throws IOException {
1286      preCheck();
1287      return doCheckAndDelete(row, family, qualifier, op.name(), value, timeRange, delete);
1288    }
1289
1290    @Override
1291    public boolean thenMutate(RowMutations mutation) throws IOException {
1292      preCheck();
1293      return doCheckAndMutate(row, family, qualifier, op.name(), value, timeRange, mutation);
1294    }
1295  }
1296}