View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.rest.client;
21  
22  import java.io.IOException;
23  import java.io.InterruptedIOException;
24  import java.io.UnsupportedEncodingException;
25  import java.net.URLEncoder;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.TreeMap;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.Cell;
38  import org.apache.hadoop.hbase.CellUtil;
39  import org.apache.hadoop.hbase.HBaseConfiguration;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HTableDescriptor;
42  import org.apache.hadoop.hbase.KeyValue;
43  import org.apache.hadoop.hbase.TableName;
44  import org.apache.hadoop.hbase.classification.InterfaceAudience;
45  import org.apache.hadoop.hbase.classification.InterfaceStability;
46  import org.apache.hadoop.hbase.client.Append;
47  import org.apache.hadoop.hbase.client.Delete;
48  import org.apache.hadoop.hbase.client.Durability;
49  import org.apache.hadoop.hbase.client.Get;
50  import org.apache.hadoop.hbase.client.Increment;
51  import org.apache.hadoop.hbase.client.Put;
52  import org.apache.hadoop.hbase.client.Result;
53  import org.apache.hadoop.hbase.client.ResultScanner;
54  import org.apache.hadoop.hbase.client.Row;
55  import org.apache.hadoop.hbase.client.RowMutations;
56  import org.apache.hadoop.hbase.client.Scan;
57  import org.apache.hadoop.hbase.client.Table;
58  import org.apache.hadoop.hbase.client.coprocessor.Batch;
59  import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
60  import org.apache.hadoop.hbase.client.metrics.ScanMetrics;
61  import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
62  import org.apache.hadoop.hbase.io.TimeRange;
63  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
64  import org.apache.hadoop.hbase.rest.Constants;
65  import org.apache.hadoop.hbase.rest.model.CellModel;
66  import org.apache.hadoop.hbase.rest.model.CellSetModel;
67  import org.apache.hadoop.hbase.rest.model.RowModel;
68  import org.apache.hadoop.hbase.rest.model.ScannerModel;
69  import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
70  import org.apache.hadoop.hbase.util.Bytes;
71  import org.apache.hadoop.util.StringUtils;
72  
73  import com.google.protobuf.Descriptors;
74  import com.google.protobuf.Message;
75  import com.google.protobuf.Service;
76  import com.google.protobuf.ServiceException;
77  
78  /**
79   * HTable interface to remote tables accessed via REST gateway
80   */
81  @InterfaceAudience.Public
82  @InterfaceStability.Stable
83  public class RemoteHTable implements Table {
84  
85    private static final Log LOG = LogFactory.getLog(RemoteHTable.class);
86  
87    final Client client;
88    final Configuration conf;
89    final byte[] name;
90    final int maxRetries;
91    final long sleepTime;
92  
93    @SuppressWarnings("rawtypes")
94    protected String buildRowSpec(final byte[] row, final Map familyMap,
95        final long startTime, final long endTime, final int maxVersions) {
96      StringBuffer sb = new StringBuffer();
97      sb.append('/');
98      sb.append(Bytes.toString(name));
99      sb.append('/');
100     sb.append(toURLEncodedBytes(row));
101     Set families = familyMap.entrySet();
102     if (families != null) {
103       Iterator i = familyMap.entrySet().iterator();
104       sb.append('/');
105       while (i.hasNext()) {
106         Map.Entry e = (Map.Entry)i.next();
107         Collection quals = (Collection)e.getValue();
108         if (quals == null || quals.isEmpty()) {
109           // this is an unqualified family. append the family name and NO ':'
110           sb.append(toURLEncodedBytes((byte[])e.getKey()));
111         } else {
112           Iterator ii = quals.iterator();
113           while (ii.hasNext()) {
114             sb.append(toURLEncodedBytes((byte[])e.getKey()));
115             Object o = ii.next();
116             // Puts use byte[] but Deletes use KeyValue
117             if (o instanceof byte[]) {
118               sb.append(':');
119               sb.append(toURLEncodedBytes((byte[])o));
120             } else if (o instanceof KeyValue) {
121               if (((KeyValue) o).getQualifierLength() != 0) {
122                 sb.append(':');
123                 sb.append(toURLEncodedBytes(((KeyValue) o).getQualifier()));
124               }
125             } else {
126               throw new RuntimeException("object type not handled");
127             }
128             if (ii.hasNext()) {
129               sb.append(',');
130             }
131           }
132         }
133         if (i.hasNext()) {
134           sb.append(',');
135         }
136       }
137     }
138     if (startTime >= 0 && endTime != Long.MAX_VALUE) {
139       sb.append('/');
140       sb.append(startTime);
141       if (startTime != endTime) {
142         sb.append(',');
143         sb.append(endTime);
144       }
145     } else if (endTime != Long.MAX_VALUE) {
146       sb.append('/');
147       sb.append(endTime);
148     }
149     if (maxVersions > 1) {
150       sb.append("?v=");
151       sb.append(maxVersions);
152     }
153     return sb.toString();
154   }
155 
156   protected String buildMultiRowSpec(final byte[][] rows, int maxVersions) {
157     StringBuilder sb = new StringBuilder();
158     sb.append('/');
159     sb.append(Bytes.toString(name));
160     sb.append("/multiget/");
161     if (rows == null || rows.length == 0) {
162       return sb.toString();
163     }
164     sb.append("?");
165     for(int i=0; i<rows.length; i++) {
166       byte[] rk = rows[i];
167       if (i != 0) {
168         sb.append('&');
169       }
170       sb.append("row=");
171       sb.append(toURLEncodedBytes(rk));
172     }
173     sb.append("&v=");
174     sb.append(maxVersions);
175 
176     return sb.toString();
177   }
178 
179   protected Result[] buildResultFromModel(final CellSetModel model) {
180     List<Result> results = new ArrayList<Result>();
181     for (RowModel row: model.getRows()) {
182       List<Cell> kvs = new ArrayList<Cell>();
183       for (CellModel cell: row.getCells()) {
184         byte[][] split = KeyValue.parseColumn(cell.getColumn());
185         byte[] column = split[0];
186         byte[] qualifier = null;
187         if (split.length == 1) {
188           qualifier = HConstants.EMPTY_BYTE_ARRAY;
189         } else if (split.length == 2) {
190           qualifier = split[1];
191         } else {
192           throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
193         }
194         kvs.add(new KeyValue(row.getKey(), column, qualifier,
195           cell.getTimestamp(), cell.getValue()));
196       }
197       results.add(Result.create(kvs));
198     }
199     return results.toArray(new Result[results.size()]);
200   }
201 
202   protected CellSetModel buildModelFromPut(Put put) {
203     RowModel row = new RowModel(put.getRow());
204     long ts = put.getTimeStamp();
205     for (List<Cell> cells: put.getFamilyCellMap().values()) {
206       for (Cell cell: cells) {
207         row.addCell(new CellModel(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell),
208           ts != HConstants.LATEST_TIMESTAMP ? ts : cell.getTimestamp(),
209           CellUtil.cloneValue(cell)));
210       }
211     }
212     CellSetModel model = new CellSetModel();
213     model.addRow(row);
214     return model;
215   }
216 
217   /**
218    * Constructor
219    */
220   public RemoteHTable(Client client, String name) {
221     this(client, HBaseConfiguration.create(), Bytes.toBytes(name));
222   }
223 
224   /**
225    * Constructor
226    */
227   public RemoteHTable(Client client, Configuration conf, String name) {
228     this(client, conf, Bytes.toBytes(name));
229   }
230 
231   /**
232    * Constructor
233    */
234   public RemoteHTable(Client client, Configuration conf, byte[] name) {
235     this.client = client;
236     this.conf = conf;
237     this.name = name;
238     this.maxRetries = conf.getInt("hbase.rest.client.max.retries", 10);
239     this.sleepTime = conf.getLong("hbase.rest.client.sleep", 1000);
240   }
241 
242   public byte[] getTableName() {
243     return name.clone();
244   }
245 
246   @Override
247   public TableName getName() {
248     return TableName.valueOf(name);
249   }
250 
251   @Override
252   public Configuration getConfiguration() {
253     return conf;
254   }
255 
256   @Override
257   public HTableDescriptor getTableDescriptor() throws IOException {
258     StringBuilder sb = new StringBuilder();
259     sb.append('/');
260     sb.append(Bytes.toString(name));
261     sb.append('/');
262     sb.append("schema");
263     for (int i = 0; i < maxRetries; i++) {
264       Response response = client.get(sb.toString(), Constants.MIMETYPE_PROTOBUF);
265       int code = response.getCode();
266       switch (code) {
267       case 200:
268         TableSchemaModel schema = new TableSchemaModel();
269         schema.getObjectFromMessage(response.getBody());
270         return schema.getTableDescriptor();
271       case 509:
272         try {
273           Thread.sleep(sleepTime);
274         } catch (InterruptedException e) {
275           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
276         }
277         break;
278       default:
279         throw new IOException("schema request returned " + code);
280       }
281     }
282     throw new IOException("schema request timed out");
283   }
284 
285   @Override
286   public void close() throws IOException {
287     client.shutdown();
288   }
289 
290   @Override
291   public Result get(Get get) throws IOException {
292     TimeRange range = get.getTimeRange();
293     String spec = buildRowSpec(get.getRow(), get.getFamilyMap(),
294       range.getMin(), range.getMax(), get.getMaxVersions());
295     if (get.getFilter() != null) {
296       LOG.warn("filters not supported on gets");
297     }
298     Result[] results = getResults(spec);
299     if (results.length > 0) {
300       if (results.length > 1) {
301         LOG.warn("too many results for get (" + results.length + ")");
302       }
303       return results[0];
304     } else {
305       return new Result();
306     }
307   }
308 
309   @Override
310   public Result[] get(List<Get> gets) throws IOException {
311     byte[][] rows = new byte[gets.size()][];
312     int maxVersions = 1;
313     int count = 0;
314 
315     for(Get g:gets) {
316 
317       if ( count == 0 ) {
318         maxVersions = g.getMaxVersions();
319       } else if (g.getMaxVersions() != maxVersions) {
320         LOG.warn("MaxVersions on Gets do not match, using the first in the list ("+maxVersions+")");
321       }
322 
323       if (g.getFilter() != null) {
324         LOG.warn("filters not supported on gets");
325       }
326 
327       rows[count] = g.getRow();
328       count ++;
329     }
330 
331     String spec = buildMultiRowSpec(rows, maxVersions);
332 
333     return getResults(spec);
334   }
335 
336   private Result[] getResults(String spec) throws IOException {
337     for (int i = 0; i < maxRetries; i++) {
338       Response response = client.get(spec, Constants.MIMETYPE_PROTOBUF);
339       int code = response.getCode();
340       switch (code) {
341         case 200:
342           CellSetModel model = new CellSetModel();
343           model.getObjectFromMessage(response.getBody());
344           Result[] results = buildResultFromModel(model);
345           if ( results.length > 0) {
346             return results;
347           }
348           // fall through
349         case 404:
350           return new Result[0];
351 
352         case 509:
353           try {
354             Thread.sleep(sleepTime);
355           } catch (InterruptedException e) {
356             throw (InterruptedIOException)new InterruptedIOException().initCause(e);
357           }
358           break;
359         default:
360           throw new IOException("get request returned " + code);
361       }
362     }
363     throw new IOException("get request timed out");
364   }
365 
366   @Override
367   public boolean exists(Get get) throws IOException {
368     LOG.warn("exists() is really get(), just use get()");
369     Result result = get(get);
370     return (result != null && !(result.isEmpty()));
371   }
372 
373   /**
374    * exists(List) is really a list of get() calls. Just use get().
375    * @param gets list of Get to test for the existence
376    */
377   @Override
378   public boolean[] existsAll(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   @Deprecated
388   public Boolean[] exists(List<Get> gets) throws IOException {
389     boolean[] results = existsAll(gets);
390     Boolean[] objectResults = new Boolean[results.length];
391     for (int i = 0; i < results.length; ++i) {
392       objectResults[i] = results[i];
393     }
394     return objectResults;
395   }
396 
397   @Override
398   public void put(Put put) throws IOException {
399     CellSetModel model = buildModelFromPut(put);
400     StringBuilder sb = new StringBuilder();
401     sb.append('/');
402     sb.append(Bytes.toString(name));
403     sb.append('/');
404     sb.append(toURLEncodedBytes(put.getRow()));
405     for (int i = 0; i < maxRetries; i++) {
406       Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
407         model.createProtobufOutput());
408       int code = response.getCode();
409       switch (code) {
410       case 200:
411         return;
412       case 509:
413         try {
414           Thread.sleep(sleepTime);
415         } catch (InterruptedException e) {
416           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
417         }
418         break;
419       default:
420         throw new IOException("put request failed with " + code);
421       }
422     }
423     throw new IOException("put request timed out");
424   }
425 
426   @Override
427   public void put(List<Put> puts) throws IOException {
428     // this is a trick: The gateway accepts multiple rows in a cell set and
429     // ignores the row specification in the URI
430 
431     // separate puts by row
432     TreeMap<byte[],List<Cell>> map =
433       new TreeMap<byte[],List<Cell>>(Bytes.BYTES_COMPARATOR);
434     for (Put put: puts) {
435       byte[] row = put.getRow();
436       List<Cell> cells = map.get(row);
437       if (cells == null) {
438         cells = new ArrayList<Cell>();
439         map.put(row, cells);
440       }
441       for (List<Cell> l: put.getFamilyCellMap().values()) {
442         cells.addAll(l);
443       }
444     }
445 
446     // build the cell set
447     CellSetModel model = new CellSetModel();
448     for (Map.Entry<byte[], List<Cell>> e: map.entrySet()) {
449       RowModel row = new RowModel(e.getKey());
450       for (Cell cell: e.getValue()) {
451         row.addCell(new CellModel(cell));
452       }
453       model.addRow(row);
454     }
455 
456     // build path for multiput
457     StringBuilder sb = new StringBuilder();
458     sb.append('/');
459     sb.append(Bytes.toString(name));
460     sb.append("/$multiput"); // can be any nonexistent row
461     for (int i = 0; i < maxRetries; i++) {
462       Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
463         model.createProtobufOutput());
464       int code = response.getCode();
465       switch (code) {
466       case 200:
467         return;
468       case 509:
469         try {
470           Thread.sleep(sleepTime);
471         } catch (InterruptedException e) {
472           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
473         }
474         break;
475       default:
476         throw new IOException("multiput request failed with " + code);
477       }
478     }
479     throw new IOException("multiput request timed out");
480   }
481 
482   @Override
483   public void delete(Delete delete) throws IOException {
484     String spec = buildRowSpec(delete.getRow(), delete.getFamilyCellMap(),
485       delete.getTimeStamp(), delete.getTimeStamp(), 1);
486     for (int i = 0; i < maxRetries; i++) {
487       Response response = client.delete(spec);
488       int code = response.getCode();
489       switch (code) {
490       case 200:
491         return;
492       case 509:
493         try {
494           Thread.sleep(sleepTime);
495         } catch (InterruptedException e) {
496           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
497         }
498         break;
499       default:
500         throw new IOException("delete request failed with " + code);
501       }
502     }
503     throw new IOException("delete request timed out");
504   }
505 
506   @Override
507   public void delete(List<Delete> deletes) throws IOException {
508     for (Delete delete: deletes) {
509       delete(delete);
510     }
511   }
512 
513   public void flushCommits() throws IOException {
514     // no-op
515   }
516 
517   class Scanner implements ResultScanner {
518 
519     String uri;
520 
521     public Scanner(Scan scan) throws IOException {
522       ScannerModel model;
523       try {
524         model = ScannerModel.fromScan(scan);
525       } catch (Exception e) {
526         throw new IOException(e);
527       }
528       StringBuffer sb = new StringBuffer();
529       sb.append('/');
530       sb.append(Bytes.toString(name));
531       sb.append('/');
532       sb.append("scanner");
533       for (int i = 0; i < maxRetries; i++) {
534         Response response = client.post(sb.toString(),
535           Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
536         int code = response.getCode();
537         switch (code) {
538         case 201:
539           uri = response.getLocation();
540           return;
541         case 509:
542           try {
543             Thread.sleep(sleepTime);
544           } catch (InterruptedException e) {
545             throw (InterruptedIOException)new InterruptedIOException().initCause(e);
546           }
547           break;
548         default:
549           throw new IOException("scan request failed with " + code);
550         }
551       }
552       throw new IOException("scan request timed out");
553     }
554 
555     @Override
556     public Result[] next(int nbRows) throws IOException {
557       StringBuilder sb = new StringBuilder(uri);
558       sb.append("?n=");
559       sb.append(nbRows);
560       for (int i = 0; i < maxRetries; i++) {
561         Response response = client.get(sb.toString(),
562           Constants.MIMETYPE_PROTOBUF);
563         int code = response.getCode();
564         switch (code) {
565         case 200:
566           CellSetModel model = new CellSetModel();
567           model.getObjectFromMessage(response.getBody());
568           return buildResultFromModel(model);
569         case 204:
570         case 206:
571           return null;
572         case 509:
573           try {
574             Thread.sleep(sleepTime);
575           } catch (InterruptedException e) {
576             throw (InterruptedIOException)new InterruptedIOException().initCause(e);
577           }
578           break;
579         default:
580           throw new IOException("scanner.next request failed with " + code);
581         }
582       }
583       throw new IOException("scanner.next request timed out");
584     }
585 
586     @Override
587     public Result next() throws IOException {
588       Result[] results = next(1);
589       if (results == null || results.length < 1) {
590         return null;
591       }
592       return results[0];
593     }
594 
595     class Iter implements Iterator<Result> {
596 
597       Result cache;
598 
599       public Iter() {
600         try {
601           cache = Scanner.this.next();
602         } catch (IOException e) {
603           LOG.warn(StringUtils.stringifyException(e));
604         }
605       }
606 
607       @Override
608       public boolean hasNext() {
609         return cache != null;
610       }
611 
612       @Override
613       public Result next() {
614         Result result = cache;
615         try {
616           cache = Scanner.this.next();
617         } catch (IOException e) {
618           LOG.warn(StringUtils.stringifyException(e));
619           cache = null;
620         }
621         return result;
622       }
623 
624       @Override
625       public void remove() {
626         throw new RuntimeException("remove() not supported");
627       }
628 
629     }
630 
631     @Override
632     public Iterator<Result> iterator() {
633       return new Iter();
634     }
635 
636     @Override
637     public void close() {
638       try {
639         client.delete(uri);
640       } catch (IOException e) {
641         LOG.warn(StringUtils.stringifyException(e));
642       }
643     }
644 
645     @Override
646     public boolean renewLease() {
647       throw new RuntimeException("renewLease() not supported");
648     }
649 
650     @Override
651     public ScanMetrics getScanMetrics() {
652       throw new RuntimeException("getScanMetrics() not supported");
653     }
654   }
655 
656   @Override
657   public ResultScanner getScanner(Scan scan) throws IOException {
658     return new Scanner(scan);
659   }
660 
661   @Override
662   public ResultScanner getScanner(byte[] family) throws IOException {
663     Scan scan = new Scan();
664     scan.addFamily(family);
665     return new Scanner(scan);
666   }
667 
668   @Override
669   public ResultScanner getScanner(byte[] family, byte[] qualifier)
670       throws IOException {
671     Scan scan = new Scan();
672     scan.addColumn(family, qualifier);
673     return new Scanner(scan);
674   }
675 
676   public boolean isAutoFlush() {
677     return true;
678   }
679 
680   public Result getRowOrBefore(byte[] row, byte[] family) throws IOException {
681     throw new IOException("getRowOrBefore not supported");
682   }
683 
684   @Override
685   public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
686       byte[] value, Put put) throws IOException {
687     // column to check-the-value
688     put.add(new KeyValue(row, family, qualifier, value));
689 
690     CellSetModel model = buildModelFromPut(put);
691     StringBuilder sb = new StringBuilder();
692     sb.append('/');
693     sb.append(Bytes.toString(name));
694     sb.append('/');
695     sb.append(toURLEncodedBytes(put.getRow()));
696     sb.append("?check=put");
697 
698     for (int i = 0; i < maxRetries; i++) {
699       Response response = client.put(sb.toString(),
700         Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
701       int code = response.getCode();
702       switch (code) {
703       case 200:
704         return true;
705       case 304: // NOT-MODIFIED
706         return false;
707       case 509:
708         try {
709           Thread.sleep(sleepTime);
710         } catch (final InterruptedException e) {
711           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
712         }
713         break;
714       default:
715         throw new IOException("checkAndPut request failed with " + code);
716       }
717     }
718     throw new IOException("checkAndPut request timed out");
719   }
720 
721   @Override
722   public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
723       CompareOp compareOp, byte[] value, Put put) throws IOException {
724     throw new IOException("checkAndPut for non-equal comparison not implemented");
725   }
726 
727   @Override
728   public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
729       byte[] value, Delete delete) throws IOException {
730     Put put = new Put(row);
731     put.setFamilyCellMap(delete.getFamilyCellMap());
732     // column to check-the-value
733     put.add(new KeyValue(row, family, qualifier, value));
734     CellSetModel model = buildModelFromPut(put);
735     StringBuilder sb = new StringBuilder();
736     sb.append('/');
737     sb.append(Bytes.toString(name));
738     sb.append('/');
739     sb.append(toURLEncodedBytes(row));
740     sb.append("?check=delete");
741 
742     for (int i = 0; i < maxRetries; i++) {
743       Response response = client.put(sb.toString(),
744         Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
745       int code = response.getCode();
746       switch (code) {
747       case 200:
748         return true;
749       case 304: // NOT-MODIFIED
750         return false;
751       case 509:
752         try {
753           Thread.sleep(sleepTime);
754         } catch (final InterruptedException e) {
755           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
756         }
757         break;
758       default:
759         throw new IOException("checkAndDelete request failed with " + code);
760       }
761     }
762     throw new IOException("checkAndDelete request timed out");
763   }
764 
765   @Override
766   public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
767       CompareOp compareOp, byte[] value, Delete delete) throws IOException {
768     throw new IOException("checkAndDelete for non-equal comparison not implemented");
769   }
770 
771   @Override
772   public Result increment(Increment increment) throws IOException {
773     throw new IOException("Increment not supported");
774   }
775 
776   @Override
777   public Result append(Append append) throws IOException {
778     throw new IOException("Append not supported");
779   }
780 
781   @Override
782   public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
783       long amount) throws IOException {
784     throw new IOException("incrementColumnValue not supported");
785   }
786 
787   @Override
788   public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
789       long amount, Durability durability) throws IOException {
790     throw new IOException("incrementColumnValue not supported");
791   }
792 
793   @Override
794   public void batch(List<? extends Row> actions, Object[] results) throws IOException {
795     throw new IOException("batch not supported");
796   }
797 
798   @Override
799   public Object[] batch(List<? extends Row> actions) throws IOException {
800     throw new IOException("batch not supported");
801   }
802 
803   @Override
804   public <R> void batchCallback(List<? extends Row> actions, Object[] results,
805       Batch.Callback<R> callback) throws IOException, InterruptedException {
806     throw new IOException("batchCallback not supported");
807   }
808 
809   @Override
810   public <R> Object[] batchCallback(List<? extends Row> actions, Batch.Callback<R> callback)
811    throws IOException, InterruptedException {
812     throw new IOException("batchCallback not supported");
813   }
814 
815   @Override
816   public CoprocessorRpcChannel coprocessorService(byte[] row) {
817     throw new UnsupportedOperationException("coprocessorService not implemented");
818   }
819 
820   @Override
821   public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
822       byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
823       throws ServiceException, Throwable {
824     throw new UnsupportedOperationException("coprocessorService not implemented");
825   }
826 
827   @Override
828   public <T extends Service, R> void coprocessorService(Class<T> service,
829       byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
830       throws ServiceException, Throwable {
831     throw new UnsupportedOperationException("coprocessorService not implemented");
832   }
833 
834   @Override
835   public void mutateRow(RowMutations rm) throws IOException {
836     throw new IOException("atomicMutation not supported");
837   }
838 
839   @Override
840   public long getWriteBufferSize() {
841     throw new UnsupportedOperationException("getWriteBufferSize not implemented");
842   }
843 
844   @Override
845   public void setWriteBufferSize(long writeBufferSize) throws IOException {
846     throw new IOException("setWriteBufferSize not supported");
847   }
848 
849   @Override
850   public <R extends Message> Map<byte[], R> batchCoprocessorService(
851       Descriptors.MethodDescriptor method, Message request,
852       byte[] startKey, byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
853     throw new UnsupportedOperationException("batchCoprocessorService not implemented");
854   }
855 
856   @Override
857   public <R extends Message> void batchCoprocessorService(
858       Descriptors.MethodDescriptor method, Message request,
859       byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback)
860       throws ServiceException, Throwable {
861     throw new UnsupportedOperationException("batchCoprocessorService not implemented");
862   }
863 
864   @Override public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
865       CompareOp compareOp, byte[] value, RowMutations rm) throws IOException {
866     throw new UnsupportedOperationException("checkAndMutate not implemented");
867   }
868 
869   @Override public void setOperationTimeout(int operationTimeout) {
870     throw new UnsupportedOperationException();
871   }
872 
873   @Override public int getOperationTimeout() {
874     throw new UnsupportedOperationException();
875   }
876 
877   @Override
878   @Deprecated
879   public void setRpcTimeout(int rpcTimeout) {
880     throw new UnsupportedOperationException();
881   }
882 
883   @Override
884   @Deprecated
885   public int getRpcTimeout() {
886     throw new UnsupportedOperationException();
887   }
888 
889   @Override
890   public int getReadRpcTimeout() {
891     throw new UnsupportedOperationException();
892   }
893 
894   @Override
895   public void setReadRpcTimeout(int readRpcTimeout) {
896     throw new UnsupportedOperationException();
897   }
898 
899   @Override
900   public int getWriteRpcTimeout() {
901     throw new UnsupportedOperationException();
902   }
903 
904   @Override
905   public void setWriteRpcTimeout(int writeRpcTimeout) {
906     throw new UnsupportedOperationException();
907   }
908 
909   /*
910    * Only a small subset of characters are valid in URLs.
911    *
912    * Row keys, column families, and qualifiers cannot be appended to URLs without first URL
913    * escaping. Table names are ok because they can only contain alphanumeric, ".","_", and "-"
914    * which are valid characters in URLs.
915    */
916   private static String toURLEncodedBytes(byte[] row) {
917     try {
918       return URLEncoder.encode(new String(row, "UTF-8"), "UTF-8");
919     } catch (UnsupportedEncodingException e) {
920       throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
921     }
922   }
923 }