001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.rest.client;
019
020import com.google.protobuf.Descriptors;
021import com.google.protobuf.Message;
022import com.google.protobuf.Service;
023import com.google.protobuf.ServiceException;
024import java.io.IOException;
025import java.io.InterruptedIOException;
026import java.io.UnsupportedEncodingException;
027import java.net.URLEncoder;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.TreeMap;
035import java.util.concurrent.TimeUnit;
036import org.apache.commons.lang3.NotImplementedException;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.hbase.Cell;
039import org.apache.hadoop.hbase.CellUtil;
040import org.apache.hadoop.hbase.CompareOperator;
041import org.apache.hadoop.hbase.HBaseConfiguration;
042import org.apache.hadoop.hbase.HConstants;
043import org.apache.hadoop.hbase.HTableDescriptor;
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.CheckAndMutate;
048import org.apache.hadoop.hbase.client.CheckAndMutateResult;
049import org.apache.hadoop.hbase.client.Delete;
050import org.apache.hadoop.hbase.client.Durability;
051import org.apache.hadoop.hbase.client.Get;
052import org.apache.hadoop.hbase.client.Increment;
053import org.apache.hadoop.hbase.client.Put;
054import org.apache.hadoop.hbase.client.RegionLocator;
055import org.apache.hadoop.hbase.client.Result;
056import org.apache.hadoop.hbase.client.ResultScanner;
057import org.apache.hadoop.hbase.client.Row;
058import org.apache.hadoop.hbase.client.RowMutations;
059import org.apache.hadoop.hbase.client.Scan;
060import org.apache.hadoop.hbase.client.Table;
061import org.apache.hadoop.hbase.client.TableDescriptor;
062import org.apache.hadoop.hbase.client.coprocessor.Batch;
063import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
064import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
065import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
066import org.apache.hadoop.hbase.filter.Filter;
067import org.apache.hadoop.hbase.io.TimeRange;
068import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
069import org.apache.hadoop.hbase.rest.Constants;
070import org.apache.hadoop.hbase.rest.model.CellModel;
071import org.apache.hadoop.hbase.rest.model.CellSetModel;
072import org.apache.hadoop.hbase.rest.model.RowModel;
073import org.apache.hadoop.hbase.rest.model.ScannerModel;
074import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
075import org.apache.hadoop.hbase.util.Bytes;
076import org.apache.hadoop.util.StringUtils;
077import org.apache.yetus.audience.InterfaceAudience;
078import org.slf4j.Logger;
079import org.slf4j.LoggerFactory;
080
081import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
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  @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(), range.getMin(), range.getMax(),
298      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(
325          "MaxVersions on Gets do not match, using the first in the list (" + maxVersions + ")");
326      }
327
328      if (g.getFilter() != null) {
329        LOG.warn("filters not supported on gets");
330      }
331
332      rows[count] = g.getRow();
333      count++;
334    }
335
336    String spec = buildMultiRowSpec(rows, maxVersions);
337
338    return getResults(spec);
339  }
340
341  private Result[] getResults(String spec) throws IOException {
342    for (int i = 0; i < maxRetries; i++) {
343      Response response = client.get(spec, Constants.MIMETYPE_PROTOBUF);
344      int code = response.getCode();
345      switch (code) {
346        case 200:
347          CellSetModel model = new CellSetModel();
348          model.getObjectFromMessage(response.getBody());
349          Result[] results = buildResultFromModel(model);
350          if (results.length > 0) {
351            return results;
352          }
353          // fall through
354        case 404:
355          return new Result[0];
356
357        case 509:
358          try {
359            Thread.sleep(sleepTime);
360          } catch (InterruptedException e) {
361            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
362          }
363          break;
364        default:
365          throw new IOException("get request returned " + code);
366      }
367    }
368    throw new IOException("get request timed out");
369  }
370
371  @Override
372  public boolean exists(Get get) throws IOException {
373    LOG.warn("exists() is really get(), just use get()");
374    Result result = get(get);
375    return (result != null && !(result.isEmpty()));
376  }
377
378  @Override
379  public boolean[] exists(List<Get> gets) throws IOException {
380    LOG.warn("exists(List<Get>) is really list of get() calls, just use get()");
381    boolean[] results = new boolean[gets.size()];
382    for (int i = 0; i < results.length; i++) {
383      results[i] = exists(gets.get(i));
384    }
385    return results;
386  }
387
388  @Override
389  public void put(Put put) throws IOException {
390    CellSetModel model = buildModelFromPut(put);
391    StringBuilder sb = new StringBuilder();
392    sb.append('/');
393    sb.append(Bytes.toString(name));
394    sb.append('/');
395    sb.append(toURLEncodedBytes(put.getRow()));
396    for (int i = 0; i < maxRetries; i++) {
397      Response response =
398        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
399      int code = response.getCode();
400      switch (code) {
401        case 200:
402          return;
403        case 509:
404          try {
405            Thread.sleep(sleepTime);
406          } catch (InterruptedException e) {
407            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
408          }
409          break;
410        default:
411          throw new IOException("put request failed with " + code);
412      }
413    }
414    throw new IOException("put request timed out");
415  }
416
417  @Override
418  public void put(List<Put> puts) throws IOException {
419    // this is a trick: The gateway accepts multiple rows in a cell set and
420    // ignores the row specification in the URI
421
422    // separate puts by row
423    TreeMap<byte[], List<Cell>> map = new TreeMap<>(Bytes.BYTES_COMPARATOR);
424    for (Put put : puts) {
425      byte[] row = put.getRow();
426      List<Cell> cells = map.get(row);
427      if (cells == null) {
428        cells = new ArrayList<>();
429        map.put(row, cells);
430      }
431      for (List<Cell> l : put.getFamilyCellMap().values()) {
432        cells.addAll(l);
433      }
434    }
435
436    // build the cell set
437    CellSetModel model = new CellSetModel();
438    for (Map.Entry<byte[], List<Cell>> e : map.entrySet()) {
439      RowModel row = new RowModel(e.getKey());
440      for (Cell cell : e.getValue()) {
441        row.addCell(new CellModel(cell));
442      }
443      model.addRow(row);
444    }
445
446    // build path for multiput
447    StringBuilder sb = new StringBuilder();
448    sb.append('/');
449    sb.append(Bytes.toString(name));
450    sb.append("/$multiput"); // can be any nonexistent row
451    for (int i = 0; i < maxRetries; i++) {
452      Response response =
453        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
454      int code = response.getCode();
455      switch (code) {
456        case 200:
457          return;
458        case 509:
459          try {
460            Thread.sleep(sleepTime);
461          } catch (InterruptedException e) {
462            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
463          }
464          break;
465        default:
466          throw new IOException("multiput request failed with " + code);
467      }
468    }
469    throw new IOException("multiput request timed out");
470  }
471
472  @Override
473  public void delete(Delete delete) throws IOException {
474    String spec = buildRowSpec(delete.getRow(), delete.getFamilyCellMap(), delete.getTimestamp(),
475      delete.getTimestamp(), 1);
476    for (int i = 0; i < maxRetries; i++) {
477      Response response = client.delete(spec);
478      int code = response.getCode();
479      switch (code) {
480        case 200:
481          return;
482        case 509:
483          try {
484            Thread.sleep(sleepTime);
485          } catch (InterruptedException e) {
486            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
487          }
488          break;
489        default:
490          throw new IOException("delete request failed with " + code);
491      }
492    }
493    throw new IOException("delete request timed out");
494  }
495
496  @Override
497  public void delete(List<Delete> deletes) throws IOException {
498    for (Delete delete : deletes) {
499      delete(delete);
500    }
501  }
502
503  public void flushCommits() throws IOException {
504    // no-op
505  }
506
507  @Override
508  public TableDescriptor getDescriptor() throws IOException {
509    return getTableDescriptor();
510  }
511
512  class Scanner implements ResultScanner {
513
514    String uri;
515
516    public Scanner(Scan scan) throws IOException {
517      ScannerModel model;
518      try {
519        model = ScannerModel.fromScan(scan);
520      } catch (Exception e) {
521        throw new IOException(e);
522      }
523      StringBuffer sb = new StringBuffer();
524      sb.append('/');
525      sb.append(Bytes.toString(name));
526      sb.append('/');
527      sb.append("scanner");
528      for (int i = 0; i < maxRetries; i++) {
529        Response response =
530          client.post(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
531        int code = response.getCode();
532        switch (code) {
533          case 201:
534            uri = response.getLocation();
535            return;
536          case 509:
537            try {
538              Thread.sleep(sleepTime);
539            } catch (InterruptedException e) {
540              throw (InterruptedIOException) new InterruptedIOException().initCause(e);
541            }
542            break;
543          default:
544            throw new IOException("scan request failed with " + code);
545        }
546      }
547      throw new IOException("scan request timed out");
548    }
549
550    @Override
551    public Result[] next(int nbRows) throws IOException {
552      StringBuilder sb = new StringBuilder(uri);
553      sb.append("?n=");
554      sb.append(nbRows);
555      for (int i = 0; i < maxRetries; i++) {
556        Response response = client.get(sb.toString(), 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) throws IOException {
664    Scan scan = new Scan();
665    scan.addColumn(family, qualifier);
666    return new Scanner(scan);
667  }
668
669  public boolean isAutoFlush() {
670    return true;
671  }
672
673  @Override
674  @Deprecated
675  public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put)
676    throws IOException {
677    return doCheckAndPut(row, family, qualifier, value, put);
678  }
679
680  private boolean doCheckAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put)
681    throws IOException {
682    // column to check-the-value
683    put.add(new KeyValue(row, family, qualifier, value));
684
685    CellSetModel model = buildModelFromPut(put);
686    StringBuilder sb = new StringBuilder();
687    sb.append('/');
688    sb.append(Bytes.toString(name));
689    sb.append('/');
690    sb.append(toURLEncodedBytes(put.getRow()));
691    sb.append("?check=put");
692
693    for (int i = 0; i < maxRetries; i++) {
694      Response response =
695        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
696      int code = response.getCode();
697      switch (code) {
698        case 200:
699          return true;
700        case 304: // NOT-MODIFIED
701          return false;
702        case 509:
703          try {
704            Thread.sleep(sleepTime);
705          } catch (final InterruptedException e) {
706            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
707          }
708          break;
709        default:
710          throw new IOException("checkAndPut request failed with " + code);
711      }
712    }
713    throw new IOException("checkAndPut request timed out");
714  }
715
716  @Override
717  @Deprecated
718  public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
719    byte[] value, Put put) throws IOException {
720    throw new IOException("checkAndPut for non-equal comparison not implemented");
721  }
722
723  @Override
724  @Deprecated
725  public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, CompareOperator compareOp,
726    byte[] value, Put put) throws IOException {
727    throw new IOException("checkAndPut for non-equal comparison not implemented");
728  }
729
730  @Override
731  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value,
732    Delete delete) throws IOException {
733    return doCheckAndDelete(row, family, qualifier, value, delete);
734  }
735
736  private boolean doCheckAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value,
737    Delete delete) throws IOException {
738    Put put = new Put(row);
739    put.setFamilyCellMap(delete.getFamilyCellMap());
740    // column to check-the-value
741    put.add(new KeyValue(row, family, qualifier, value));
742    CellSetModel model = buildModelFromPut(put);
743    StringBuilder sb = new StringBuilder();
744    sb.append('/');
745    sb.append(Bytes.toString(name));
746    sb.append('/');
747    sb.append(toURLEncodedBytes(row));
748    sb.append("?check=delete");
749
750    for (int i = 0; i < maxRetries; i++) {
751      Response response =
752        client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
753      int code = response.getCode();
754      switch (code) {
755        case 200:
756          return true;
757        case 304: // NOT-MODIFIED
758          return false;
759        case 509:
760          try {
761            Thread.sleep(sleepTime);
762          } catch (final InterruptedException e) {
763            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
764          }
765          break;
766        default:
767          throw new IOException("checkAndDelete request failed with " + code);
768      }
769    }
770    throw new IOException("checkAndDelete request timed out");
771  }
772
773  @Override
774  @Deprecated
775  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
776    byte[] value, Delete delete) throws IOException {
777    throw new IOException("checkAndDelete for non-equal comparison not implemented");
778  }
779
780  @Override
781  @Deprecated
782  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
783    CompareOperator compareOp, byte[] value, Delete delete) throws IOException {
784    throw new IOException("checkAndDelete for non-equal comparison not implemented");
785  }
786
787  @Override
788  public CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) {
789    return new CheckAndMutateBuilderImpl(row, family);
790  }
791
792  @Override
793  public CheckAndMutateWithFilterBuilder checkAndMutate(byte[] row, Filter filter) {
794    throw new NotImplementedException("Implement later");
795  }
796
797  @Override
798  @Deprecated
799  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier, CompareOp compareOp,
800    byte[] value, RowMutations rm) throws IOException {
801    throw new UnsupportedOperationException("checkAndMutate not implemented");
802  }
803
804  @Override
805  @Deprecated
806  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
807    CompareOperator compareOp, byte[] value, RowMutations rm) throws IOException {
808    throw new UnsupportedOperationException("checkAndMutate not implemented");
809  }
810
811  public CheckAndMutateResult checkAndMutate(CheckAndMutate checkAndMutate) {
812    throw new NotImplementedException("Implement later");
813  }
814
815  @Override
816  public List<CheckAndMutateResult> checkAndMutate(List<CheckAndMutate> checkAndMutates) {
817    throw new NotImplementedException("Implement later");
818  }
819
820  @Override
821  public Result increment(Increment increment) throws IOException {
822    throw new IOException("Increment not supported");
823  }
824
825  @Override
826  public Result append(Append append) throws IOException {
827    throw new IOException("Append not supported");
828  }
829
830  @Override
831  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount)
832    throws IOException {
833    throw new IOException("incrementColumnValue not supported");
834  }
835
836  @Override
837  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount,
838    Durability durability) throws IOException {
839    throw new IOException("incrementColumnValue not supported");
840  }
841
842  @Override
843  public void batch(List<? extends Row> actions, Object[] results) throws IOException {
844    throw new IOException("batch not supported");
845  }
846
847  @Override
848  public <R> void batchCallback(List<? extends Row> actions, Object[] results,
849    Batch.Callback<R> callback) throws IOException, InterruptedException {
850    throw new IOException("batchCallback not supported");
851  }
852
853  @Override
854  public CoprocessorRpcChannel coprocessorService(byte[] row) {
855    throw new UnsupportedOperationException("coprocessorService not implemented");
856  }
857
858  @Override
859  public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service, byte[] startKey,
860    byte[] endKey, Batch.Call<T, R> callable) throws ServiceException, Throwable {
861    throw new UnsupportedOperationException("coprocessorService not implemented");
862  }
863
864  @Override
865  public <T extends Service, R> void coprocessorService(Class<T> service, byte[] startKey,
866    byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
867    throws ServiceException, Throwable {
868    throw new UnsupportedOperationException("coprocessorService not implemented");
869  }
870
871  @Override
872  public Result mutateRow(RowMutations rm) throws IOException {
873    throw new IOException("atomicMutation not supported");
874  }
875
876  @Override
877  public <R extends Message> Map<byte[], R> batchCoprocessorService(
878    Descriptors.MethodDescriptor method, Message request, byte[] startKey, byte[] endKey,
879    R responsePrototype) throws ServiceException, Throwable {
880    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
881  }
882
883  @Override
884  public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
885    Message request, byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback)
886    throws ServiceException, Throwable {
887    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
888  }
889
890  @Override
891  @Deprecated
892  public void setOperationTimeout(int operationTimeout) {
893    throw new UnsupportedOperationException();
894  }
895
896  @Override
897  @Deprecated
898  public int getOperationTimeout() {
899    throw new UnsupportedOperationException();
900  }
901
902  @Override
903  @Deprecated
904  public void setRpcTimeout(int rpcTimeout) {
905    throw new UnsupportedOperationException();
906  }
907
908  @Override
909  public long getReadRpcTimeout(TimeUnit unit) {
910    throw new UnsupportedOperationException();
911  }
912
913  @Override
914  @Deprecated
915  public int getRpcTimeout() {
916    throw new UnsupportedOperationException();
917  }
918
919  @Override
920  public long getRpcTimeout(TimeUnit unit) {
921    throw new UnsupportedOperationException();
922  }
923
924  @Override
925  @Deprecated
926  public int getReadRpcTimeout() {
927    throw new UnsupportedOperationException();
928  }
929
930  @Override
931  @Deprecated
932  public void setReadRpcTimeout(int readRpcTimeout) {
933    throw new UnsupportedOperationException();
934  }
935
936  @Override
937  public long getWriteRpcTimeout(TimeUnit unit) {
938    throw new UnsupportedOperationException();
939  }
940
941  @Override
942  @Deprecated
943  public int getWriteRpcTimeout() {
944    throw new UnsupportedOperationException();
945  }
946
947  @Override
948  @Deprecated
949  public void setWriteRpcTimeout(int writeRpcTimeout) {
950    throw new UnsupportedOperationException();
951  }
952
953  @Override
954  public long getOperationTimeout(TimeUnit unit) {
955    throw new UnsupportedOperationException();
956  }
957
958  /*
959   * Only a small subset of characters are valid in URLs. Row keys, column families, and qualifiers
960   * cannot be appended to URLs without first URL escaping. Table names are ok because they can only
961   * contain alphanumeric, ".","_", and "-" which are valid characters in URLs.
962   */
963  private static String toURLEncodedBytes(byte[] row) {
964    try {
965      return URLEncoder.encode(new String(row, "UTF-8"), "UTF-8");
966    } catch (UnsupportedEncodingException e) {
967      throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
968    }
969  }
970
971  private class CheckAndMutateBuilderImpl implements CheckAndMutateBuilder {
972
973    private final byte[] row;
974    private final byte[] family;
975    private byte[] qualifier;
976    private byte[] value;
977
978    CheckAndMutateBuilderImpl(byte[] row, byte[] family) {
979      this.row = Preconditions.checkNotNull(row, "row is null");
980      this.family = Preconditions.checkNotNull(family, "family is null");
981    }
982
983    @Override
984    public CheckAndMutateBuilder qualifier(byte[] qualifier) {
985      this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null. Consider using"
986        + " an empty byte array, or just do not call this method if you want a null qualifier");
987      return this;
988    }
989
990    @Override
991    public CheckAndMutateBuilder timeRange(TimeRange timeRange) {
992      throw new UnsupportedOperationException("timeRange not implemented");
993    }
994
995    @Override
996    public CheckAndMutateBuilder ifNotExists() {
997      throw new UnsupportedOperationException(
998        "CheckAndMutate for non-equal comparison " + "not implemented");
999    }
1000
1001    @Override
1002    public CheckAndMutateBuilder ifMatches(CompareOperator compareOp, byte[] value) {
1003      if (compareOp == CompareOperator.EQUAL) {
1004        this.value = Preconditions.checkNotNull(value, "value is null");
1005        return this;
1006      } else {
1007        throw new UnsupportedOperationException(
1008          "CheckAndMutate for non-equal comparison " + "not implemented");
1009      }
1010    }
1011
1012    @Override
1013    public CheckAndMutateBuilder ifEquals(byte[] value) {
1014      this.value = Preconditions.checkNotNull(value, "value is null");
1015      return this;
1016    }
1017
1018    @Override
1019    public boolean thenPut(Put put) throws IOException {
1020      return doCheckAndPut(row, family, qualifier, value, put);
1021    }
1022
1023    @Override
1024    public boolean thenDelete(Delete delete) throws IOException {
1025      return doCheckAndDelete(row, family, qualifier, value, delete);
1026    }
1027
1028    @Override
1029    public boolean thenMutate(RowMutations mutation) throws IOException {
1030      throw new UnsupportedOperationException("thenMutate not implemented");
1031    }
1032  }
1033
1034  @Override
1035  public RegionLocator getRegionLocator() throws IOException {
1036    throw new UnsupportedOperationException();
1037  }
1038}