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