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 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.Result;
046import org.apache.hadoop.hbase.client.ResultScanner;
047import org.apache.hadoop.hbase.client.Row;
048import org.apache.hadoop.hbase.client.RowMutations;
049import org.apache.hadoop.hbase.client.Scan;
050import org.apache.hadoop.hbase.client.Table;
051import org.apache.hadoop.hbase.client.TableDescriptor;
052import org.apache.hadoop.hbase.client.coprocessor.Batch;
053import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
054import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
055import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
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      CompareOp compareOp, byte[] value, Put put) throws IOException {
721    throw new IOException("checkAndPut for non-equal comparison not implemented");
722  }
723
724  @Override
725  @Deprecated
726  public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
727                             CompareOperator compareOp, byte[] value, Put put) throws IOException {
728    throw new IOException("checkAndPut for non-equal comparison not implemented");
729  }
730
731  @Override
732  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
733      byte[] value, Delete delete) throws IOException {
734    return doCheckAndDelete(row, family, qualifier, value, delete);
735  }
736
737  private boolean doCheckAndDelete(byte[] row, byte[] family, byte[] qualifier,
738      byte[] value, Delete delete) throws IOException {
739    Put put = new Put(row);
740    put.setFamilyCellMap(delete.getFamilyCellMap());
741    // column to check-the-value
742    put.add(new KeyValue(row, family, qualifier, value));
743    CellSetModel model = buildModelFromPut(put);
744    StringBuilder sb = new StringBuilder();
745    sb.append('/');
746    sb.append(Bytes.toString(name));
747    sb.append('/');
748    sb.append(toURLEncodedBytes(row));
749    sb.append("?check=delete");
750
751    for (int i = 0; i < maxRetries; i++) {
752      Response response = client.put(sb.toString(),
753          Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
754      int code = response.getCode();
755      switch (code) {
756      case 200:
757        return true;
758      case 304: // NOT-MODIFIED
759        return false;
760      case 509:
761        try {
762          Thread.sleep(sleepTime);
763        } catch (final InterruptedException e) {
764          throw (InterruptedIOException)new InterruptedIOException().initCause(e);
765        }
766        break;
767      default:
768        throw new IOException("checkAndDelete request failed with " + code);
769      }
770    }
771    throw new IOException("checkAndDelete request timed out");
772  }
773
774  @Override
775  @Deprecated
776  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
777      CompareOp compareOp, byte[] value, Delete delete) throws IOException {
778    throw new IOException("checkAndDelete for non-equal comparison not implemented");
779  }
780
781  @Override
782  @Deprecated
783  public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
784                                CompareOperator compareOp, byte[] value, Delete delete) throws IOException {
785    throw new IOException("checkAndDelete for non-equal comparison not implemented");
786  }
787
788  @Override
789  public CheckAndMutateBuilder checkAndMutate(byte[] row, byte[] family) {
790    return new CheckAndMutateBuilderImpl(row, family);
791  }
792
793  @Override
794  @Deprecated
795  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
796      CompareOp compareOp, byte[] value, RowMutations rm) throws IOException {
797    throw new UnsupportedOperationException("checkAndMutate not implemented");
798  }
799
800  @Override
801  @Deprecated
802  public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
803      CompareOperator compareOp, byte[] value, RowMutations rm) throws IOException {
804    throw new UnsupportedOperationException("checkAndMutate not implemented");
805  }
806
807  @Override
808  public Result increment(Increment increment) throws IOException {
809    throw new IOException("Increment not supported");
810  }
811
812  @Override
813  public Result append(Append append) throws IOException {
814    throw new IOException("Append not supported");
815  }
816
817  @Override
818  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
819      long amount) throws IOException {
820    throw new IOException("incrementColumnValue not supported");
821  }
822
823  @Override
824  public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
825      long amount, Durability durability) throws IOException {
826    throw new IOException("incrementColumnValue not supported");
827  }
828
829  @Override
830  public void batch(List<? extends Row> actions, Object[] results) throws IOException {
831    throw new IOException("batch not supported");
832  }
833
834  @Override
835  public <R> void batchCallback(List<? extends Row> actions, Object[] results,
836      Batch.Callback<R> callback) throws IOException, InterruptedException {
837    throw new IOException("batchCallback not supported");
838  }
839
840  @Override
841  public CoprocessorRpcChannel coprocessorService(byte[] row) {
842    throw new UnsupportedOperationException("coprocessorService not implemented");
843  }
844
845  @Override
846  public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
847      byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
848      throws ServiceException, Throwable {
849    throw new UnsupportedOperationException("coprocessorService not implemented");
850  }
851
852  @Override
853  public <T extends Service, R> void coprocessorService(Class<T> service,
854      byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
855      throws ServiceException, Throwable {
856    throw new UnsupportedOperationException("coprocessorService not implemented");
857  }
858
859  @Override
860  public void mutateRow(RowMutations rm) throws IOException {
861    throw new IOException("atomicMutation not supported");
862  }
863
864  @Override
865  public <R extends Message> Map<byte[], R> batchCoprocessorService(
866      Descriptors.MethodDescriptor method, Message request,
867      byte[] startKey, byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
868    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
869  }
870
871  @Override
872  public <R extends Message> void batchCoprocessorService(
873      Descriptors.MethodDescriptor method, Message request,
874      byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback)
875      throws ServiceException, Throwable {
876    throw new UnsupportedOperationException("batchCoprocessorService not implemented");
877  }
878
879  @Override
880  @Deprecated
881  public void setOperationTimeout(int operationTimeout) {
882    throw new UnsupportedOperationException();
883  }
884
885  @Override
886  @Deprecated
887  public int getOperationTimeout() {
888    throw new UnsupportedOperationException();
889  }
890
891  @Override
892  @Deprecated
893  public void setRpcTimeout(int rpcTimeout) {
894    throw new UnsupportedOperationException();
895  }
896
897  @Override
898  public long getReadRpcTimeout(TimeUnit unit) {
899    throw new UnsupportedOperationException();
900  }
901
902  @Override
903  @Deprecated
904  public int getRpcTimeout() {
905    throw new UnsupportedOperationException();
906  }
907
908  @Override
909  public long getRpcTimeout(TimeUnit unit) {
910    throw new UnsupportedOperationException();
911  }
912
913  @Override
914  @Deprecated
915  public int getReadRpcTimeout() {
916    throw new UnsupportedOperationException();
917  }
918
919  @Override
920  @Deprecated
921  public void setReadRpcTimeout(int readRpcTimeout) {
922    throw new UnsupportedOperationException();
923  }
924
925  @Override
926  public long getWriteRpcTimeout(TimeUnit unit) {
927    throw new UnsupportedOperationException();
928  }
929
930  @Override
931  @Deprecated
932  public int getWriteRpcTimeout() {
933    throw new UnsupportedOperationException();
934  }
935
936  @Override
937  @Deprecated
938  public void setWriteRpcTimeout(int writeRpcTimeout) {
939    throw new UnsupportedOperationException();
940  }
941
942  @Override
943  public long getOperationTimeout(TimeUnit unit) {
944    throw new UnsupportedOperationException();
945  }
946
947  /*
948   * Only a small subset of characters are valid in URLs.
949   *
950   * Row keys, column families, and qualifiers cannot be appended to URLs without first URL
951   * escaping. Table names are ok because they can only contain alphanumeric, ".","_", and "-"
952   * which are valid characters in URLs.
953   */
954  private static String toURLEncodedBytes(byte[] row) {
955    try {
956      return URLEncoder.encode(new String(row, "UTF-8"), "UTF-8");
957    } catch (UnsupportedEncodingException e) {
958      throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
959    }
960  }
961
962  private class CheckAndMutateBuilderImpl implements CheckAndMutateBuilder {
963
964    private final byte[] row;
965    private final byte[] family;
966    private byte[] qualifier;
967    private byte[] value;
968
969    CheckAndMutateBuilderImpl(byte[] row, byte[] family) {
970      this.row = Preconditions.checkNotNull(row, "row is null");
971      this.family = Preconditions.checkNotNull(family, "family is null");
972    }
973
974    @Override
975    public CheckAndMutateBuilder qualifier(byte[] qualifier) {
976      this.qualifier = Preconditions.checkNotNull(qualifier, "qualifier is null. Consider using" +
977          " an empty byte array, or just do not call this method if you want a null qualifier");
978      return this;
979    }
980
981    @Override
982    public CheckAndMutateBuilder timeRange(TimeRange timeRange) {
983      throw new UnsupportedOperationException("timeRange not implemented");
984    }
985
986    @Override
987    public CheckAndMutateBuilder ifNotExists() {
988      throw new UnsupportedOperationException("CheckAndMutate for non-equal comparison "
989          + "not implemented");
990    }
991
992    @Override
993    public CheckAndMutateBuilder ifMatches(CompareOperator compareOp, byte[] value) {
994      if (compareOp == CompareOperator.EQUAL) {
995        this.value = Preconditions.checkNotNull(value, "value is null");
996        return this;
997      } else {
998        throw new UnsupportedOperationException("CheckAndMutate for non-equal comparison " +
999            "not implemented");
1000      }
1001    }
1002
1003    @Override
1004    public CheckAndMutateBuilder ifEquals(byte[] value) {
1005      this.value = Preconditions.checkNotNull(value, "value is null");
1006      return this;
1007    }
1008
1009    @Override
1010    public boolean thenPut(Put put) throws IOException {
1011      return doCheckAndPut(row, family, qualifier, value, put);
1012    }
1013
1014    @Override
1015    public boolean thenDelete(Delete delete) throws IOException {
1016      return doCheckAndDelete(row, family, qualifier, value, delete);
1017    }
1018
1019    @Override
1020    public boolean thenMutate(RowMutations mutation) throws IOException {
1021      throw new UnsupportedOperationException("thenMutate not implemented");
1022    }
1023  }
1024}