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