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