View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.thrift;
20  
21  import static org.apache.hadoop.hbase.util.Bytes.getBytes;
22  
23  import java.io.IOException;
24  import java.net.InetAddress;
25  import java.net.InetSocketAddress;
26  import java.net.UnknownHostException;
27  import java.nio.ByteBuffer;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.TreeMap;
35  import java.util.concurrent.BlockingQueue;
36  import java.util.concurrent.ExecutorService;
37  import java.util.concurrent.LinkedBlockingQueue;
38  import java.util.concurrent.ThreadPoolExecutor;
39  import java.util.concurrent.TimeUnit;
40  
41  import org.apache.commons.cli.CommandLine;
42  import org.apache.commons.cli.Option;
43  import org.apache.commons.cli.OptionGroup;
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  import org.apache.hadoop.conf.Configuration;
47  import org.apache.hadoop.hbase.HBaseConfiguration;
48  import org.apache.hadoop.hbase.HColumnDescriptor;
49  import org.apache.hadoop.hbase.HConstants;
50  import org.apache.hadoop.hbase.HRegionInfo;
51  import org.apache.hadoop.hbase.HTableDescriptor;
52  import org.apache.hadoop.hbase.KeyValue;
53  import org.apache.hadoop.hbase.ServerName;
54  import org.apache.hadoop.hbase.TableNotFoundException;
55  import org.apache.hadoop.hbase.client.Delete;
56  import org.apache.hadoop.hbase.client.Get;
57  import org.apache.hadoop.hbase.client.HBaseAdmin;
58  import org.apache.hadoop.hbase.client.HTable;
59  import org.apache.hadoop.hbase.client.Increment;
60  import org.apache.hadoop.hbase.client.OperationWithAttributes;
61  import org.apache.hadoop.hbase.client.Put;
62  import org.apache.hadoop.hbase.client.Result;
63  import org.apache.hadoop.hbase.client.ResultScanner;
64  import org.apache.hadoop.hbase.client.Scan;
65  import org.apache.hadoop.hbase.filter.Filter;
66  import org.apache.hadoop.hbase.filter.ParseFilter;
67  import org.apache.hadoop.hbase.filter.PrefixFilter;
68  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
69  import org.apache.hadoop.hbase.thrift.CallQueue.Call;
70  import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
71  import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
72  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
73  import org.apache.hadoop.hbase.thrift.generated.Hbase;
74  import org.apache.hadoop.hbase.thrift.generated.IOError;
75  import org.apache.hadoop.hbase.thrift.generated.IllegalArgument;
76  import org.apache.hadoop.hbase.thrift.generated.Mutation;
77  import org.apache.hadoop.hbase.thrift.generated.TCell;
78  import org.apache.hadoop.hbase.thrift.generated.TIncrement;
79  import org.apache.hadoop.hbase.thrift.generated.TRegionInfo;
80  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
81  import org.apache.hadoop.hbase.thrift.generated.TScan;
82  import org.apache.hadoop.hbase.util.Addressing;
83  import org.apache.hadoop.hbase.util.Bytes;
84  import org.apache.hadoop.hbase.util.Writables;
85  import org.apache.thrift.TException;
86  import org.apache.thrift.protocol.TBinaryProtocol;
87  import org.apache.thrift.protocol.TCompactProtocol;
88  import org.apache.thrift.protocol.TProtocolFactory;
89  import org.apache.thrift.server.THsHaServer;
90  import org.apache.thrift.server.TNonblockingServer;
91  import org.apache.thrift.server.TServer;
92  import org.apache.thrift.server.TThreadedSelectorServer;
93  import org.apache.thrift.transport.TFramedTransport;
94  import org.apache.thrift.transport.TNonblockingServerSocket;
95  import org.apache.thrift.transport.TNonblockingServerTransport;
96  import org.apache.thrift.transport.TServerSocket;
97  import org.apache.thrift.transport.TServerTransport;
98  import org.apache.thrift.transport.TTransportFactory;
99  
100 import com.google.common.base.Joiner;
101 import com.google.common.util.concurrent.ThreadFactoryBuilder;
102 
103 /**
104  * ThriftServerRunner - this class starts up a Thrift server which implements
105  * the Hbase API specified in the Hbase.thrift IDL file.
106  */
107 public class ThriftServerRunner implements Runnable {
108 
109   private static final Log LOG = LogFactory.getLog(ThriftServerRunner.class);
110 
111   static final String SERVER_TYPE_CONF_KEY =
112       "hbase.regionserver.thrift.server.type";
113 
114   static final String BIND_CONF_KEY = "hbase.regionserver.thrift.ipaddress";
115   static final String COMPACT_CONF_KEY = "hbase.regionserver.thrift.compact";
116   static final String FRAMED_CONF_KEY = "hbase.regionserver.thrift.framed";
117   static final String PORT_CONF_KEY = "hbase.regionserver.thrift.port";
118   static final String COALESCE_INC_KEY = "hbase.regionserver.thrift.coalesceIncrement";
119 
120   private static final String DEFAULT_BIND_ADDR = "0.0.0.0";
121   public static final int DEFAULT_LISTEN_PORT = 9090;
122   private final int listenPort;
123 
124   private Configuration conf;
125   volatile TServer tserver;
126   private final Hbase.Iface handler;
127   private final ThriftMetrics metrics;
128 
129   /** An enum of server implementation selections */
130   enum ImplType {
131     HS_HA("hsha", true, THsHaServer.class, false),
132     NONBLOCKING("nonblocking", true, TNonblockingServer.class, false),
133     THREAD_POOL("threadpool", false, TBoundedThreadPoolServer.class, true),
134     THREADED_SELECTOR(
135         "threadedselector", true, TThreadedSelectorServer.class, false);
136 
137     public static final ImplType DEFAULT = THREAD_POOL;
138 
139     final String option;
140     final boolean isAlwaysFramed;
141     final Class<? extends TServer> serverClass;
142     final boolean canSpecifyBindIP;
143 
144     ImplType(String option, boolean isAlwaysFramed,
145         Class<? extends TServer> serverClass, boolean canSpecifyBindIP) {
146       this.option = option;
147       this.isAlwaysFramed = isAlwaysFramed;
148       this.serverClass = serverClass;
149       this.canSpecifyBindIP = canSpecifyBindIP;
150     }
151 
152     /**
153      * @return <code>-option</code> so we can get the list of options from
154      *         {@link #values()}
155      */
156     @Override
157     public String toString() {
158       return "-" + option;
159     }
160 
161     String getDescription() {
162       StringBuilder sb = new StringBuilder("Use the " +
163           serverClass.getSimpleName());
164       if (isAlwaysFramed) {
165         sb.append(" This implies the framed transport.");
166       }
167       if (this == DEFAULT) {
168         sb.append("This is the default.");
169       }
170       return sb.toString();
171     }
172 
173     static OptionGroup createOptionGroup() {
174       OptionGroup group = new OptionGroup();
175       for (ImplType t : values()) {
176         group.addOption(new Option(t.option, t.getDescription()));
177       }
178       return group;
179     }
180 
181     static ImplType getServerImpl(Configuration conf) {
182       String confType = conf.get(SERVER_TYPE_CONF_KEY, THREAD_POOL.option);
183       for (ImplType t : values()) {
184         if (confType.equals(t.option)) {
185           return t;
186         }
187       }
188       throw new AssertionError("Unknown server ImplType.option:" + confType);
189     }
190 
191     static void setServerImpl(CommandLine cmd, Configuration conf) {
192       ImplType chosenType = null;
193       int numChosen = 0;
194       for (ImplType t : values()) {
195         if (cmd.hasOption(t.option)) {
196           chosenType = t;
197           ++numChosen;
198         }
199       }
200       if (numChosen < 1) {
201         LOG.info("Using default thrift server type");
202         chosenType = DEFAULT;
203       } else if (numChosen > 1) {
204         throw new AssertionError("Exactly one option out of " +
205           Arrays.toString(values()) + " has to be specified");
206       }
207       LOG.info("Using thrift server type " + chosenType.option);
208       conf.set(SERVER_TYPE_CONF_KEY, chosenType.option);
209     }
210 
211     public String simpleClassName() {
212       return serverClass.getSimpleName();
213     }
214 
215     public static List<String> serversThatCannotSpecifyBindIP() {
216       List<String> l = new ArrayList<String>();
217       for (ImplType t : values()) {
218         if (!t.canSpecifyBindIP) {
219           l.add(t.simpleClassName());
220         }
221       }
222       return l;
223     }
224 
225   }
226 
227   public ThriftServerRunner(Configuration conf) throws IOException {
228     this(conf, new ThriftServerRunner.HBaseHandler(conf));
229   }
230 
231   public ThriftServerRunner(Configuration conf, HBaseHandler handler) {
232     this.conf = HBaseConfiguration.create(conf);
233     this.listenPort = conf.getInt(PORT_CONF_KEY, DEFAULT_LISTEN_PORT);
234     this.metrics = new ThriftMetrics(listenPort, conf, Hbase.Iface.class);
235     handler.initMetrics(metrics);
236     this.handler = HbaseHandlerMetricsProxy.newInstance(handler, metrics, conf);
237   }
238 
239   /*
240    * Runs the Thrift server
241    */
242   @Override
243   public void run() {
244     try {
245       setupServer();
246       tserver.serve();
247     } catch (Exception e) {
248       LOG.fatal("Cannot run ThriftServer", e);
249       // Crash the process if the ThriftServer is not running
250       System.exit(-1);
251     }
252   }
253 
254   public void shutdown() {
255     if (tserver != null) {
256       tserver.stop();
257       tserver = null;
258     }
259   }
260 
261   /**
262    * Setting up the thrift TServer
263    */
264   private void setupServer() throws Exception {
265     // Construct correct ProtocolFactory
266     TProtocolFactory protocolFactory;
267     if (conf.getBoolean(COMPACT_CONF_KEY, false)) {
268       LOG.debug("Using compact protocol");
269       protocolFactory = new TCompactProtocol.Factory();
270     } else {
271       LOG.debug("Using binary protocol");
272       protocolFactory = new TBinaryProtocol.Factory();
273     }
274 
275     Hbase.Processor<Hbase.Iface> processor =
276         new Hbase.Processor<Hbase.Iface>(handler);
277     ImplType implType = ImplType.getServerImpl(conf);
278 
279     // Construct correct TransportFactory
280     TTransportFactory transportFactory;
281     if (conf.getBoolean(FRAMED_CONF_KEY, false) || implType.isAlwaysFramed) {
282       transportFactory = new TFramedTransport.Factory();
283       LOG.debug("Using framed transport");
284     } else {
285       transportFactory = new TTransportFactory();
286     }
287 
288     if (conf.get(BIND_CONF_KEY) != null && !implType.canSpecifyBindIP) {
289       LOG.error("Server types " + Joiner.on(", ").join(
290           ImplType.serversThatCannotSpecifyBindIP()) + " don't support IP " +
291           "address binding at the moment. See " +
292           "https://issues.apache.org/jira/browse/HBASE-2155 for details.");
293       throw new RuntimeException(
294           "-" + BIND_CONF_KEY + " not supported with " + implType);
295     }
296 
297     if (implType == ImplType.HS_HA || implType == ImplType.NONBLOCKING ||
298         implType == ImplType.THREADED_SELECTOR) {
299 
300       TNonblockingServerTransport serverTransport =
301           new TNonblockingServerSocket(listenPort);
302 
303       if (implType == ImplType.NONBLOCKING) {
304         TNonblockingServer.Args serverArgs =
305             new TNonblockingServer.Args(serverTransport);
306         serverArgs.processor(processor)
307                   .transportFactory(transportFactory)
308                   .protocolFactory(protocolFactory);
309         tserver = new TNonblockingServer(serverArgs);
310       } else if (implType == ImplType.HS_HA) {
311         THsHaServer.Args serverArgs = new THsHaServer.Args(serverTransport);
312         CallQueue callQueue =
313             new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
314         ExecutorService executorService = createExecutor(
315             callQueue, serverArgs.getWorkerThreads());
316         serverArgs.executorService(executorService)
317                   .processor(processor)
318                   .transportFactory(transportFactory)
319                   .protocolFactory(protocolFactory);
320         tserver = new THsHaServer(serverArgs);
321       } else { // THREADED_SELECTOR
322         TThreadedSelectorServer.Args serverArgs =
323             new HThreadedSelectorServerArgs(serverTransport, conf);
324         CallQueue callQueue =
325             new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
326         ExecutorService executorService = createExecutor(
327             callQueue, serverArgs.getWorkerThreads());
328         serverArgs.executorService(executorService)
329                   .processor(processor)
330                   .transportFactory(transportFactory)
331                   .protocolFactory(protocolFactory);
332         tserver = new TThreadedSelectorServer(serverArgs);
333       }
334       LOG.info("starting HBase " + implType.simpleClassName() +
335           " server on " + Integer.toString(listenPort));
336     } else if (implType == ImplType.THREAD_POOL) {
337       // Thread pool server. Get the IP address to bind to.
338       InetAddress listenAddress = getBindAddress(conf);
339 
340       TServerTransport serverTransport = new TServerSocket(
341           new InetSocketAddress(listenAddress, listenPort));
342 
343       TBoundedThreadPoolServer.Args serverArgs =
344           new TBoundedThreadPoolServer.Args(serverTransport, conf);
345       serverArgs.processor(processor)
346                 .transportFactory(transportFactory)
347                 .protocolFactory(protocolFactory);
348       LOG.info("starting " + ImplType.THREAD_POOL.simpleClassName() + " on "
349           + listenAddress + ":" + Integer.toString(listenPort)
350           + "; " + serverArgs);
351       TBoundedThreadPoolServer tserver =
352           new TBoundedThreadPoolServer(serverArgs, metrics);
353       this.tserver = tserver;
354     } else {
355       throw new AssertionError("Unsupported Thrift server implementation: " +
356           implType.simpleClassName());
357     }
358 
359     // A sanity check that we instantiated the right type of server.
360     if (tserver.getClass() != implType.serverClass) {
361       throw new AssertionError("Expected to create Thrift server class " +
362           implType.serverClass.getName() + " but got " +
363           tserver.getClass().getName());
364     }
365 
366     registerFilters(conf);
367   }
368 
369   ExecutorService createExecutor(BlockingQueue<Runnable> callQueue,
370                                  int workerThreads) {
371     ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
372     tfb.setDaemon(true);
373     tfb.setNameFormat("thrift-worker-%d");
374     return new ThreadPoolExecutor(workerThreads, workerThreads,
375             Long.MAX_VALUE, TimeUnit.SECONDS, callQueue, tfb.build());
376   }
377 
378   private InetAddress getBindAddress(Configuration conf)
379       throws UnknownHostException {
380     String bindAddressStr = conf.get(BIND_CONF_KEY, DEFAULT_BIND_ADDR);
381     return InetAddress.getByName(bindAddressStr);
382   }
383 
384   /**
385    * The HBaseHandler is a glue object that connects Thrift RPC calls to the
386    * HBase client API primarily defined in the HBaseAdmin and HTable objects.
387    */
388   public static class HBaseHandler implements Hbase.Iface {
389     protected Configuration conf;
390     protected HBaseAdmin admin = null;
391     protected final Log LOG = LogFactory.getLog(this.getClass().getName());
392 
393     // nextScannerId and scannerMap are used to manage scanner state
394     protected int nextScannerId = 0;
395     protected HashMap<Integer, ResultScanner> scannerMap = null;
396     private ThriftMetrics metrics = null;
397 
398     private static ThreadLocal<Map<String, HTable>> threadLocalTables =
399         new ThreadLocal<Map<String, HTable>>() {
400       @Override
401       protected Map<String, HTable> initialValue() {
402         return new TreeMap<String, HTable>();
403       }
404     };
405 
406     IncrementCoalescer coalescer = null;
407 
408     /**
409      * Returns a list of all the column families for a given htable.
410      *
411      * @param table
412      * @return
413      * @throws IOException
414      */
415     byte[][] getAllColumns(HTable table) throws IOException {
416       HColumnDescriptor[] cds = table.getTableDescriptor().getColumnFamilies();
417       byte[][] columns = new byte[cds.length][];
418       for (int i = 0; i < cds.length; i++) {
419         columns[i] = Bytes.add(cds[i].getName(),
420             KeyValue.COLUMN_FAMILY_DELIM_ARRAY);
421       }
422       return columns;
423     }
424 
425     /**
426      * Creates and returns an HTable instance from a given table name.
427      *
428      * @param tableName
429      *          name of table
430      * @return HTable object
431      * @throws IOException
432      * @throws IOError
433      */
434     public HTable getTable(final byte[] tableName) throws
435         IOException {
436       String table = new String(tableName);
437       Map<String, HTable> tables = threadLocalTables.get();
438       if (!tables.containsKey(table)) {
439         tables.put(table, new HTable(conf, tableName));
440       }
441       return tables.get(table);
442     }
443 
444     public HTable getTable(final ByteBuffer tableName) throws IOException {
445       return getTable(getBytes(tableName));
446     }
447 
448     /**
449      * Assigns a unique ID to the scanner and adds the mapping to an internal
450      * hash-map.
451      *
452      * @param scanner
453      * @return integer scanner id
454      */
455     protected synchronized int addScanner(ResultScanner scanner) {
456       int id = nextScannerId++;
457       scannerMap.put(id, scanner);
458       return id;
459     }
460 
461     /**
462      * Returns the scanner associated with the specified ID.
463      *
464      * @param id
465      * @return a Scanner, or null if ID was invalid.
466      */
467     protected synchronized ResultScanner getScanner(int id) {
468       return scannerMap.get(id);
469     }
470 
471     /**
472      * Removes the scanner associated with the specified ID from the internal
473      * id->scanner hash-map.
474      *
475      * @param id
476      * @return a Scanner, or null if ID was invalid.
477      */
478     protected synchronized ResultScanner removeScanner(int id) {
479       return scannerMap.remove(id);
480     }
481 
482     /**
483      * Constructs an HBaseHandler object.
484      * @throws IOException
485      */
486     protected HBaseHandler()
487     throws IOException {
488       this(HBaseConfiguration.create());
489     }
490 
491     protected HBaseHandler(final Configuration c) throws IOException {
492       this.conf = c;
493       admin = new HBaseAdmin(conf);
494       scannerMap = new HashMap<Integer, ResultScanner>();
495       this.coalescer = new IncrementCoalescer(this);
496     }
497 
498     @Override
499     public void enableTable(ByteBuffer tableName) throws IOError {
500       try{
501         admin.enableTable(getBytes(tableName));
502       } catch (IOException e) {
503         LOG.warn(e.getMessage(), e);
504         throw new IOError(e.getMessage());
505       }
506     }
507 
508     @Override
509     public void disableTable(ByteBuffer tableName) throws IOError{
510       try{
511         admin.disableTable(getBytes(tableName));
512       } catch (IOException e) {
513         LOG.warn(e.getMessage(), e);
514         throw new IOError(e.getMessage());
515       }
516     }
517 
518     @Override
519     public boolean isTableEnabled(ByteBuffer tableName) throws IOError {
520       try {
521         return HTable.isTableEnabled(this.conf, getBytes(tableName));
522       } catch (IOException e) {
523         LOG.warn(e.getMessage(), e);
524         throw new IOError(e.getMessage());
525       }
526     }
527 
528     @Override
529     public void compact(ByteBuffer tableNameOrRegionName) throws IOError {
530       try{
531         admin.compact(getBytes(tableNameOrRegionName));
532       } catch (InterruptedException e) {
533         throw new IOError(e.getMessage());
534       } catch (IOException e) {
535         LOG.warn(e.getMessage(), e);
536         throw new IOError(e.getMessage());
537       }
538     }
539 
540     @Override
541     public void majorCompact(ByteBuffer tableNameOrRegionName) throws IOError {
542       try{
543         admin.majorCompact(getBytes(tableNameOrRegionName));
544       } catch (InterruptedException e) {
545         LOG.warn(e.getMessage(), e);
546         throw new IOError(e.getMessage());
547       } catch (IOException e) {
548         LOG.warn(e.getMessage(), e);
549         throw new IOError(e.getMessage());
550       }
551     }
552 
553     @Override
554     public List<ByteBuffer> getTableNames() throws IOError {
555       try {
556         HTableDescriptor[] tables = this.admin.listTables();
557         ArrayList<ByteBuffer> list = new ArrayList<ByteBuffer>(tables.length);
558         for (int i = 0; i < tables.length; i++) {
559           list.add(ByteBuffer.wrap(tables[i].getName()));
560         }
561         return list;
562       } catch (IOException e) {
563         LOG.warn(e.getMessage(), e);
564         throw new IOError(e.getMessage());
565       }
566     }
567 
568     @Override
569     public List<TRegionInfo> getTableRegions(ByteBuffer tableName)
570     throws IOError {
571       try {
572         HTable table = getTable(tableName);
573         Map<HRegionInfo, ServerName> regionLocations =
574             table.getRegionLocations();
575         List<TRegionInfo> results = new ArrayList<TRegionInfo>();
576         for (Map.Entry<HRegionInfo, ServerName> entry :
577             regionLocations.entrySet()) {
578           HRegionInfo info = entry.getKey();
579           ServerName serverName = entry.getValue();
580           TRegionInfo region = new TRegionInfo();
581           region.serverName = ByteBuffer.wrap(
582               Bytes.toBytes(serverName.getHostname()));
583           region.port = serverName.getPort();
584           region.startKey = ByteBuffer.wrap(info.getStartKey());
585           region.endKey = ByteBuffer.wrap(info.getEndKey());
586           region.id = info.getRegionId();
587           region.name = ByteBuffer.wrap(info.getRegionName());
588           region.version = info.getVersion();
589           results.add(region);
590         }
591         return results;
592       } catch (TableNotFoundException e) {
593         // Return empty list for non-existing table
594         return Collections.emptyList();
595       } catch (IOException e){
596         LOG.warn(e.getMessage(), e);
597         throw new IOError(e.getMessage());
598       }
599     }
600 
601     /**
602      * Convert ByteBuffer to byte array. Note that this cannot be replaced by
603      * Bytes.toBytes().
604      */
605     public static byte[] toBytes(ByteBuffer bb) {
606       byte[] result = new byte[bb.remaining()];
607       // Make a duplicate so the position doesn't change
608       ByteBuffer dup = bb.duplicate();
609       dup.get(result, 0, result.length);
610       return result;
611     }
612 
613     @Deprecated
614     @Override
615     public List<TCell> get(
616         ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
617         Map<ByteBuffer, ByteBuffer> attributes)
618         throws IOError {
619       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
620       if(famAndQf.length == 1) {
621         return get(tableName, row, famAndQf[0], new byte[0], attributes);
622       }
623       return get(tableName, row, famAndQf[0], famAndQf[1], attributes);
624     }
625 
626     protected List<TCell> get(ByteBuffer tableName,
627                               ByteBuffer row,
628                               byte[] family,
629                               byte[] qualifier,
630                               Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
631       try {
632         HTable table = getTable(tableName);
633         Get get = new Get(getBytes(row));
634         addAttributes(get, attributes);
635         if (qualifier == null || qualifier.length == 0) {
636           get.addFamily(family);
637         } else {
638           get.addColumn(family, qualifier);
639         }
640         Result result = table.get(get);
641         return ThriftUtilities.cellFromHBase(result.raw());
642       } catch (IOException e) {
643         LOG.warn(e.getMessage(), e);
644         throw new IOError(e.getMessage());
645       }
646     }
647 
648     @Deprecated
649     @Override
650     public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row,
651         ByteBuffer column, int numVersions,
652         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
653       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
654       if(famAndQf.length == 1) {
655         return getVer(tableName, row, famAndQf[0],
656             new byte[0], numVersions, attributes);
657       }
658       return getVer(tableName, row,
659           famAndQf[0], famAndQf[1], numVersions, attributes);
660     }
661 
662     public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row,
663                               byte[] family,
664         byte[] qualifier, int numVersions,
665         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
666       try {
667         HTable table = getTable(tableName);
668         Get get = new Get(getBytes(row));
669         addAttributes(get, attributes);
670         get.addColumn(family, qualifier);
671         get.setMaxVersions(numVersions);
672         Result result = table.get(get);
673         return ThriftUtilities.cellFromHBase(result.raw());
674       } catch (IOException e) {
675         LOG.warn(e.getMessage(), e);
676         throw new IOError(e.getMessage());
677       }
678     }
679 
680     @Deprecated
681     @Override
682     public List<TCell> getVerTs(ByteBuffer tableName,
683                                    ByteBuffer row,
684         ByteBuffer column,
685         long timestamp,
686         int numVersions,
687         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
688       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
689       if(famAndQf.length == 1) {
690         return getVerTs(tableName, row, famAndQf[0], new byte[0], timestamp,
691             numVersions, attributes);
692       }
693       return getVerTs(tableName, row, famAndQf[0], famAndQf[1], timestamp,
694           numVersions, attributes);
695     }
696 
697     protected List<TCell> getVerTs(ByteBuffer tableName,
698                                    ByteBuffer row, byte [] family,
699         byte [] qualifier, long timestamp, int numVersions,
700         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
701       try {
702         HTable table = getTable(tableName);
703         Get get = new Get(getBytes(row));
704         addAttributes(get, attributes);
705         get.addColumn(family, qualifier);
706         get.setTimeRange(Long.MIN_VALUE, timestamp);
707         get.setMaxVersions(numVersions);
708         Result result = table.get(get);
709         return ThriftUtilities.cellFromHBase(result.raw());
710       } catch (IOException e) {
711         LOG.warn(e.getMessage(), e);
712         throw new IOError(e.getMessage());
713       }
714     }
715 
716     @Override
717     public List<TRowResult> getRow(ByteBuffer tableName, ByteBuffer row,
718         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
719       return getRowWithColumnsTs(tableName, row, null,
720                                  HConstants.LATEST_TIMESTAMP,
721                                  attributes);
722     }
723 
724     @Override
725     public List<TRowResult> getRowWithColumns(ByteBuffer tableName,
726                                               ByteBuffer row,
727         List<ByteBuffer> columns,
728         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
729       return getRowWithColumnsTs(tableName, row, columns,
730                                  HConstants.LATEST_TIMESTAMP,
731                                  attributes);
732     }
733 
734     @Override
735     public List<TRowResult> getRowTs(ByteBuffer tableName, ByteBuffer row,
736         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
737       return getRowWithColumnsTs(tableName, row, null,
738                                  timestamp, attributes);
739     }
740 
741     @Override
742     public List<TRowResult> getRowWithColumnsTs(
743         ByteBuffer tableName, ByteBuffer row, List<ByteBuffer> columns,
744         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
745       try {
746         HTable table = getTable(tableName);
747         if (columns == null) {
748           Get get = new Get(getBytes(row));
749           addAttributes(get, attributes);
750           get.setTimeRange(Long.MIN_VALUE, timestamp);
751           Result result = table.get(get);
752           return ThriftUtilities.rowResultFromHBase(result);
753         }
754         Get get = new Get(getBytes(row));
755         addAttributes(get, attributes);
756         for(ByteBuffer column : columns) {
757           byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
758           if (famAndQf.length == 1) {
759               get.addFamily(famAndQf[0]);
760           } else {
761               get.addColumn(famAndQf[0], famAndQf[1]);
762           }
763         }
764         get.setTimeRange(Long.MIN_VALUE, timestamp);
765         Result result = table.get(get);
766         return ThriftUtilities.rowResultFromHBase(result);
767       } catch (IOException e) {
768         LOG.warn(e.getMessage(), e);
769         throw new IOError(e.getMessage());
770       }
771     }
772 
773     @Override
774     public List<TRowResult> getRows(ByteBuffer tableName,
775                                     List<ByteBuffer> rows,
776         Map<ByteBuffer, ByteBuffer> attributes)
777         throws IOError {
778       return getRowsWithColumnsTs(tableName, rows, null,
779                                   HConstants.LATEST_TIMESTAMP,
780                                   attributes);
781     }
782 
783     @Override
784     public List<TRowResult> getRowsWithColumns(ByteBuffer tableName,
785                                                List<ByteBuffer> rows,
786         List<ByteBuffer> columns,
787         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
788       return getRowsWithColumnsTs(tableName, rows, columns,
789                                   HConstants.LATEST_TIMESTAMP,
790                                   attributes);
791     }
792 
793     @Override
794     public List<TRowResult> getRowsTs(ByteBuffer tableName,
795                                       List<ByteBuffer> rows,
796         long timestamp,
797         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
798       return getRowsWithColumnsTs(tableName, rows, null,
799                                   timestamp, attributes);
800     }
801 
802     @Override
803     public List<TRowResult> getRowsWithColumnsTs(ByteBuffer tableName,
804                                                  List<ByteBuffer> rows,
805         List<ByteBuffer> columns, long timestamp,
806         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
807       try {
808         List<Get> gets = new ArrayList<Get>(rows.size());
809         HTable table = getTable(tableName);
810         if (metrics != null) {
811           metrics.incNumRowKeysInBatchGet(rows.size());
812         }
813         for (ByteBuffer row : rows) {
814           Get get = new Get(getBytes(row));
815           addAttributes(get, attributes);
816           if (columns != null) {
817 
818             for(ByteBuffer column : columns) {
819               byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
820               if (famAndQf.length == 1) {
821                 get.addFamily(famAndQf[0]);
822               } else {
823                 get.addColumn(famAndQf[0], famAndQf[1]);
824               }
825             }
826           }
827           get.setTimeRange(Long.MIN_VALUE, timestamp);
828           gets.add(get);
829         }
830         Result[] result = table.get(gets);
831         return ThriftUtilities.rowResultFromHBase(result);
832       } catch (IOException e) {
833         LOG.warn(e.getMessage(), e);
834         throw new IOError(e.getMessage());
835       }
836     }
837 
838     @Override
839     public void deleteAll(
840         ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
841         Map<ByteBuffer, ByteBuffer> attributes)
842         throws IOError {
843       deleteAllTs(tableName, row, column, HConstants.LATEST_TIMESTAMP,
844                   attributes);
845     }
846 
847     @Override
848     public void deleteAllTs(ByteBuffer tableName,
849                             ByteBuffer row,
850                             ByteBuffer column,
851         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
852       try {
853         HTable table = getTable(tableName);
854         Delete delete  = new Delete(getBytes(row));
855         addAttributes(delete, attributes);
856         byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
857         if (famAndQf.length == 1) {
858           delete.deleteFamily(famAndQf[0], timestamp);
859         } else {
860           delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
861         }
862         table.delete(delete);
863 
864       } catch (IOException e) {
865         LOG.warn(e.getMessage(), e);
866         throw new IOError(e.getMessage());
867       }
868     }
869 
870     @Override
871     public void deleteAllRow(
872         ByteBuffer tableName, ByteBuffer row,
873         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
874       deleteAllRowTs(tableName, row, HConstants.LATEST_TIMESTAMP, attributes);
875     }
876 
877     @Override
878     public void deleteAllRowTs(
879         ByteBuffer tableName, ByteBuffer row, long timestamp,
880         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
881       try {
882         HTable table = getTable(tableName);
883         Delete delete  = new Delete(getBytes(row), timestamp, null);
884         addAttributes(delete, attributes);
885         table.delete(delete);
886       } catch (IOException e) {
887         LOG.warn(e.getMessage(), e);
888         throw new IOError(e.getMessage());
889       }
890     }
891 
892     @Override
893     public void createTable(ByteBuffer in_tableName,
894         List<ColumnDescriptor> columnFamilies) throws IOError,
895         IllegalArgument, AlreadyExists {
896       byte [] tableName = getBytes(in_tableName);
897       try {
898         if (admin.tableExists(tableName)) {
899           throw new AlreadyExists("table name already in use");
900         }
901         HTableDescriptor desc = new HTableDescriptor(tableName);
902         for (ColumnDescriptor col : columnFamilies) {
903           HColumnDescriptor colDesc = ThriftUtilities.colDescFromThrift(col);
904           desc.addFamily(colDesc);
905         }
906         admin.createTable(desc);
907       } catch (IOException e) {
908         LOG.warn(e.getMessage(), e);
909         throw new IOError(e.getMessage());
910       } catch (IllegalArgumentException e) {
911         LOG.warn(e.getMessage(), e);
912         throw new IllegalArgument(e.getMessage());
913       }
914     }
915 
916     @Override
917     public void deleteTable(ByteBuffer in_tableName) throws IOError {
918       byte [] tableName = getBytes(in_tableName);
919       if (LOG.isDebugEnabled()) {
920         LOG.debug("deleteTable: table=" + Bytes.toString(tableName));
921       }
922       try {
923         if (!admin.tableExists(tableName)) {
924           throw new IOException("table does not exist");
925         }
926         admin.deleteTable(tableName);
927       } catch (IOException e) {
928         LOG.warn(e.getMessage(), e);
929         throw new IOError(e.getMessage());
930       }
931     }
932 
933     @Override
934     public void mutateRow(ByteBuffer tableName, ByteBuffer row,
935         List<Mutation> mutations, Map<ByteBuffer, ByteBuffer> attributes)
936         throws IOError, IllegalArgument {
937       mutateRowTs(tableName, row, mutations, HConstants.LATEST_TIMESTAMP,
938                   attributes);
939     }
940 
941     @Override
942     public void mutateRowTs(ByteBuffer tableName, ByteBuffer row,
943         List<Mutation> mutations, long timestamp,
944         Map<ByteBuffer, ByteBuffer> attributes)
945         throws IOError, IllegalArgument {
946       HTable table = null;
947       try {
948         table = getTable(tableName);
949         Put put = new Put(getBytes(row), timestamp, null);
950         addAttributes(put, attributes);
951 
952         Delete delete = new Delete(getBytes(row));
953         addAttributes(delete, attributes);
954         if (metrics != null) {
955           metrics.incNumRowKeysInBatchMutate(mutations.size());
956         }
957 
958         // I apologize for all this mess :)
959         for (Mutation m : mutations) {
960           byte[][] famAndQf = KeyValue.parseColumn(getBytes(m.column));
961           if (m.isDelete) {
962             if (famAndQf.length == 1) {
963               delete.deleteFamily(famAndQf[0], timestamp);
964             } else {
965               delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
966             }
967             delete.setWriteToWAL(m.writeToWAL);
968           } else {
969             if(famAndQf.length == 1) {
970               put.add(famAndQf[0], HConstants.EMPTY_BYTE_ARRAY,
971                   m.value != null ? getBytes(m.value)
972                       : HConstants.EMPTY_BYTE_ARRAY);
973             } else {
974               put.add(famAndQf[0], famAndQf[1],
975                   m.value != null ? getBytes(m.value)
976                       : HConstants.EMPTY_BYTE_ARRAY);
977             }
978             put.setWriteToWAL(m.writeToWAL);
979           }
980         }
981         if (!delete.isEmpty())
982           table.delete(delete);
983         if (!put.isEmpty())
984           table.put(put);
985       } catch (IOException e) {
986         LOG.warn(e.getMessage(), e);
987         throw new IOError(e.getMessage());
988       } catch (IllegalArgumentException e) {
989         LOG.warn(e.getMessage(), e);
990         throw new IllegalArgument(e.getMessage());
991       }
992     }
993 
994     @Override
995     public void mutateRows(ByteBuffer tableName, List<BatchMutation> rowBatches,
996         Map<ByteBuffer, ByteBuffer> attributes)
997         throws IOError, IllegalArgument, TException {
998       mutateRowsTs(tableName, rowBatches, HConstants.LATEST_TIMESTAMP, attributes);
999     }
1000 
1001     @Override
1002     public void mutateRowsTs(
1003         ByteBuffer tableName, List<BatchMutation> rowBatches, long timestamp,
1004         Map<ByteBuffer, ByteBuffer> attributes)
1005         throws IOError, IllegalArgument, TException {
1006       List<Put> puts = new ArrayList<Put>();
1007       List<Delete> deletes = new ArrayList<Delete>();
1008 
1009       for (BatchMutation batch : rowBatches) {
1010         byte[] row = getBytes(batch.row);
1011         List<Mutation> mutations = batch.mutations;
1012         Delete delete = new Delete(row);
1013         addAttributes(delete, attributes);
1014         Put put = new Put(row, timestamp, null);
1015         addAttributes(put, attributes);
1016         for (Mutation m : mutations) {
1017           byte[][] famAndQf = KeyValue.parseColumn(getBytes(m.column));
1018           if (m.isDelete) {
1019             // no qualifier, family only.
1020             if (famAndQf.length == 1) {
1021               delete.deleteFamily(famAndQf[0], timestamp);
1022             } else {
1023               delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
1024             }
1025             delete.setWriteToWAL(m.writeToWAL);
1026           } else {
1027             if(famAndQf.length == 1) {
1028               put.add(famAndQf[0], HConstants.EMPTY_BYTE_ARRAY,
1029                   m.value != null ? getBytes(m.value)
1030                       : HConstants.EMPTY_BYTE_ARRAY);
1031             } else {
1032               put.add(famAndQf[0], famAndQf[1],
1033                   m.value != null ? getBytes(m.value)
1034                       : HConstants.EMPTY_BYTE_ARRAY);
1035             }
1036             put.setWriteToWAL(m.writeToWAL);
1037           }
1038         }
1039         if (!delete.isEmpty())
1040           deletes.add(delete);
1041         if (!put.isEmpty())
1042           puts.add(put);
1043       }
1044 
1045       HTable table = null;
1046       try {
1047         table = getTable(tableName);
1048         if (!puts.isEmpty())
1049           table.put(puts);
1050         if (!deletes.isEmpty()) table.delete(deletes);
1051       } catch (IOException e) {
1052         LOG.warn(e.getMessage(), e);
1053         throw new IOError(e.getMessage());
1054       } catch (IllegalArgumentException e) {
1055         LOG.warn(e.getMessage(), e);
1056         throw new IllegalArgument(e.getMessage());
1057       }
1058     }
1059 
1060     @Deprecated
1061     @Override
1062     public long atomicIncrement(
1063         ByteBuffer tableName, ByteBuffer row, ByteBuffer column, long amount)
1064             throws IOError, IllegalArgument, TException {
1065       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
1066       if(famAndQf.length == 1) {
1067         return atomicIncrement(tableName, row, famAndQf[0], new byte[0],
1068             amount);
1069       }
1070       return atomicIncrement(tableName, row, famAndQf[0], famAndQf[1], amount);
1071     }
1072 
1073     protected long atomicIncrement(ByteBuffer tableName, ByteBuffer row,
1074         byte [] family, byte [] qualifier, long amount)
1075         throws IOError, IllegalArgument, TException {
1076       HTable table;
1077       try {
1078         table = getTable(tableName);
1079         return table.incrementColumnValue(
1080             getBytes(row), family, qualifier, amount);
1081       } catch (IOException e) {
1082         LOG.warn(e.getMessage(), e);
1083         throw new IOError(e.getMessage());
1084       }
1085     }
1086 
1087     public void scannerClose(int id) throws IOError, IllegalArgument {
1088       LOG.debug("scannerClose: id=" + id);
1089       ResultScanner scanner = getScanner(id);
1090       if (scanner == null) {
1091         String message = "scanner ID is invalid";
1092         LOG.warn(message);
1093         throw new IllegalArgument("scanner ID is invalid");
1094       }
1095       scanner.close();
1096       removeScanner(id);
1097     }
1098 
1099     @Override
1100     public List<TRowResult> scannerGetList(int id,int nbRows)
1101         throws IllegalArgument, IOError {
1102       LOG.debug("scannerGetList: id=" + id);
1103       ResultScanner scanner = getScanner(id);
1104       if (null == scanner) {
1105         String message = "scanner ID is invalid";
1106         LOG.warn(message);
1107         throw new IllegalArgument("scanner ID is invalid");
1108       }
1109 
1110       Result [] results = null;
1111       try {
1112         results = scanner.next(nbRows);
1113         if (null == results) {
1114           return new ArrayList<TRowResult>();
1115         }
1116       } catch (IOException e) {
1117         LOG.warn(e.getMessage(), e);
1118         throw new IOError(e.getMessage());
1119       }
1120       return ThriftUtilities.rowResultFromHBase(results);
1121     }
1122 
1123     @Override
1124     public List<TRowResult> scannerGet(int id) throws IllegalArgument, IOError {
1125       return scannerGetList(id,1);
1126     }
1127 
1128     public int scannerOpenWithScan(ByteBuffer tableName, TScan tScan,
1129         Map<ByteBuffer, ByteBuffer> attributes)
1130         throws IOError {
1131       try {
1132         HTable table = getTable(tableName);
1133         Scan scan = new Scan();
1134         addAttributes(scan, attributes);
1135         if (tScan.isSetStartRow()) {
1136           scan.setStartRow(tScan.getStartRow());
1137         }
1138         if (tScan.isSetStopRow()) {
1139           scan.setStopRow(tScan.getStopRow());
1140         }
1141         if (tScan.isSetTimestamp()) {
1142           scan.setTimeRange(Long.MIN_VALUE, tScan.getTimestamp());
1143         }
1144         if (tScan.isSetCaching()) {
1145           scan.setCaching(tScan.getCaching());
1146         }
1147         if (tScan.isSetColumns() && tScan.getColumns().size() != 0) {
1148           for(ByteBuffer column : tScan.getColumns()) {
1149             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1150             if(famQf.length == 1) {
1151               scan.addFamily(famQf[0]);
1152             } else {
1153               scan.addColumn(famQf[0], famQf[1]);
1154             }
1155           }
1156         }
1157         if (tScan.isSetFilterString()) {
1158           ParseFilter parseFilter = new ParseFilter();
1159           scan.setFilter(
1160               parseFilter.parseFilterString(tScan.getFilterString()));
1161         }
1162         return addScanner(table.getScanner(scan));
1163       } catch (IOException e) {
1164         LOG.warn(e.getMessage(), e);
1165         throw new IOError(e.getMessage());
1166       }
1167     }
1168 
1169     @Override
1170     public int scannerOpen(ByteBuffer tableName, ByteBuffer startRow,
1171         List<ByteBuffer> columns,
1172         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
1173       try {
1174         HTable table = getTable(tableName);
1175         Scan scan = new Scan(getBytes(startRow));
1176         addAttributes(scan, attributes);
1177         if(columns != null && columns.size() != 0) {
1178           for(ByteBuffer column : columns) {
1179             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1180             if(famQf.length == 1) {
1181               scan.addFamily(famQf[0]);
1182             } else {
1183               scan.addColumn(famQf[0], famQf[1]);
1184             }
1185           }
1186         }
1187         return addScanner(table.getScanner(scan));
1188       } catch (IOException e) {
1189         LOG.warn(e.getMessage(), e);
1190         throw new IOError(e.getMessage());
1191       }
1192     }
1193 
1194     @Override
1195     public int scannerOpenWithStop(ByteBuffer tableName, ByteBuffer startRow,
1196         ByteBuffer stopRow, List<ByteBuffer> columns,
1197         Map<ByteBuffer, ByteBuffer> attributes)
1198         throws IOError, TException {
1199       try {
1200         HTable table = getTable(tableName);
1201         Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1202         addAttributes(scan, attributes);
1203         if(columns != null && columns.size() != 0) {
1204           for(ByteBuffer column : columns) {
1205             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1206             if(famQf.length == 1) {
1207               scan.addFamily(famQf[0]);
1208             } else {
1209               scan.addColumn(famQf[0], famQf[1]);
1210             }
1211           }
1212         }
1213         return addScanner(table.getScanner(scan));
1214       } catch (IOException e) {
1215         LOG.warn(e.getMessage(), e);
1216         throw new IOError(e.getMessage());
1217       }
1218     }
1219 
1220     @Override
1221     public int scannerOpenWithPrefix(ByteBuffer tableName,
1222                                      ByteBuffer startAndPrefix,
1223                                      List<ByteBuffer> columns,
1224         Map<ByteBuffer, ByteBuffer> attributes)
1225         throws IOError, TException {
1226       try {
1227         HTable table = getTable(tableName);
1228         Scan scan = new Scan(getBytes(startAndPrefix));
1229         addAttributes(scan, attributes);
1230         Filter f = new WhileMatchFilter(
1231             new PrefixFilter(getBytes(startAndPrefix)));
1232         scan.setFilter(f);
1233         if (columns != null && columns.size() != 0) {
1234           for(ByteBuffer column : columns) {
1235             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1236             if(famQf.length == 1) {
1237               scan.addFamily(famQf[0]);
1238             } else {
1239               scan.addColumn(famQf[0], famQf[1]);
1240             }
1241           }
1242         }
1243         return addScanner(table.getScanner(scan));
1244       } catch (IOException e) {
1245         LOG.warn(e.getMessage(), e);
1246         throw new IOError(e.getMessage());
1247       }
1248     }
1249 
1250     @Override
1251     public int scannerOpenTs(ByteBuffer tableName, ByteBuffer startRow,
1252         List<ByteBuffer> columns, long timestamp,
1253         Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
1254       try {
1255         HTable table = getTable(tableName);
1256         Scan scan = new Scan(getBytes(startRow));
1257         addAttributes(scan, attributes);
1258         scan.setTimeRange(Long.MIN_VALUE, timestamp);
1259         if (columns != null && columns.size() != 0) {
1260           for (ByteBuffer column : columns) {
1261             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1262             if(famQf.length == 1) {
1263               scan.addFamily(famQf[0]);
1264             } else {
1265               scan.addColumn(famQf[0], famQf[1]);
1266             }
1267           }
1268         }
1269         return addScanner(table.getScanner(scan));
1270       } catch (IOException e) {
1271         LOG.warn(e.getMessage(), e);
1272         throw new IOError(e.getMessage());
1273       }
1274     }
1275 
1276     @Override
1277     public int scannerOpenWithStopTs(ByteBuffer tableName, ByteBuffer startRow,
1278         ByteBuffer stopRow, List<ByteBuffer> columns, long timestamp,
1279         Map<ByteBuffer, ByteBuffer> attributes)
1280         throws IOError, TException {
1281       try {
1282         HTable table = getTable(tableName);
1283         Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1284         addAttributes(scan, attributes);
1285         scan.setTimeRange(Long.MIN_VALUE, timestamp);
1286         if (columns != null && columns.size() != 0) {
1287           for (ByteBuffer column : columns) {
1288             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1289             if(famQf.length == 1) {
1290               scan.addFamily(famQf[0]);
1291             } else {
1292               scan.addColumn(famQf[0], famQf[1]);
1293             }
1294           }
1295         }
1296         scan.setTimeRange(Long.MIN_VALUE, timestamp);
1297         return addScanner(table.getScanner(scan));
1298       } catch (IOException e) {
1299         LOG.warn(e.getMessage(), e);
1300         throw new IOError(e.getMessage());
1301       }
1302     }
1303 
1304     @Override
1305     public Map<ByteBuffer, ColumnDescriptor> getColumnDescriptors(
1306         ByteBuffer tableName) throws IOError, TException {
1307       try {
1308         TreeMap<ByteBuffer, ColumnDescriptor> columns =
1309           new TreeMap<ByteBuffer, ColumnDescriptor>();
1310 
1311         HTable table = getTable(tableName);
1312         HTableDescriptor desc = table.getTableDescriptor();
1313 
1314         for (HColumnDescriptor e : desc.getFamilies()) {
1315           ColumnDescriptor col = ThriftUtilities.colDescFromHbase(e);
1316           columns.put(col.name, col);
1317         }
1318         return columns;
1319       } catch (IOException e) {
1320         LOG.warn(e.getMessage(), e);
1321         throw new IOError(e.getMessage());
1322       }
1323     }
1324 
1325     @Override
1326     public List<TCell> getRowOrBefore(ByteBuffer tableName, ByteBuffer row,
1327         ByteBuffer family) throws IOError {
1328       try {
1329         HTable table = getTable(getBytes(tableName));
1330         Result result = table.getRowOrBefore(getBytes(row), getBytes(family));
1331         return ThriftUtilities.cellFromHBase(result.raw());
1332       } catch (IOException e) {
1333         LOG.warn(e.getMessage(), e);
1334         throw new IOError(e.getMessage());
1335       }
1336     }
1337 
1338     @Override
1339     public TRegionInfo getRegionInfo(ByteBuffer searchRow) throws IOError {
1340       try {
1341         HTable table = getTable(HConstants.META_TABLE_NAME);
1342         byte[] row = toBytes(searchRow);
1343         Result startRowResult = table.getRowOrBefore(
1344           row, HConstants.CATALOG_FAMILY);
1345 
1346         if (startRowResult == null) {
1347           throw new IOException("Cannot find row in .META., row="
1348                                 + Bytes.toString(searchRow.array()));
1349         }
1350 
1351         // find region start and end keys
1352         byte[] value = startRowResult.getValue(HConstants.CATALOG_FAMILY,
1353                                                HConstants.REGIONINFO_QUALIFIER);
1354         if (value == null || value.length == 0) {
1355           throw new IOException("HRegionInfo REGIONINFO was null or " +
1356                                 " empty in Meta for row="
1357                                 + Bytes.toString(row));
1358         }
1359         HRegionInfo regionInfo = Writables.getHRegionInfo(value);
1360         TRegionInfo region = new TRegionInfo();
1361         region.setStartKey(regionInfo.getStartKey());
1362         region.setEndKey(regionInfo.getEndKey());
1363         region.id = regionInfo.getRegionId();
1364         region.setName(regionInfo.getRegionName());
1365         region.version = regionInfo.getVersion();
1366 
1367         // find region assignment to server
1368         value = startRowResult.getValue(HConstants.CATALOG_FAMILY,
1369                                         HConstants.SERVER_QUALIFIER);
1370         if (value != null && value.length > 0) {
1371           String hostAndPort = Bytes.toString(value);
1372           region.setServerName(Bytes.toBytes(
1373               Addressing.parseHostname(hostAndPort)));
1374           region.port = Addressing.parsePort(hostAndPort);
1375         }
1376         return region;
1377       } catch (IOException e) {
1378         LOG.warn(e.getMessage(), e);
1379         throw new IOError(e.getMessage());
1380       }
1381     }
1382 
1383     private void initMetrics(ThriftMetrics metrics) {
1384       this.metrics = metrics;
1385     }
1386 
1387     @Override
1388     public void increment(TIncrement tincrement) throws IOError, TException {
1389 
1390       if (tincrement.getRow().length == 0 || tincrement.getTable().length == 0) {
1391         throw new TException("Must supply a table and a row key; can't increment");
1392       }
1393 
1394       if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1395         this.coalescer.queueIncrement(tincrement);
1396         return;
1397       }
1398 
1399       try {
1400         HTable table = getTable(tincrement.getTable());
1401         Increment inc = ThriftUtilities.incrementFromThrift(tincrement);
1402         table.increment(inc);
1403       } catch (IOException e) {
1404         LOG.warn(e.getMessage(), e);
1405         throw new IOError(e.getMessage());
1406       }
1407     }
1408 
1409     @Override
1410     public void incrementRows(List<TIncrement> tincrements) throws IOError, TException {
1411       if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1412         this.coalescer.queueIncrements(tincrements);
1413         return;
1414       }
1415       for (TIncrement tinc : tincrements) {
1416         increment(tinc);
1417       }
1418     }
1419   }
1420 
1421 
1422 
1423   /**
1424    * Adds all the attributes into the Operation object
1425    */
1426   private static void addAttributes(OperationWithAttributes op,
1427     Map<ByteBuffer, ByteBuffer> attributes) {
1428     if (attributes == null || attributes.size() == 0) {
1429       return;
1430     }
1431     for (Map.Entry<ByteBuffer, ByteBuffer> entry : attributes.entrySet()) {
1432       String name = Bytes.toStringBinary(getBytes(entry.getKey()));
1433       byte[] value =  getBytes(entry.getValue());
1434       op.setAttribute(name, value);
1435     }
1436   }
1437 
1438   public static void registerFilters(Configuration conf) {
1439     String[] filters = conf.getStrings("hbase.thrift.filters");
1440     if(filters != null) {
1441       for(String filterClass: filters) {
1442         String[] filterPart = filterClass.split(":");
1443         if(filterPart.length != 2) {
1444           LOG.warn("Invalid filter specification " + filterClass + " - skipping");
1445         } else {
1446           ParseFilter.registerFilter(filterPart[0], filterPart[1]);
1447         }
1448       }
1449     }
1450   }
1451 }