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.filter.CompareFilter.CompareOp;
61  import org.apache.hadoop.hbase.io.TimeRange;
62  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
63  import org.apache.hadoop.hbase.rest.Constants;
64  import org.apache.hadoop.hbase.rest.model.CellModel;
65  import org.apache.hadoop.hbase.rest.model.CellSetModel;
66  import org.apache.hadoop.hbase.rest.model.RowModel;
67  import org.apache.hadoop.hbase.rest.model.ScannerModel;
68  import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
69  import org.apache.hadoop.hbase.util.Bytes;
70  import org.apache.hadoop.util.StringUtils;
71
72  import com.google.protobuf.Descriptors;
73  import com.google.protobuf.Message;
74  import com.google.protobuf.Service;
75  import com.google.protobuf.ServiceException;
76
77  /**
78   * HTable interface to remote tables accessed via REST gateway
79   */
80  @InterfaceAudience.Public
81  @InterfaceStability.Stable
82  public class RemoteHTable implements Table {
83  
84    private static final Log LOG = LogFactory.getLog(RemoteHTable.class);
85
86    final Client client;
87    final Configuration conf;
88    final byte[] name;
89    final int maxRetries;
90    final long sleepTime;
91
92    @SuppressWarnings("rawtypes")
93    protected String buildRowSpec(final byte[] row, final Map familyMap,
94        final long startTime, final long endTime, final int maxVersions) {
95      StringBuffer sb = new StringBuffer();
96      sb.append('/');
97      sb.append(Bytes.toString(name));
98      sb.append('/');
99      sb.append(toURLEncodedBytes(row));
100     Set families = familyMap.entrySet();
101     if (families != null) {
102       Iterator i = familyMap.entrySet().iterator();
103       sb.append('/');
104       while (i.hasNext()) {
105         Map.Entry e = (Map.Entry)i.next();
106         Collection quals = (Collection)e.getValue();
107         if (quals == null || quals.isEmpty()) {
108           // this is an unqualified family. append the family name and NO ':'
109           sb.append(toURLEncodedBytes((byte[])e.getKey()));
110         } else {
111           Iterator ii = quals.iterator();
112           while (ii.hasNext()) {
113             sb.append(toURLEncodedBytes((byte[])e.getKey()));
114             sb.append(':');
115             Object o = ii.next();
116             // Puts use byte[] but Deletes use KeyValue
117             if (o instanceof byte[]) {
118               sb.append(toURLEncodedBytes((byte[])o));
119             } else if (o instanceof KeyValue) {
120               sb.append(toURLEncodedBytes(CellUtil.cloneQualifier((KeyValue)o)));
121             } else {
122               throw new RuntimeException("object type not handled");
123             }
124             if (ii.hasNext()) {
125               sb.append(',');
126             }
127           }
128         }
129         if (i.hasNext()) {
130           sb.append(',');
131         }
132       }
133     }
134     if (startTime >= 0 && endTime != Long.MAX_VALUE) {
135       sb.append('/');
136       sb.append(startTime);
137       if (startTime != endTime) {
138         sb.append(',');
139         sb.append(endTime);
140       }
141     } else if (endTime != Long.MAX_VALUE) {
142       sb.append('/');
143       sb.append(endTime);
144     }
145     if (maxVersions > 1) {
146       sb.append("?v=");
147       sb.append(maxVersions);
148     }
149     return sb.toString();
150   }
151
152   protected String buildMultiRowSpec(final byte[][] rows, int maxVersions) {
153     StringBuilder sb = new StringBuilder();
154     sb.append('/');
155     sb.append(Bytes.toString(name));
156     sb.append("/multiget/");
157     if (rows == null || rows.length == 0) {
158       return sb.toString();
159     }
160     sb.append("?");
161     for(int i=0; i<rows.length; i++) {
162       byte[] rk = rows[i];
163       if (i != 0) {
164         sb.append('&');
165       }
166       sb.append("row=");
167       sb.append(toURLEncodedBytes(rk));
168     }
169     sb.append("&v=");
170     sb.append(maxVersions);
171
172     return sb.toString();
173   }
174
175   protected Result[] buildResultFromModel(final CellSetModel model) {
176     List<Result> results = new ArrayList<Result>();
177     for (RowModel row: model.getRows()) {
178       List<Cell> kvs = new ArrayList<Cell>();
179       for (CellModel cell: row.getCells()) {
180         byte[][] split = KeyValue.parseColumn(cell.getColumn());
181         byte[] column = split[0];
182         byte[] qualifier = null;
183         if (split.length == 1) {
184           qualifier = HConstants.EMPTY_BYTE_ARRAY;
185         } else if (split.length == 2) {
186           qualifier = split[1];
187         } else {
188           throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
189         }
190         kvs.add(new KeyValue(row.getKey(), column, qualifier,
191           cell.getTimestamp(), cell.getValue()));
192       }
193       results.add(Result.create(kvs));
194     }
195     return results.toArray(new Result[results.size()]);
196   }
197
198   protected CellSetModel buildModelFromPut(Put put) {
199     RowModel row = new RowModel(put.getRow());
200     long ts = put.getTimeStamp();
201     for (List<Cell> cells: put.getFamilyCellMap().values()) {
202       for (Cell cell: cells) {
203         row.addCell(new CellModel(CellUtil.cloneFamily(cell), CellUtil.cloneQualifier(cell),
204           ts != HConstants.LATEST_TIMESTAMP ? ts : cell.getTimestamp(),
205           CellUtil.cloneValue(cell)));
206       }
207     }
208     CellSetModel model = new CellSetModel();
209     model.addRow(row);
210     return model;
211   }
212
213   /**
214    * Constructor
215    */
216   public RemoteHTable(Client client, String name) {
217     this(client, HBaseConfiguration.create(), Bytes.toBytes(name));
218   }
219
220   /**
221    * Constructor
222    */
223   public RemoteHTable(Client client, Configuration conf, String name) {
224     this(client, conf, Bytes.toBytes(name));
225   }
226
227   /**
228    * Constructor
229    */
230   public RemoteHTable(Client client, Configuration conf, byte[] name) {
231     this.client = client;
232     this.conf = conf;
233     this.name = name;
234     this.maxRetries = conf.getInt("hbase.rest.client.max.retries", 10);
235     this.sleepTime = conf.getLong("hbase.rest.client.sleep", 1000);
236   }
237
238   public byte[] getTableName() {
239     return name.clone();
240   }
241
242   @Override
243   public TableName getName() {
244     return TableName.valueOf(name);
245   }
246
247   @Override
248   public Configuration getConfiguration() {
249     return conf;
250   }
251
252   @Override
253   public HTableDescriptor getTableDescriptor() throws IOException {
254     StringBuilder sb = new StringBuilder();
255     sb.append('/');
256     sb.append(Bytes.toString(name));
257     sb.append('/');
258     sb.append("schema");
259     for (int i = 0; i < maxRetries; i++) {
260       Response response = client.get(sb.toString(), Constants.MIMETYPE_PROTOBUF);
261       int code = response.getCode();
262       switch (code) {
263       case 200:
264         TableSchemaModel schema = new TableSchemaModel();
265         schema.getObjectFromMessage(response.getBody());
266         return schema.getTableDescriptor();
267       case 509:
268         try {
269           Thread.sleep(sleepTime);
270         } catch (InterruptedException e) {
271           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
272         }
273         break;
274       default:
275         throw new IOException("schema request returned " + code);
276       }
277     }
278     throw new IOException("schema request timed out");
279   }
280
281   @Override
282   public void close() throws IOException {
283     client.shutdown();
284   }
285
286   @Override
287   public Result get(Get get) throws IOException {
288     TimeRange range = get.getTimeRange();
289     String spec = buildRowSpec(get.getRow(), get.getFamilyMap(),
290       range.getMin(), range.getMax(), get.getMaxVersions());
291     if (get.getFilter() != null) {
292       LOG.warn("filters not supported on gets");
293     }
294     Result[] results = getResults(spec);
295     if (results.length > 0) {
296       if (results.length > 1) {
297         LOG.warn("too many results for get (" + results.length + ")");
298       }
299       return results[0];
300     } else {
301       return new Result();
302     }
303   }
304
305   @Override
306   public Result[] get(List<Get> gets) throws IOException {
307     byte[][] rows = new byte[gets.size()][];
308     int maxVersions = 1;
309     int count = 0;
310
311     for(Get g:gets) {
312
313       if ( count == 0 ) {
314         maxVersions = g.getMaxVersions();
315       } else if (g.getMaxVersions() != maxVersions) {
316         LOG.warn("MaxVersions on Gets do not match, using the first in the list ("+maxVersions+")");
317       }
318
319       if (g.getFilter() != null) {
320         LOG.warn("filters not supported on gets");
321       }
322
323       rows[count] = g.getRow();
324       count ++;
325     }
326
327     String spec = buildMultiRowSpec(rows, maxVersions);
328
329     return getResults(spec);
330   }
331
332   private Result[] getResults(String spec) throws IOException {
333     for (int i = 0; i < maxRetries; i++) {
334       Response response = client.get(spec, Constants.MIMETYPE_PROTOBUF);
335       int code = response.getCode();
336       switch (code) {
337         case 200:
338           CellSetModel model = new CellSetModel();
339           model.getObjectFromMessage(response.getBody());
340           Result[] results = buildResultFromModel(model);
341           if ( results.length > 0) {
342             return results;
343           }
344           // fall through
345         case 404:
346           return new Result[0];
347
348         case 509:
349           try {
350             Thread.sleep(sleepTime);
351           } catch (InterruptedException e) {
352             throw (InterruptedIOException)new InterruptedIOException().initCause(e);
353           }
354           break;
355         default:
356           throw new IOException("get request returned " + code);
357       }
358     }
359     throw new IOException("get request timed out");
360   }
361
362   @Override
363   public boolean exists(Get get) throws IOException {
364     LOG.warn("exists() is really get(), just use get()");
365     Result result = get(get);
366     return (result != null && !(result.isEmpty()));
367   }
368 
369   /**
370    * exists(List) is really a list of get() calls. Just use get().
371    * @param gets list of Get to test for the existence
372    */
373   @Override
374   public boolean[] existsAll(List<Get> gets) throws IOException {
375     LOG.warn("exists(List<Get>) is really list of get() calls, just use get()");
376     boolean[] results = new boolean[gets.size()];
377     for (int i = 0; i < results.length; i++) {
378       results[i] = exists(gets.get(i));
379     }
380     return results;
381   }
382
383   @Deprecated
384   public Boolean[] exists(List<Get> gets) throws IOException {
385     boolean[] results = existsAll(gets);
386     Boolean[] objectResults = new Boolean[results.length];
387     for (int i = 0; i < results.length; ++i) {
388       objectResults[i] = results[i];
389     }
390     return objectResults;
391   }
392
393   @Override
394   public void put(Put put) throws IOException {
395     CellSetModel model = buildModelFromPut(put);
396     StringBuilder sb = new StringBuilder();
397     sb.append('/');
398     sb.append(Bytes.toString(name));
399     sb.append('/');
400     sb.append(toURLEncodedBytes(put.getRow()));
401     for (int i = 0; i < maxRetries; i++) {
402       Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
403         model.createProtobufOutput());
404       int code = response.getCode();
405       switch (code) {
406       case 200:
407         return;
408       case 509:
409         try {
410           Thread.sleep(sleepTime);
411         } catch (InterruptedException e) {
412           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
413         }
414         break;
415       default:
416         throw new IOException("put request failed with " + code);
417       }
418     }
419     throw new IOException("put request timed out");
420   }
421
422   @Override
423   public void put(List<Put> puts) throws IOException {
424     // this is a trick: The gateway accepts multiple rows in a cell set and
425     // ignores the row specification in the URI
426
427     // separate puts by row
428     TreeMap<byte[],List<Cell>> map =
429       new TreeMap<byte[],List<Cell>>(Bytes.BYTES_COMPARATOR);
430     for (Put put: puts) {
431       byte[] row = put.getRow();
432       List<Cell> cells = map.get(row);
433       if (cells == null) {
434         cells = new ArrayList<Cell>();
435         map.put(row, cells);
436       }
437       for (List<Cell> l: put.getFamilyCellMap().values()) {
438         cells.addAll(l);
439       }
440     }
441
442     // build the cell set
443     CellSetModel model = new CellSetModel();
444     for (Map.Entry<byte[], List<Cell>> e: map.entrySet()) {
445       RowModel row = new RowModel(e.getKey());
446       for (Cell cell: e.getValue()) {
447         row.addCell(new CellModel(cell));
448       }
449       model.addRow(row);
450     }
451
452     // build path for multiput
453     StringBuilder sb = new StringBuilder();
454     sb.append('/');
455     sb.append(Bytes.toString(name));
456     sb.append("/$multiput"); // can be any nonexistent row
457     for (int i = 0; i < maxRetries; i++) {
458       Response response = client.put(sb.toString(), Constants.MIMETYPE_PROTOBUF,
459         model.createProtobufOutput());
460       int code = response.getCode();
461       switch (code) {
462       case 200:
463         return;
464       case 509:
465         try {
466           Thread.sleep(sleepTime);
467         } catch (InterruptedException e) {
468           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
469         }
470         break;
471       default:
472         throw new IOException("multiput request failed with " + code);
473       }
474     }
475     throw new IOException("multiput request timed out");
476   }
477
478   @Override
479   public void delete(Delete delete) throws IOException {
480     String spec = buildRowSpec(delete.getRow(), delete.getFamilyCellMap(),
481       delete.getTimeStamp(), delete.getTimeStamp(), 1);
482     for (int i = 0; i < maxRetries; i++) {
483       Response response = client.delete(spec);
484       int code = response.getCode();
485       switch (code) {
486       case 200:
487         return;
488       case 509:
489         try {
490           Thread.sleep(sleepTime);
491         } catch (InterruptedException e) {
492           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
493         }
494         break;
495       default:
496         throw new IOException("delete request failed with " + code);
497       }
498     }
499     throw new IOException("delete request timed out");
500   }
501
502   @Override
503   public void delete(List<Delete> deletes) throws IOException {
504     for (Delete delete: deletes) {
505       delete(delete);
506     }
507   }
508 
509   public void flushCommits() throws IOException {
510     // no-op
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
647   @Override
648   public ResultScanner getScanner(Scan scan) throws IOException {
649     return new Scanner(scan);
650   }
651
652   @Override
653   public ResultScanner getScanner(byte[] family) throws IOException {
654     Scan scan = new Scan();
655     scan.addFamily(family);
656     return new Scanner(scan);
657   }
658 
659   @Override
660   public ResultScanner getScanner(byte[] family, byte[] qualifier)
661       throws IOException {
662     Scan scan = new Scan();
663     scan.addColumn(family, qualifier);
664     return new Scanner(scan);
665   }
666
667   public boolean isAutoFlush() {
668     return true;
669   }
670
671   @Override
672   public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
673       byte[] value, Put put) throws IOException {
674     // column to check-the-value
675     put.add(new KeyValue(row, family, qualifier, value));
676
677     CellSetModel model = buildModelFromPut(put);
678     StringBuilder sb = new StringBuilder();
679     sb.append('/');
680     sb.append(Bytes.toString(name));
681     sb.append('/');
682     sb.append(toURLEncodedBytes(put.getRow()));
683     sb.append("?check=put");
684
685     for (int i = 0; i < maxRetries; i++) {
686       Response response = client.put(sb.toString(),
687         Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
688       int code = response.getCode();
689       switch (code) {
690       case 200:
691         return true;
692       case 304: // NOT-MODIFIED
693         return false;
694       case 509:
695         try {
696           Thread.sleep(sleepTime);
697         } catch (final InterruptedException e) {
698           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
699         }
700         break;
701       default:
702         throw new IOException("checkAndPut request failed with " + code);
703       }
704     }
705     throw new IOException("checkAndPut request timed out");
706   }
707
708   @Override
709   public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
710       CompareOp compareOp, byte[] value, Put put) throws IOException {
711     throw new IOException("checkAndPut for non-equal comparison not implemented");
712   }
713
714   @Override
715   public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
716       byte[] value, Delete delete) throws IOException {
717     Put put = new Put(row);
718     put.setFamilyCellMap(delete.getFamilyCellMap());
719     // column to check-the-value
720     put.add(new KeyValue(row, family, qualifier, value));
721     CellSetModel model = buildModelFromPut(put);
722     StringBuilder sb = new StringBuilder();
723     sb.append('/');
724     sb.append(Bytes.toString(name));
725     sb.append('/');
726     sb.append(toURLEncodedBytes(row));
727     sb.append("?check=delete");
728
729     for (int i = 0; i < maxRetries; i++) {
730       Response response = client.put(sb.toString(),
731         Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
732       int code = response.getCode();
733       switch (code) {
734       case 200:
735         return true;
736       case 304: // NOT-MODIFIED
737         return false;
738       case 509:
739         try {
740           Thread.sleep(sleepTime);
741         } catch (final InterruptedException e) {
742           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
743         }
744         break;
745       default:
746         throw new IOException("checkAndDelete request failed with " + code);
747       }
748     }
749     throw new IOException("checkAndDelete request timed out");
750   }
751
752   @Override
753   public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
754       CompareOp compareOp, byte[] value, Delete delete) throws IOException {
755     throw new IOException("checkAndDelete for non-equal comparison not implemented");
756   }
757
758   @Override
759   public Result increment(Increment increment) throws IOException {
760     throw new IOException("Increment not supported");
761   }
762
763   @Override
764   public Result append(Append append) throws IOException {
765     throw new IOException("Append not supported");
766   }
767
768   @Override
769   public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
770       long amount) throws IOException {
771     throw new IOException("incrementColumnValue not supported");
772   }
773
774   @Override
775   public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier,
776       long amount, Durability durability) throws IOException {
777     throw new IOException("incrementColumnValue not supported");
778   }
779
780   @Override
781   public void batch(List<? extends Row> actions, Object[] results) throws IOException {
782     throw new IOException("batch not supported");
783   }
784
785   @Override
786   public <R> void batchCallback(List<? extends Row> actions, Object[] results,
787       Batch.Callback<R> callback) throws IOException, InterruptedException {
788     throw new IOException("batchCallback not supported");
789   }
790
791   @Override
792   public CoprocessorRpcChannel coprocessorService(byte[] row) {
793     throw new UnsupportedOperationException("coprocessorService not implemented");
794   }
795
796   @Override
797   public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
798       byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
799       throws ServiceException, Throwable {
800     throw new UnsupportedOperationException("coprocessorService not implemented");
801   }
802 
803   @Override
804   public <T extends Service, R> void coprocessorService(Class<T> service,
805       byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
806       throws ServiceException, Throwable {
807     throw new UnsupportedOperationException("coprocessorService not implemented");
808   }
809 
810   @Override
811   public void mutateRow(RowMutations rm) throws IOException {
812     throw new IOException("atomicMutation not supported");
813   }
814
815   @Override
816   public long getWriteBufferSize() {
817     throw new UnsupportedOperationException("getWriteBufferSize not implemented");
818   }
819
820   @Override
821   public void setWriteBufferSize(long writeBufferSize) throws IOException {
822     throw new IOException("setWriteBufferSize not supported");
823   }
824
825   @Override
826   public <R extends Message> Map<byte[], R> batchCoprocessorService(
827       Descriptors.MethodDescriptor method, Message request,
828       byte[] startKey, byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
829     throw new UnsupportedOperationException("batchCoprocessorService not implemented");
830   }
831 
832   @Override
833   public <R extends Message> void batchCoprocessorService(
834       Descriptors.MethodDescriptor method, Message request,
835       byte[] startKey, byte[] endKey, R responsePrototype, Callback<R> callback)
836       throws ServiceException, Throwable {
837     throw new UnsupportedOperationException("batchCoprocessorService not implemented");
838   }
839
840   @Override public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
841       CompareOp compareOp, byte[] value, RowMutations rm) throws IOException {
842     throw new UnsupportedOperationException("checkAndMutate not implemented");
843   }
844
845   @Override
846   public void setOperationTimeout(int operationTimeout) {
847     throw new UnsupportedOperationException();
848   }
849
850   @Override
851   public int getOperationTimeout() {
852     throw new UnsupportedOperationException();
853   }
854
855   @Override
856   @Deprecated
857   public void setRpcTimeout(int rpcTimeout) {
858     throw new UnsupportedOperationException();
859   }
860
861   @Override
862   @Deprecated
863   public int getRpcTimeout() {
864     throw new UnsupportedOperationException();
865   }
866
867   @Override
868   public int getReadRpcTimeout() {
869     throw new UnsupportedOperationException();
870   }
871
872   @Override
873   public void setReadRpcTimeout(int readRpcTimeout) {
874     throw new UnsupportedOperationException();
875   }
876
877   @Override
878   public int getWriteRpcTimeout() {
879     throw new UnsupportedOperationException();
880   }
881
882   @Override
883   public void setWriteRpcTimeout(int writeRpcTimeout) {
884     throw new UnsupportedOperationException();
885   }
886
887   /*
888    * Only a small subset of characters are valid in URLs.
889    *
890    * Row keys, column families, and qualifiers cannot be appended to URLs without first URL
891    * escaping. Table names are ok because they can only contain alphanumeric, ".","_", and "-"
892    * which are valid characters in URLs.
893    */
894   private static String toURLEncodedBytes(byte[] row) {
895     try {
896       return URLEncoder.encode(new String(row, "UTF-8"), "UTF-8");
897     } catch (UnsupportedEncodingException e) {
898       throw new IllegalStateException("URLEncoder doesn't support UTF-8", e);
899     }
900   }
901 }