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.rest.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.IOException;
026import java.io.InterruptedIOException;
027import java.io.UnsupportedEncodingException;
028import java.net.URLEncoder;
029import java.nio.charset.StandardCharsets;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.Iterator;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.TreeMap;
037import java.util.concurrent.TimeUnit;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.hbase.Cell;
040import org.apache.hadoop.hbase.CellUtil;
041import org.apache.hadoop.hbase.CompareOperator;
042import org.apache.hadoop.hbase.HBaseConfiguration;
043import org.apache.hadoop.hbase.HConstants;
044import org.apache.hadoop.hbase.KeyValue;
045import org.apache.hadoop.hbase.TableName;
046import org.apache.hadoop.hbase.client.Append;
047import org.apache.hadoop.hbase.client.Delete;
048import org.apache.hadoop.hbase.client.Durability;
049import org.apache.hadoop.hbase.client.Get;
050import org.apache.hadoop.hbase.client.Increment;
051import org.apache.hadoop.hbase.client.Put;
052import org.apache.hadoop.hbase.client.RegionLocator;
053import org.apache.hadoop.hbase.client.Result;
054import org.apache.hadoop.hbase.client.ResultScanner;
055import org.apache.hadoop.hbase.client.Row;
056import org.apache.hadoop.hbase.client.RowMutations;
057import org.apache.hadoop.hbase.client.Scan;
058import org.apache.hadoop.hbase.client.Table;
059import org.apache.hadoop.hbase.client.TableDescriptor;
060import org.apache.hadoop.hbase.client.coprocessor.Batch;
061import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
062import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
063import org.apache.hadoop.hbase.io.TimeRange;
064import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
065import org.apache.hadoop.hbase.rest.Constants;
066import org.apache.hadoop.hbase.rest.model.CellModel;
067import org.apache.hadoop.hbase.rest.model.CellSetModel;
068import org.apache.hadoop.hbase.rest.model.RowModel;
069import org.apache.hadoop.hbase.rest.model.ScannerModel;
070import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
071import org.apache.hadoop.hbase.util.Bytes;
072import org.apache.hadoop.util.StringUtils;
073import org.apache.yetus.audience.InterfaceAudience;
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
078
079/**
080 * HTable interface to remote tables accessed via REST gateway
081 */
082@InterfaceAudience.Public
083public class RemoteHTable implements Table {
084
085  private static final Logger LOG = LoggerFactory.getLogger(RemoteHTable.class);
086
087  final Client client;
088  final Configuration conf;
089  final byte[] name;
090  final int maxRetries;
091  final long sleepTime;
092
093  @SuppressWarnings("rawtypes")
094  protected String buildRowSpec(final byte[] row, final Map familyMap, final long startTime,
095      final long endTime, final int maxVersions) {
096    StringBuffer sb = new StringBuffer();
097    sb.append('/');
098    sb.append(Bytes.toString(name));
099    sb.append('/');
100    sb.append(toURLEncodedBytes(row));
101    Set families = familyMap.entrySet();
102    if (families != null) {
103      Iterator i = familyMap.entrySet().iterator();
104      sb.append('/');
105      while (i.hasNext()) {
106        Map.Entry e = (Map.Entry) i.next();
107        Collection quals = (Collection) e.getValue();
108        if (quals == null || quals.isEmpty()) {
109          // this is an unqualified family. append the family name and NO ':'
110          sb.append(toURLEncodedBytes((byte[]) e.getKey()));
111        } else {
112          Iterator ii = quals.iterator();
113          while (ii.hasNext()) {
114            sb.append(toURLEncodedBytes((byte[]) e.getKey()));
115            Object o = ii.next();
116            // Puts use byte[] but Deletes use KeyValue
117            if (o instanceof byte[]) {
118              sb.append(':');
119              sb.append(toURLEncodedBytes((byte[]) o));
120            } else if (o instanceof KeyValue) {
121              if (((KeyValue) o).getQualifierLength() != 0) {
122                sb.append(':');
123                sb.append(toURLEncodedBytes(CellUtil.cloneQualifier((KeyValue) o)));
124              }
125            } else {
126              throw new RuntimeException("object type not handled");
127            }
128            if (ii.hasNext()) {
129              sb.append(',');
130            }
131          }
132        }
133        if (i.hasNext()) {
134          sb.append(',');
135        }
136      }
137    }
138    if (startTime >= 0 && endTime != Long.MAX_VALUE) {
139      sb.append('/');
140      sb.append(startTime);
141      if (startTime != endTime) {
142        sb.append(',');
143        sb.append(endTime);
144      }
145    } else if (endTime != Long.MAX_VALUE) {
146      sb.append('/');
147      sb.append(endTime);
148    }
149    if (maxVersions > 1) {
150      sb.append("?v=");
151      sb.append(maxVersions);
152    }
153    return sb.toString();
154  }
155
156  protected String buildMultiRowSpec(final byte[][] rows, int maxVersions) {
157    StringBuilder sb = new StringBuilder();
158    sb.append('/');
159    sb.append(Bytes.toString(name));
160    sb.append("/multiget/");
161    if (rows == null || rows.length == 0) {
162      return sb.toString();
163    }
164    sb.append("?");
165    for (int i = 0; i < rows.length; i++) {
166      byte[] rk = rows[i];
167      if (i != 0) {
168        sb.append('&');
169      }
170      sb.append("row=");
171      sb.append(toURLEncodedBytes(rk));
172    }
173    sb.append("&v=");
174    sb.append(maxVersions);
175
176    return sb.toString();
177  }
178
179  protected Result[] buildResultFromModel(final CellSetModel model) {
180    List<Result> results = new ArrayList<>();
181    for (RowModel row : model.getRows()) {
182      List<Cell> kvs = new ArrayList<>(row.getCells().size());
183      for (CellModel cell : row.getCells()) {
184        byte[][] split = CellUtil.parseColumn(cell.getColumn());
185        byte[] column = split[0];
186        byte[] qualifier = null;
187        if (split.length == 1) {
188          qualifier = HConstants.EMPTY_BYTE_ARRAY;
189        } else if (split.length == 2) {
190          qualifier = split[1];
191        } else {
192          throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
193        }
194        kvs
195          .add(new KeyValue(row.getKey(), column, qualifier, cell.getTimestamp(), cell.getValue()));
196      }
197      results.add(Result.create(kvs));
198    }
199    return results.toArray(new Result[results.size()]);
200  }
201
202  protected CellSetModel buildModelFromPut(Put put) {
203    RowModel row = new RowModel(put.getRow());
204    long ts = put.getTimestamp();
205    for (List<Cell> cells : put.getFamilyCellMap().values()) {
206      for (Cell cell : cells) {
207        row.addCell(new CellModel(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell),
208          ts != HConstants.LATEST_TIMESTAMP ? ts : cell.getTimestamp(), CellUtil.cloneValue(cell)));
209      }
210    }
211    CellSetModel model = new CellSetModel();
212    model.addRow(row);
213    return model;
214  }
215
216  /**
217   * Constructor
218   */
219  public RemoteHTable(Client client, String name) {
220    this(client, HBaseConfiguration.create(), Bytes.toBytes(name));
221  }
222
223  /**
224   * Constructor
225   */
226  public RemoteHTable(Client client, Configuration conf, String name) {
227    this(client, conf, Bytes.toBytes(name));
228  }
229
230  /**
231   * Constructor
232   */
233  public RemoteHTable(Client client, Configuration conf, byte[] name) {
234    this.client = client;
235    this.conf = conf;
236    this.name = name;
237    this.maxRetries = conf.getInt("hbase.rest.client.max.retries", 10);
238    this.sleepTime = conf.getLong("hbase.rest.client.sleep", 1000);
239  }
240
241  public byte[] getTableName() {
242    return name.clone();
243  }
244
245  @Override
246  public TableName getName() {
247    return TableName.valueOf(name);
248  }
249
250  @Override
251  public Configuration getConfiguration() {
252    return conf;
253  }
254
255  @Override
256  public void close() throws IOException {
257    client.shutdown();
258  }
259
260  @Override
261  public Result get(Get get) throws IOException {
262    TimeRange range = get.getTimeRange();
263    String spec = buildRowSpec(get.getRow(), get.getFamilyMap(), range.getMin(), range.getMax(),
264      get.getMaxVersions());
265    if (get.getFilter() != null) {
266      LOG.warn("filters not supported on gets");
267    }
268    Result[] results = getResults(spec);
269    if (results.length > 0) {
270      if (results.length > 1) {
271        LOG.warn("too many results for get (" + results.length + ")");
272      }
273      return results[0];
274    } else {
275      return new Result();
276    }
277  }
278
279  @Override
280  public Result[] get(List<Get> gets) throws IOException {
281    byte[][] rows = new byte[gets.size()][];
282    int maxVersions = 1;
283    int count = 0;
284
285    for (Get g : gets) {
286
287      if (count == 0) {
288        maxVersions = g.getMaxVersions();
289      } else if (g.getMaxVersions() != maxVersions) {
290        LOG.warn(
291          "MaxVersions on Gets do not match, using the first in the list (" + maxVersions + ")");
292      }
293
294      if (g.getFilter() != null) {
295        LOG.warn("filters not supported on gets");
296      }
297
298      rows[count] = g.getRow();
299      count++;
300    }
301
302    String spec = buildMultiRowSpec(rows, maxVersions);
303
304    return getResults(spec);
305  }
306
307  private Result[] getResults(String spec) throws IOException {
308    for (int i = 0; i < maxRetries; i++) {
309      Response response = client.get(spec, Constants.MIMETYPE_PROTOBUF);
310      int code = response.getCode();
311      switch (code) {
312        case 200:
313          CellSetModel model = new CellSetModel();
314          model.getObjectFromMessage(response.getBody());
315          Result[] results = buildResultFromModel(model);
316          if (results.length > 0) {
317            return results;
318          }
319          // fall through
320        case 404:
321          return new Result[0];
322
323        case 509:
324          try {
325            Thread.sleep(sleepTime);
326          } catch (InterruptedException e) {
327            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
328          }
329          break;
330        default:
331          throw new IOException("get request returned " + code);
332      }
333    }
334    throw new IOException("get request timed out");
335  }
336
337  @Override
338  public boolean exists(Get get) throws IOException {
339    LOG.warn("exists() is really get(), just use get()");
340    Result result = get(get);
341    return (result != null && !(result.isEmpty()));
342  }
343
344  @Override
345  public boolean[] exists(List<Get> gets) throws IOException {
346    LOG.warn("exists(List<Get>) is really list of get() calls, just use get()");
347    boolean[] results = new boolean[gets.size()];
348    for (int i = 0; i < results.length; i++) {
349      results[i] = exists(gets.get(i));
350    }
351    return results;
352  }
353
354  @Override
355  public void put(Put put) throws IOException {
356    CellSetModel model = buildModelFromPut(put);
357    StringBuilder sb = new StringBuilder();
358    sb.append('/');
359    sb.append(Bytes.toString(name));
360    sb.append('/');
361    sb.append(toURLEncodedBytes(put.getRow()));
362    for (int i = 0; i < maxRetries; i++) {
363      Response response =
364        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
365      int code = response.getCode();
366      switch (code) {
367        case 200:
368          return;
369        case 509:
370          try {
371            Thread.sleep(sleepTime);
372          } catch (InterruptedException e) {
373            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
374          }
375          break;
376        default:
377          throw new IOException("put request failed with " + code);
378      }
379    }
380    throw new IOException("put request timed out");
381  }
382
383  @Override
384  public void put(List<Put> puts) throws IOException {
385    // this is a trick: The gateway accepts multiple rows in a cell set and
386    // ignores the row specification in the URI
387
388    // separate puts by row
389    TreeMap<byte[], List<Cell>> map = new TreeMap<>(Bytes.BYTES_COMPARATOR);
390    for (Put put : puts) {
391      byte[] row = put.getRow();
392      List<Cell> cells = map.get(row);
393      if (cells == null) {
394        cells = new ArrayList<>();
395        map.put(row, cells);
396      }
397      for (List<Cell> l : put.getFamilyCellMap().values()) {
398        cells.addAll(l);
399      }
400    }
401
402    // build the cell set
403    CellSetModel model = new CellSetModel();
404    for (Map.Entry<byte[], List<Cell>> e : map.entrySet()) {
405      RowModel row = new RowModel(e.getKey());
406      for (Cell cell : e.getValue()) {
407        row.addCell(new CellModel(cell));
408      }
409      model.addRow(row);
410    }
411
412    // build path for multiput
413    StringBuilder sb = new StringBuilder();
414    sb.append('/');
415    sb.append(Bytes.toString(name));
416    sb.append("/$multiput"); // can be any nonexistent row
417    for (int i = 0; i < maxRetries; i++) {
418      Response response =
419        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
420      int code = response.getCode();
421      switch (code) {
422        case 200:
423          return;
424        case 509:
425          try {
426            Thread.sleep(sleepTime);
427          } catch (InterruptedException e) {
428            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
429          }
430          break;
431        default:
432          throw new IOException("multiput request failed with " + code);
433      }
434    }
435    throw new IOException("multiput request timed out");
436  }
437
438  @Override
439  public void delete(Delete delete) throws IOException {
440    String spec = buildRowSpec(delete.getRow(), delete.getFamilyCellMap(), delete.getTimestamp(),
441      delete.getTimestamp(), 1);
442    for (int i = 0; i < maxRetries; i++) {
443      Response response = client.delete(spec);
444      int code = response.getCode();
445      switch (code) {
446        case 200:
447          return;
448        case 509:
449          try {
450            Thread.sleep(sleepTime);
451          } catch (InterruptedException e) {
452            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
453          }
454          break;
455        default:
456          throw new IOException("delete request failed with " + code);
457      }
458    }
459    throw new IOException("delete request timed out");
460  }
461
462  @Override
463  public void delete(List<Delete> deletes) throws IOException {
464    for (Delete delete : deletes) {
465      delete(delete);
466    }
467  }
468
469  public void flushCommits() throws IOException {
470    // no-op
471  }
472
473  @Override
474  public TableDescriptor getDescriptor() throws IOException {
475    StringBuilder sb = new StringBuilder();
476    sb.append('/');
477    sb.append(Bytes.toString(name));
478    sb.append('/');
479    sb.append("schema");
480    for (int i = 0; i < maxRetries; i++) {
481      Response response = client.get(sb.toString(), Constants.MIMETYPE_PROTOBUF);
482      int code = response.getCode();
483      switch (code) {
484        case 200:
485          TableSchemaModel schema = new TableSchemaModel();
486          schema.getObjectFromMessage(response.getBody());
487          return schema.getTableDescriptor();
488        case 509:
489          try {
490            Thread.sleep(sleepTime);
491          } catch (InterruptedException e) {
492            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
493          }
494          break;
495        default:
496          throw new IOException("schema request returned " + code);
497      }
498    }
499    throw new IOException("schema request timed out");
500  }
501
502  class Scanner implements ResultScanner {
503
504    String uri;
505
506    public Scanner(Scan scan) throws IOException {
507      ScannerModel model;
508      try {
509        model = ScannerModel.fromScan(scan);
510      } catch (Exception e) {
511        throw new IOException(e);
512      }
513      StringBuffer sb = new StringBuffer();
514      sb.append('/');
515      sb.append(Bytes.toString(name));
516      sb.append('/');
517      sb.append("scanner");
518      for (int i = 0; i < maxRetries; i++) {
519        Response response =
520          client.post(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
521        int code = response.getCode();
522        switch (code) {
523          case 201:
524            uri = response.getLocation();
525            return;
526          case 509:
527            try {
528              Thread.sleep(sleepTime);
529            } catch (InterruptedException e) {
530              throw (InterruptedIOException) new InterruptedIOException().initCause(e);
531            }
532            break;
533          default:
534            throw new IOException("scan request failed with " + code);
535        }
536      }
537      throw new IOException("scan request timed out");
538    }
539
540    @Override
541    public Result[] next(int nbRows) throws IOException {
542      StringBuilder sb = new StringBuilder(uri);
543      sb.append("?n=");
544      sb.append(nbRows);
545      for (int i = 0; i < maxRetries; i++) {
546        Response response = client.get(sb.toString(), Constants.MIMETYPE_PROTOBUF);
547        int code = response.getCode();
548        switch (code) {
549          case 200:
550            CellSetModel model = new CellSetModel();
551            model.getObjectFromMessage(response.getBody());
552            return buildResultFromModel(model);
553          case 204:
554          case 206:
555            return null;
556          case 509:
557            try {
558              Thread.sleep(sleepTime);
559            } catch (InterruptedException e) {
560              throw (InterruptedIOException) new InterruptedIOException().initCause(e);
561            }
562            break;
563          default:
564            throw new IOException("scanner.next request failed with " + code);
565        }
566      }
567      throw new IOException("scanner.next request timed out");
568    }
569
570    @Override
571    public Result next() throws IOException {
572      Result[] results = next(1);
573      if (results == null || results.length < 1) {
574        return null;
575      }
576      return results[0];
577    }
578
579    class Iter implements Iterator<Result> {
580
581      Result cache;
582
583      public Iter() {
584        try {
585          cache = Scanner.this.next();
586        } catch (IOException e) {
587          LOG.warn(StringUtils.stringifyException(e));
588        }
589      }
590
591      @Override
592      public boolean hasNext() {
593        return cache != null;
594      }
595
596      @Override
597      public Result next() {
598        Result result = cache;
599        try {
600          cache = Scanner.this.next();
601        } catch (IOException e) {
602          LOG.warn(StringUtils.stringifyException(e));
603          cache = null;
604        }
605        return result;
606      }
607
608      @Override
609      public void remove() {
610        throw new RuntimeException("remove() not supported");
611      }
612
613    }
614
615    @Override
616    public Iterator<Result> iterator() {
617      return new Iter();
618    }
619
620    @Override
621    public void close() {
622      try {
623        client.delete(uri);
624      } catch (IOException e) {
625        LOG.warn(StringUtils.stringifyException(e));
626      }
627    }
628
629    @Override
630    public boolean renewLease() {
631      throw new RuntimeException("renewLease() not supported");
632    }
633
634    @Override
635    public ScanMetrics getScanMetrics() {
636      throw new RuntimeException("getScanMetrics() not supported");
637    }
638  }
639
640  @Override
641  public ResultScanner getScanner(Scan scan) throws IOException {
642    return new Scanner(scan);
643  }
644
645  @Override
646  public ResultScanner getScanner(byte[] family) throws IOException {
647    Scan scan = new Scan();
648    scan.addFamily(family);
649    return new Scanner(scan);
650  }
651
652  @Override
653  public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException {
654    Scan scan = new Scan();
655    scan.addColumn(family, qualifier);
656    return new Scanner(scan);
657  }
658
659  public boolean isAutoFlush() {
660    return true;
661  }
662
663  private boolean doCheckAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put)
664      throws IOException {
665    // column to check-the-value
666    put.add(new KeyValue(row, family, qualifier, value));
667
668    CellSetModel model = buildModelFromPut(put);
669    StringBuilder sb = new StringBuilder();
670    sb.append('/');
671    sb.append(Bytes.toString(name));
672    sb.append('/');
673    sb.append(toURLEncodedBytes(put.getRow()));
674    sb.append("?check=put");
675
676    for (int i = 0; i < maxRetries; i++) {
677      Response response =
678        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
679      int code = response.getCode();
680      switch (code) {
681        case 200:
682          return true;
683        case 304: // NOT-MODIFIED
684          return false;
685        case 509:
686          try {
687            Thread.sleep(sleepTime);
688          } catch (final InterruptedException e) {
689            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
690          }
691          break;
692        default:
693          throw new IOException("checkAndPut request failed with " + code);
694      }
695    }
696    throw new IOException("checkAndPut request timed out");
697  }
698
699  private boolean doCheckAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value,
700      Delete delete) throws IOException {
701    Put put = new Put(row);
702    put.setFamilyCellMap(delete.getFamilyCellMap());
703    // column to check-the-value
704    put.add(new KeyValue(row, family, qualifier, value));
705    CellSetModel model = buildModelFromPut(put);
706    StringBuilder sb = new StringBuilder();
707    sb.append('/');
708    sb.append(Bytes.toString(name));
709    sb.append('/');
710    sb.append(toURLEncodedBytes(row));
711    sb.append("?check=delete");
712
713    for (int i = 0; i < maxRetries; i++) {
714      Response response =
715        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
716      int code = response.getCode();
717      switch (code) {
718        case 200:
719          return true;
720        case 304: // NOT-MODIFIED
721          return false;
722        case 509:
723          try {
724            Thread.sleep(sleepTime);
725          } catch (final InterruptedException e) {
726            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
727          }
728          break;
729        default:
730          throw new IOException("checkAndDelete request failed with " + code);
731      }
732    }
733    throw new IOException("checkAndDelete request timed out");
734  }
735
736  @Override
737  public CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) {
738    return new CheckAndMutateBuilderImpl(row, family);
739  }
740
741  @Override
742  public Result increment(Increment increment) throws IOException {
743    throw new IOException("Increment not supported");
744  }
745
746  @Override
747  public Result append(Append append) throws IOException {
748    throw new IOException("Append not supported");
749  }
750
751  @Override
752  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount)
753      throws IOException {
754    throw new IOException("incrementColumnValue not supported");
755  }
756
757  @Override
758  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount,
759      Durability durability) throws IOException {
760    throw new IOException("incrementColumnValue not supported");
761  }
762
763  @Override
764  public void batch(List<? extends Row> actions, Object[] results) throws IOException {
765    throw new IOException("batch not supported");
766  }
767
768  @Override
769  public <R> void batchCallback(List<? extends Row> actions, Object[] results,
770      Batch.Callback<R> callback) throws IOException, InterruptedException {
771    throw new IOException("batchCallback not supported");
772  }
773
774  @Override
775  public CoprocessorRpcChannel coprocessorService(byte[] row) {
776    throw new UnsupportedOperationException("coprocessorService not implemented");
777  }
778
779  @Override
780  public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service, byte[] startKey,
781      byte[] endKey, Batch.Call<T, R> callable) throws ServiceException, Throwable {
782    throw new UnsupportedOperationException("coprocessorService not implemented");
783  }
784
785  @Override
786  public <T extends Service, R> void coprocessorService(Class<T> service, byte[] startKey,
787      byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
788      throws ServiceException, Throwable {
789    throw new UnsupportedOperationException("coprocessorService not implemented");
790  }
791
792  @Override
793  public void mutateRow(RowMutations rm) throws IOException {
794    throw new IOException("atomicMutation not supported");
795  }
796
797  @Override
798  public <R extends Message> Map<byte[], R> batchCoprocessorService(
799      Descriptors.MethodDescriptor method, Message request, byte[] startKey, byte[] endKey,
800      R responsePrototype) throws ServiceException, Throwable {
801    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
802  }
803
804  @Override
805  public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
806      Message request, byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback)
807      throws ServiceException, Throwable {
808    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
809  }
810
811  @Override
812  public long getReadRpcTimeout(TimeUnit unit) {
813    throw new UnsupportedOperationException();
814  }
815
816  @Override
817  public long getRpcTimeout(TimeUnit unit) {
818    throw new UnsupportedOperationException();
819  }
820
821  @Override
822  public long getWriteRpcTimeout(TimeUnit unit) {
823    throw new UnsupportedOperationException();
824  }
825
826  @Override
827  public long getOperationTimeout(TimeUnit unit) {
828    throw new UnsupportedOperationException();
829  }
830
831  /*
832   * Only a small subset of characters are valid in URLs. Row keys, column families, and qualifiers
833   * cannot be appended to URLs without first URL escaping. Table names are ok because they can only
834   * contain alphanumeric, ".","_", and "-" which are valid characters in URLs.
835   */
836  private static String toURLEncodedBytes(byte[] row) {
837    try {
838      return URLEncoder.encode(new String(row, StandardCharsets.UTF_8), "UTF-8");
839    } catch (UnsupportedEncodingException e) {
840      throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
841    }
842  }
843
844  private class CheckAndMutateBuilderImpl implements CheckAndMutateBuilder {
845
846    private final byte[] row;
847    private final byte[] family;
848    private byte[] qualifier;
849    private byte[] value;
850
851    CheckAndMutateBuilderImpl(byte[] row, byte[] family) {
852      this.row = Preconditions.checkNotNull(row, "row is null");
853      this.family = Preconditions.checkNotNull(family, "family is null");
854    }
855
856    @Override
857    public CheckAndMutateBuilder qualifier(byte[] qualifier) {
858      this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null. Consider using" +
859        " an empty byte array, or just do not call this method if you want a null qualifier");
860      return this;
861    }
862
863    @Override
864    public CheckAndMutateBuilder timeRange(TimeRange timeRange) {
865      throw new UnsupportedOperationException("timeRange not implemented");
866    }
867
868    @Override
869    public CheckAndMutateBuilder ifNotExists() {
870      throw new UnsupportedOperationException(
871        "CheckAndMutate for non-equal comparison " + "not implemented");
872    }
873
874    @Override
875    public CheckAndMutateBuilder ifMatches(CompareOperator compareOp, byte[] value) {
876      if (compareOp == CompareOperator.EQUAL) {
877        this.value = Preconditions.checkNotNull(value, "value is null");
878        return this;
879      } else {
880        throw new UnsupportedOperationException(
881          "CheckAndMutate for non-equal comparison " + "not implemented");
882      }
883    }
884
885    @Override
886    public CheckAndMutateBuilder ifEquals(byte[] value) {
887      this.value = Preconditions.checkNotNull(value, "value is null");
888      return this;
889    }
890
891    @Override
892    public boolean thenPut(Put put) throws IOException {
893      return doCheckAndPut(row, family, qualifier, value, put);
894    }
895
896    @Override
897    public boolean thenDelete(Delete delete) throws IOException {
898      return doCheckAndDelete(row, family, qualifier, value, delete);
899    }
900
901    @Override
902    public boolean thenMutate(RowMutations mutation) throws IOException {
903      throw new UnsupportedOperationException("thenMutate not implemented");
904    }
905  }
906
907  @Override
908  public RegionLocator getRegionLocator() throws IOException {
909    throw new UnsupportedOperationException();
910  }
911}