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
021import com.google.protobuf.Descriptors;
022import com.google.protobuf.Message;
023import com.google.protobuf.Service;
024import com.google.protobuf.ServiceException;
025import java.io.Closeable;
026import java.io.IOException;
027import java.util.Collections;
028import java.util.List;
029import java.util.Map;
030import java.util.TreeMap;
031import java.util.concurrent.TimeUnit;
032import org.apache.commons.lang3.NotImplementedException;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.Cell;
035import org.apache.hadoop.hbase.CompareOperator;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.coprocessor.Batch;
038import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
039import org.apache.hadoop.hbase.io.TimeRange;
040import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
041import org.apache.hadoop.hbase.util.Bytes;
042import org.apache.yetus.audience.InterfaceAudience;
043
044/**
045 * Used to communicate with a single HBase table.
046 * Obtain an instance from a {@link Connection} and call {@link #close()} afterwards.
047 *
048 * <p><code>Table</code> can be used to get, put, delete or scan data from a table.
049 * @see ConnectionFactory
050 * @see Connection
051 * @see Admin
052 * @see RegionLocator
053 * @since 0.99.0
054 */
055@InterfaceAudience.Public
056public interface Table extends Closeable {
057  /**
058   * Gets the fully qualified table name instance of this table.
059   */
060  TableName getName();
061
062  /**
063   * Returns the {@link org.apache.hadoop.conf.Configuration} object used by this instance.
064   * <p>
065   * The reference returned is not a copy, so any change made to it will
066   * affect this instance.
067   */
068  Configuration getConfiguration();
069
070  /**
071   * Gets the {@link org.apache.hadoop.hbase.client.TableDescriptor table descriptor} for this table.
072   * @throws java.io.IOException if a remote or network exception occurs.
073   */
074  TableDescriptor getDescriptor() throws IOException;
075
076  /**
077   * Gets the {@link RegionLocator} for this table.
078   */
079  RegionLocator getRegionLocator() throws IOException;
080
081  /**
082   * Test for the existence of columns in the table, as specified by the Get.
083   * <p>
084   *
085   * This will return true if the Get matches one or more keys, false if not.
086   * <p>
087   *
088   * This is a server-side call so it prevents any data from being transfered to
089   * the client.
090   *
091   * @param get the Get
092   * @return true if the specified Get matches one or more keys, false if not
093   * @throws IOException e
094   */
095  default boolean exists(Get get) throws IOException {
096    return exists(Collections.singletonList(get))[0];
097  }
098
099  /**
100   * Test for the existence of columns in the table, as specified by the Gets.
101   * <p>
102   *
103   * This will return an array of booleans. Each value will be true if the related Get matches
104   * one or more keys, false if not.
105   * <p>
106   *
107   * This is a server-side call so it prevents any data from being transferred to
108   * the client.
109   *
110   * @param gets the Gets
111   * @return Array of boolean.  True if the specified Get matches one or more keys, false if not.
112   * @throws IOException e
113   */
114  default boolean[] exists(List<Get> gets) throws IOException {
115    throw new NotImplementedException("Add an implementation!");
116  }
117
118  /**
119   * Method that does a batch call on Deletes, Gets, Puts, Increments, Appends, RowMutations.
120   * The ordering of execution of the actions is not defined. Meaning if you do a Put and a
121   * Get in the same {@link #batch} call, you will not necessarily be
122   * guaranteed that the Get returns what the Put had put.
123   *
124   * @param actions list of Get, Put, Delete, Increment, Append, RowMutations.
125   * @param results Empty Object[], same size as actions. Provides access to partial
126   *                results, in case an exception is thrown. A null in the result array means that
127   *                the call for that action failed, even after retries. The order of the objects
128   *                in the results array corresponds to the order of actions in the request list.
129   * @throws IOException
130   * @since 0.90.0
131   */
132  default void batch(final List<? extends Row> actions, final Object[] results) throws IOException,
133    InterruptedException {
134    throw new NotImplementedException("Add an implementation!");
135  }
136
137  /**
138   * Same as {@link #batch(List, Object[])}, but with a callback.
139   * @since 0.96.0
140   * @deprecated since 3.0.0, will removed in 4.0.0. Please use the batch related methods in
141   *             {@link AsyncTable} directly if you want to use callback. We reuse the callback for
142   *             coprocessor here, and the problem is that for batch operation, the
143   *             {@link AsyncTable} does not tell us the region, so in this method we need an extra
144   *             locating after we get the result, which is not good.
145   */
146  @Deprecated
147  default <R> void batchCallback(final List<? extends Row> actions, final Object[] results,
148      final Batch.Callback<R> callback) throws IOException, InterruptedException {
149    throw new NotImplementedException("Add an implementation!");
150  }
151
152  /**
153   * Extracts certain cells from a given row.
154   * @param get The object that specifies what data to fetch and from which row.
155   * @return The data coming from the specified row, if it exists.  If the row
156   *   specified doesn't exist, the {@link Result} instance returned won't
157   *   contain any {@link org.apache.hadoop.hbase.KeyValue}, as indicated by
158   *   {@link Result#isEmpty()}.
159   * @throws IOException if a remote or network exception occurs.
160   * @since 0.20.0
161   */
162  default Result get(Get get) throws IOException {
163    return get(Collections.singletonList(get))[0];
164  }
165
166  /**
167   * Extracts specified cells from the given rows, as a batch.
168   *
169   * @param gets The objects that specify what data to fetch and from which rows.
170   * @return The data coming from the specified rows, if it exists.  If the row specified doesn't
171   *   exist, the {@link Result} instance returned won't contain any
172   *   {@link org.apache.hadoop.hbase.Cell}s, as indicated by {@link Result#isEmpty()}. If there
173   *   are any failures even after retries, there will be a <code>null</code> in the results' array
174   *   for  those Gets, AND an exception will be thrown. The ordering of the Result array
175   *   corresponds to  the order of the list of passed in Gets.
176   * @throws IOException if a remote or network exception occurs.
177   * @since 0.90.0
178   * @apiNote {@link #put(List)} runs pre-flight validations on the input list on client.
179   *          Currently {@link #get(List)} doesn't run any validations on the client-side,
180   *          currently there is no need, but this may change in the future. An
181   *          {@link IllegalArgumentException} will be thrown in this case.
182   */
183  default Result[] get(List<Get> gets) throws IOException {
184    throw new NotImplementedException("Add an implementation!");
185  }
186
187  /**
188   * Returns a scanner on the current table as specified by the {@link Scan}
189   * object.
190   * Note that the passed {@link Scan}'s start row and caching properties
191   * maybe changed.
192   *
193   * @param scan A configured {@link Scan} object.
194   * @return A scanner.
195   * @throws IOException if a remote or network exception occurs.
196   * @since 0.20.0
197   */
198  default ResultScanner getScanner(Scan scan) throws IOException {
199    throw new NotImplementedException("Add an implementation!");
200  }
201
202  /**
203   * Gets a scanner on the current table for the given family.
204   *
205   * @param family The column family to scan.
206   * @return A scanner.
207   * @throws IOException if a remote or network exception occurs.
208   * @since 0.20.0
209   */
210  default ResultScanner getScanner(byte[] family) throws IOException {
211    throw new NotImplementedException("Add an implementation!");
212  }
213
214  /**
215   * Gets a scanner on the current table for the given family and qualifier.
216   *
217   * @param family The column family to scan.
218   * @param qualifier The column qualifier to scan.
219   * @return A scanner.
220   * @throws IOException if a remote or network exception occurs.
221   * @since 0.20.0
222   */
223  default ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException {
224    throw new NotImplementedException("Add an implementation!");
225  }
226
227  /**
228   * Puts some data in the table.
229   *
230   * @param put The data to put.
231   * @throws IOException if a remote or network exception occurs.
232   * @since 0.20.0
233   */
234  default void put(Put put) throws IOException {
235    put(Collections.singletonList(put));
236  }
237
238  /**
239   * Batch puts the specified data into the table.
240   * <p>
241   * This can be used for group commit, or for submitting user defined batches. Before sending
242   * a batch of mutations to the server, the client runs a few validations on the input list. If an
243   * error is found, for example, a mutation was supplied but was missing it's column an
244   * {@link IllegalArgumentException} will be thrown and no mutations will be applied. If there
245   * are any failures even after retries, a {@link RetriesExhaustedWithDetailsException} will be
246   * thrown. RetriesExhaustedWithDetailsException contains lists of failed mutations and
247   * corresponding remote exceptions. The ordering of mutations and exceptions in the
248   * encapsulating exception corresponds to the order of the input list of Put requests.
249   *
250   * @param puts The list of mutations to apply.
251   * @throws IOException if a remote or network exception occurs.
252   * @since 0.20.0
253   */
254  default void put(List<Put> puts) throws IOException {
255    throw new NotImplementedException("Add an implementation!");
256  }
257
258  /**
259   * Deletes the specified cells/row.
260   *
261   * @param delete The object that specifies what to delete.
262   * @throws IOException if a remote or network exception occurs.
263   * @since 0.20.0
264   */
265  default void delete(Delete delete) throws IOException {
266    throw new NotImplementedException("Add an implementation!");
267  }
268
269  /**
270   * Batch Deletes the specified cells/rows from the table.
271   * <p>
272   * If a specified row does not exist, {@link Delete} will report as though sucessful
273   * delete; no exception will be thrown. If there are any failures even after retries,
274   * a {@link RetriesExhaustedWithDetailsException} will be thrown.
275   * RetriesExhaustedWithDetailsException contains lists of failed {@link Delete}s and
276   * corresponding remote exceptions.
277   *
278   * @param deletes List of things to delete. The input list gets modified by this
279   * method. All successfully applied {@link Delete}s in the list are removed (in particular it
280   * gets re-ordered, so the order in which the elements are inserted in the list gives no
281   * guarantee as to the order in which the {@link Delete}s are executed).
282   * @throws IOException if a remote or network exception occurs. In that case
283   * the {@code deletes} argument will contain the {@link Delete} instances
284   * that have not be successfully applied.
285   * @since 0.20.1
286   * @apiNote In 3.0.0 version, the input list {@code deletes} will no longer be modified. Also,
287   *          {@link #put(List)} runs pre-flight validations on the input list on client. Currently
288   *          {@link #delete(List)} doesn't run validations on the client, there is no need
289   *          currently, but this may change in the future. An * {@link IllegalArgumentException}
290   *          will be thrown in this case.
291   */
292  default void delete(List<Delete> deletes) throws IOException {
293    throw new NotImplementedException("Add an implementation!");
294  }
295
296  /**
297   * Atomically checks if a row/family/qualifier value matches the expected value. If it does, it
298   * adds the Put/Delete/RowMutations.
299   * <p>
300   * Use the returned {@link CheckAndMutateBuilder} to construct your request and then execute it.
301   * This is a fluent style API, the code is like:
302   *
303   * <pre>
304   * <code>
305   * table.checkAndMutate(row, family).qualifier(qualifier).ifNotExists().thenPut(put);
306   * </code>
307   * </pre>
308   */
309  default CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) {
310    throw new NotImplementedException("Add an implementation!");
311  }
312
313  /**
314   * A helper class for sending checkAndMutate request.
315   */
316  interface CheckAndMutateBuilder {
317
318    /**
319     * @param qualifier column qualifier to check.
320     */
321    CheckAndMutateBuilder qualifier(byte[] qualifier);
322
323    /**
324     * @param timeRange timeRange to check
325     */
326    CheckAndMutateBuilder timeRange(TimeRange timeRange);
327
328    /**
329     * Check for lack of column.
330     */
331    CheckAndMutateBuilder ifNotExists();
332
333    /**
334     * Check for equality.
335     * @param value the expected value
336     */
337    default CheckAndMutateBuilder ifEquals(byte[] value) {
338      return ifMatches(CompareOperator.EQUAL, value);
339    }
340
341    /**
342     * @param compareOp comparison operator to use
343     * @param value the expected value
344     */
345    CheckAndMutateBuilder ifMatches(CompareOperator compareOp, byte[] value);
346
347    /**
348     * @param put data to put if check succeeds
349     * @return {@code true} if the new put was executed, {@code false} otherwise.
350     */
351    boolean thenPut(Put put) throws IOException;
352
353    /**
354     * @param delete data to delete if check succeeds
355     * @return {@code true} if the new delete was executed, {@code false} otherwise.
356     */
357    boolean thenDelete(Delete delete) throws IOException;
358    /**
359     * @param mutation mutations to perform if check succeeds
360     * @return true if the new mutation was executed, false otherwise.
361     */
362    boolean thenMutate(RowMutations mutation) throws IOException;
363  }
364
365  /**
366   * Performs multiple mutations atomically on a single row. Currently
367   * {@link Put} and {@link Delete} are supported.
368   *
369   * @param rm object that specifies the set of mutations to perform atomically
370   * @throws IOException
371   */
372  default void mutateRow(final RowMutations rm) throws IOException {
373    throw new NotImplementedException("Add an implementation!");
374  }
375
376  /**
377   * Appends values to one or more columns within a single row.
378   * <p>
379   * This operation guaranteed atomicity to readers. Appends are done
380   * under a single row lock, so write operations to a row are synchronized, and
381   * readers are guaranteed to see this operation fully completed.
382   *
383   * @param append object that specifies the columns and values to be appended
384   * @throws IOException e
385   * @return values of columns after the append operation (maybe null)
386   */
387  default Result append(final Append append) throws IOException {
388    throw new NotImplementedException("Add an implementation!");
389  }
390
391  /**
392   * Increments one or more columns within a single row.
393   * <p>
394   * This operation ensures atomicity to readers. Increments are done
395   * under a single row lock, so write operations to a row are synchronized, and
396   * readers are guaranteed to see this operation fully completed.
397   *
398   * @param increment object that specifies the columns and amounts to be used
399   *                  for the increment operations
400   * @throws IOException e
401   * @return values of columns after the increment
402   */
403  default Result increment(final Increment increment) throws IOException {
404    throw new NotImplementedException("Add an implementation!");
405  }
406
407  /**
408   * See {@link #incrementColumnValue(byte[], byte[], byte[], long, Durability)}
409   * <p>
410   * The {@link Durability} is defaulted to {@link Durability#SYNC_WAL}.
411   * @param row The row that contains the cell to increment.
412   * @param family The column family of the cell to increment.
413   * @param qualifier The column qualifier of the cell to increment.
414   * @param amount The amount to increment the cell with (or decrement, if the
415   * amount is negative).
416   * @return The new value, post increment.
417   * @throws IOException if a remote or network exception occurs.
418   */
419  default long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount)
420      throws IOException {
421    Increment increment = new Increment(row).addColumn(family, qualifier, amount);
422    Cell cell = increment(increment).getColumnLatestCell(family, qualifier);
423    return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
424  }
425
426  /**
427   * Atomically increments a column value. If the column value already exists
428   * and is not a big-endian long, this could throw an exception. If the column
429   * value does not yet exist it is initialized to <code>amount</code> and
430   * written to the specified column.
431   *
432   * <p>Setting durability to {@link Durability#SKIP_WAL} means that in a fail
433   * scenario you will lose any increments that have not been flushed.
434   * @param row The row that contains the cell to increment.
435   * @param family The column family of the cell to increment.
436   * @param qualifier The column qualifier of the cell to increment.
437   * @param amount The amount to increment the cell with (or decrement, if the
438   * amount is negative).
439   * @param durability The persistence guarantee for this increment.
440   * @return The new value, post increment.
441   * @throws IOException if a remote or network exception occurs.
442   */
443  default long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
444    long amount, Durability durability) throws IOException {
445    Increment increment = new Increment(row)
446        .addColumn(family, qualifier, amount)
447        .setDurability(durability);
448    Cell cell = increment(increment).getColumnLatestCell(family, qualifier);
449    return Bytes.toLong(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
450  }
451
452  /**
453   * Releases any resources held or pending changes in internal buffers.
454   *
455   * @throws IOException if a remote or network exception occurs.
456   */
457  @Override
458  default void close() throws IOException {
459    throw new NotImplementedException("Add an implementation!");
460  }
461
462  /**
463   * Creates and returns a {@link com.google.protobuf.RpcChannel} instance connected to the table
464   * region containing the specified row. The row given does not actually have to exist. Whichever
465   * region would contain the row based on start and end keys will be used. Note that the
466   * {@code row} parameter is also not passed to the coprocessor handler registered for this
467   * protocol, unless the {@code row} is separately passed as an argument in the service request.
468   * The parameter here is only used to locate the region used to handle the call.
469   * <p>
470   * The obtained {@link com.google.protobuf.RpcChannel} instance can be used to access a published
471   * coprocessor {@link com.google.protobuf.Service} using standard protobuf service invocations:
472   * </p>
473   * <div style="background-color: #cccccc; padding: 2px"> <blockquote>
474   *
475   * <pre>
476   * CoprocessorRpcChannel channel = myTable.coprocessorService(rowkey);
477   * MyService.BlockingInterface service = MyService.newBlockingStub(channel);
478   * MyCallRequest request = MyCallRequest.newBuilder()
479   *     ...
480   *     .build();
481   * MyCallResponse response = service.myCall(null, request);
482   * </pre>
483   *
484   * </blockquote></div>
485   * @param row The row key used to identify the remote region location
486   * @return A CoprocessorRpcChannel instance
487   * @deprecated since 3.0.0, will removed in 4.0.0. This is too low level, please stop using it any
488   *             more. Use the coprocessorService methods in {@link AsyncTable} instead.
489   * @see Connection#toAsyncConnection()
490   */
491  @Deprecated
492  default CoprocessorRpcChannel coprocessorService(byte[] row) {
493    throw new NotImplementedException("Add an implementation!");
494  }
495
496  /**
497   * Creates an instance of the given {@link com.google.protobuf.Service} subclass for each table
498   * region spanning the range from the {@code startKey} row to {@code endKey} row (inclusive), and
499   * invokes the passed {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method
500   * with each {@link com.google.protobuf.Service} instance.
501   * @param service the protocol buffer {@code Service} implementation to call
502   * @param startKey start region selection with region containing this row. If {@code null}, the
503   *          selection will start with the first table region.
504   * @param endKey select regions up to and including the region containing this row. If
505   *          {@code null}, selection will continue through the last table region.
506   * @param callable this instance's
507   *          {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method will be
508   *          invoked once per table region, using the {@link com.google.protobuf.Service} instance
509   *          connected to that region.
510   * @param <T> the {@link com.google.protobuf.Service} subclass to connect to
511   * @param <R> Return type for the {@code callable} parameter's
512   *          {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method
513   * @return a map of result values keyed by region name
514   * @deprecated since 3.0.0, will removed in 4.0.0. The batch call here references the blocking
515   *             interface for of a protobuf stub, so it is not possible to do it in an asynchronous
516   *             way, even if now we are building the {@link Table} implementation based on the
517   *             {@link AsyncTable}, which is not good. Use the coprocessorService methods in
518   *             {@link AsyncTable} directly instead.
519   * @see Connection#toAsyncConnection()
520   */
521  @Deprecated
522  default <T extends Service, R> Map<byte[], R> coprocessorService(final Class<T> service,
523      byte[] startKey, byte[] endKey, final Batch.Call<T, R> callable)
524      throws ServiceException, Throwable {
525    Map<byte[], R> results =
526      Collections.synchronizedMap(new TreeMap<byte[], R>(Bytes.BYTES_COMPARATOR));
527    coprocessorService(service, startKey, endKey, callable, new Batch.Callback<R>() {
528      @Override
529      public void update(byte[] region, byte[] row, R value) {
530        if (region != null) {
531          results.put(region, value);
532        }
533      }
534    });
535    return results;
536  }
537
538  /**
539   * Creates an instance of the given {@link com.google.protobuf.Service} subclass for each table
540   * region spanning the range from the {@code startKey} row to {@code endKey} row (inclusive), and
541   * invokes the passed {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method
542   * with each {@link Service} instance.
543   * <p>
544   * The given
545   * {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Callback#update(byte[],byte[],Object)}
546   * method will be called with the return value from each region's
547   * {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} invocation.
548   * </p>
549   * @param service the protocol buffer {@code Service} implementation to call
550   * @param startKey start region selection with region containing this row. If {@code null}, the
551   *          selection will start with the first table region.
552   * @param endKey select regions up to and including the region containing this row. If
553   *          {@code null}, selection will continue through the last table region.
554   * @param callable this instance's
555   *          {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method will be
556   *          invoked once per table region, using the {@link Service} instance connected to that
557   *          region.
558   * @param <T> the {@link Service} subclass to connect to
559   * @param <R> Return type for the {@code callable} parameter's
560   *          {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Call#call} method
561   * @deprecated since 3.0.0, will removed in 4.0.0. The batch call here references the blocking
562   *             interface for of a protobuf stub, so it is not possible to do it in an asynchronous
563   *             way, even if now we are building the {@link Table} implementation based on the
564   *             {@link AsyncTable}, which is not good. Use the coprocessorService methods in
565   *             {@link AsyncTable} directly instead.
566   * @see Connection#toAsyncConnection()
567   */
568  @Deprecated
569  default <T extends Service, R> void coprocessorService(final Class<T> service, byte[] startKey,
570      byte[] endKey, final Batch.Call<T, R> callable, final Batch.Callback<R> callback)
571      throws ServiceException, Throwable {
572    throw new NotImplementedException("Add an implementation!");
573  }
574
575  /**
576   * Creates an instance of the given {@link com.google.protobuf.Service} subclass for each table
577   * region spanning the range from the {@code startKey} row to {@code endKey} row (inclusive), all
578   * the invocations to the same region server will be batched into one call. The coprocessor
579   * service is invoked according to the service instance, method name and parameters.
580   * @param methodDescriptor the descriptor for the protobuf service method to call.
581   * @param request the method call parameters
582   * @param startKey start region selection with region containing this row. If {@code null}, the
583   *          selection will start with the first table region.
584   * @param endKey select regions up to and including the region containing this row. If
585   *          {@code null}, selection will continue through the last table region.
586   * @param responsePrototype the proto type of the response of the method in Service.
587   * @param <R> the response type for the coprocessor Service method
588   * @return a map of result values keyed by region name
589   * @deprecated since 3.0.0, will removed in 4.0.0. The batch call here references the blocking
590   *             interface for of a protobuf stub, so it is not possible to do it in an asynchronous
591   *             way, even if now we are building the {@link Table} implementation based on the
592   *             {@link AsyncTable}, which is not good. Use the coprocessorService methods in
593   *             {@link AsyncTable} directly instead.
594   * @see Connection#toAsyncConnection()
595   */
596  @Deprecated
597  default <R extends Message> Map<byte[], R> batchCoprocessorService(
598      Descriptors.MethodDescriptor methodDescriptor, Message request, byte[] startKey,
599      byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
600    final Map<byte[], R> results =
601      Collections.synchronizedMap(new TreeMap<byte[], R>(Bytes.BYTES_COMPARATOR));
602    batchCoprocessorService(methodDescriptor, request, startKey, endKey, responsePrototype,
603      new Callback<R>() {
604        @Override
605        public void update(byte[] region, byte[] row, R result) {
606          if (region != null) {
607            results.put(region, result);
608          }
609        }
610      });
611    return results;
612  }
613
614  /**
615   * Creates an instance of the given {@link com.google.protobuf.Service} subclass for each table
616   * region spanning the range from the {@code startKey} row to {@code endKey} row (inclusive), all
617   * the invocations to the same region server will be batched into one call. The coprocessor
618   * service is invoked according to the service instance, method name and parameters.
619   * <p>
620   * The given
621   * {@link org.apache.hadoop.hbase.client.coprocessor.Batch.Callback#update(byte[],byte[],Object)}
622   * method will be called with the return value from each region's invocation.
623   * </p>
624   * @param methodDescriptor the descriptor for the protobuf service method to call.
625   * @param request the method call parameters
626   * @param startKey start region selection with region containing this row. If {@code null}, the
627   *          selection will start with the first table region.
628   * @param endKey select regions up to and including the region containing this row. If
629   *          {@code null}, selection will continue through the last table region.
630   * @param responsePrototype the proto type of the response of the method in Service.
631   * @param callback callback to invoke with the response for each region
632   * @param <R> the response type for the coprocessor Service method
633   * @deprecated since 3.0.0, will removed in 4.0.0. The batch call here references the blocking
634   *             interface for of a protobuf stub, so it is not possible to do it in an asynchronous
635   *             way, even if now we are building the {@link Table} implementation based on the
636   *             {@link AsyncTable}, which is not good. Use the coprocessorService methods in
637   *             {@link AsyncTable} directly instead.
638   * @see Connection#toAsyncConnection()
639   */
640  @Deprecated
641  default <R extends Message> void batchCoprocessorService(
642      Descriptors.MethodDescriptor methodDescriptor, Message request, byte[] startKey,
643      byte[] endKey, R responsePrototype, Batch.Callback<R> callback)
644      throws ServiceException, Throwable {
645    throw new NotImplementedException("Add an implementation!");
646  }
647
648  /**
649   * Get timeout of each rpc request in this Table instance. It will be overridden by a more
650   * specific rpc timeout config such as readRpcTimeout or writeRpcTimeout.
651   * @see #getReadRpcTimeout(TimeUnit)
652   * @see #getWriteRpcTimeout(TimeUnit)
653   * @param unit the unit of time the timeout to be represented in
654   * @return rpc timeout in the specified time unit
655   */
656  default long getRpcTimeout(TimeUnit unit) {
657    throw new NotImplementedException("Add an implementation!");
658  }
659
660  /**
661   * Get timeout of each rpc read request in this Table instance.
662   * @param unit the unit of time the timeout to be represented in
663   * @return read rpc timeout in the specified time unit
664   */
665  default long getReadRpcTimeout(TimeUnit unit) {
666    throw new NotImplementedException("Add an implementation!");
667  }
668
669  /**
670   * Get timeout of each rpc write request in this Table instance.
671   * @param unit the unit of time the timeout to be represented in
672   * @return write rpc timeout in the specified time unit
673   */
674  default long getWriteRpcTimeout(TimeUnit unit) {
675    throw new NotImplementedException("Add an implementation!");
676  }
677
678  /**
679   * Get timeout of each operation in Table instance.
680   * @param unit the unit of time the timeout to be represented in
681   * @return operation rpc timeout in the specified time unit
682   */
683  default long getOperationTimeout(TimeUnit unit) {
684    throw new NotImplementedException("Add an implementation!");
685  }
686}