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.security.PrivilegedAction;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.HashMap;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.TreeMap;
36  import java.util.concurrent.BlockingQueue;
37  import java.util.concurrent.ExecutorService;
38  import java.util.concurrent.LinkedBlockingQueue;
39  import java.util.concurrent.ThreadPoolExecutor;
40  import java.util.concurrent.TimeUnit;
41  
42  import javax.security.auth.callback.Callback;
43  import javax.security.auth.callback.UnsupportedCallbackException;
44  import javax.security.sasl.AuthorizeCallback;
45  import javax.security.sasl.Sasl;
46  import javax.security.sasl.SaslServer;
47  
48  import org.apache.commons.cli.CommandLine;
49  import org.apache.commons.cli.Option;
50  import org.apache.commons.cli.OptionGroup;
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  import org.apache.hadoop.hbase.classification.InterfaceAudience;
54  import org.apache.hadoop.conf.Configuration;
55  import org.apache.hadoop.hbase.HBaseConfiguration;
56  import org.apache.hadoop.hbase.HColumnDescriptor;
57  import org.apache.hadoop.hbase.HConstants;
58  import org.apache.hadoop.hbase.HRegionInfo;
59  import org.apache.hadoop.hbase.HTableDescriptor;
60  import org.apache.hadoop.hbase.KeyValue;
61  import org.apache.hadoop.hbase.ServerName;
62  import org.apache.hadoop.hbase.TableName;
63  import org.apache.hadoop.hbase.TableNotFoundException;
64  import org.apache.hadoop.hbase.client.Delete;
65  import org.apache.hadoop.hbase.client.Durability;
66  import org.apache.hadoop.hbase.client.Get;
67  import org.apache.hadoop.hbase.client.HBaseAdmin;
68  import org.apache.hadoop.hbase.client.HTable;
69  import org.apache.hadoop.hbase.client.HTableInterface;
70  import org.apache.hadoop.hbase.client.Increment;
71  import org.apache.hadoop.hbase.client.OperationWithAttributes;
72  import org.apache.hadoop.hbase.client.Put;
73  import org.apache.hadoop.hbase.client.Result;
74  import org.apache.hadoop.hbase.client.ResultScanner;
75  import org.apache.hadoop.hbase.client.Scan;
76  import org.apache.hadoop.hbase.filter.Filter;
77  import org.apache.hadoop.hbase.filter.ParseFilter;
78  import org.apache.hadoop.hbase.filter.PrefixFilter;
79  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
80  import org.apache.hadoop.hbase.security.SecurityUtil;
81  import org.apache.hadoop.hbase.security.UserProvider;
82  import org.apache.hadoop.hbase.thrift.CallQueue.Call;
83  import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
84  import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
85  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
86  import org.apache.hadoop.hbase.thrift.generated.Hbase;
87  import org.apache.hadoop.hbase.thrift.generated.IOError;
88  import org.apache.hadoop.hbase.thrift.generated.IllegalArgument;
89  import org.apache.hadoop.hbase.thrift.generated.Mutation;
90  import org.apache.hadoop.hbase.thrift.generated.TCell;
91  import org.apache.hadoop.hbase.thrift.generated.TIncrement;
92  import org.apache.hadoop.hbase.thrift.generated.TRegionInfo;
93  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
94  import org.apache.hadoop.hbase.thrift.generated.TScan;
95  import org.apache.hadoop.hbase.util.Bytes;
96  import org.apache.hadoop.hbase.util.ConnectionCache;
97  import org.apache.hadoop.hbase.util.Strings;
98  import org.apache.hadoop.net.DNS;
99  import org.apache.hadoop.security.SaslRpcServer.SaslGssCallbackHandler;
100 import org.apache.hadoop.security.UserGroupInformation;
101 import org.apache.thrift.TException;
102 import org.apache.thrift.TProcessor;
103 import org.apache.thrift.protocol.TBinaryProtocol;
104 import org.apache.thrift.protocol.TCompactProtocol;
105 import org.apache.thrift.protocol.TProtocol;
106 import org.apache.thrift.protocol.TProtocolFactory;
107 import org.apache.thrift.server.THsHaServer;
108 import org.apache.thrift.server.TNonblockingServer;
109 import org.apache.thrift.server.TServer;
110 import org.apache.thrift.server.TThreadedSelectorServer;
111 import org.apache.thrift.transport.TFramedTransport;
112 import org.apache.thrift.transport.TNonblockingServerSocket;
113 import org.apache.thrift.transport.TNonblockingServerTransport;
114 import org.apache.thrift.transport.TSaslServerTransport;
115 import org.apache.thrift.transport.TServerSocket;
116 import org.apache.thrift.transport.TServerTransport;
117 import org.apache.thrift.transport.TTransportFactory;
118 
119 import com.google.common.base.Joiner;
120 import com.google.common.base.Throwables;
121 import com.google.common.util.concurrent.ThreadFactoryBuilder;
122 
123 /**
124  * ThriftServerRunner - this class starts up a Thrift server which implements
125  * the Hbase API specified in the Hbase.thrift IDL file.
126  */
127 @InterfaceAudience.Private
128 @SuppressWarnings("deprecation")
129 public class ThriftServerRunner implements Runnable {
130 
131   private static final Log LOG = LogFactory.getLog(ThriftServerRunner.class);
132 
133   static final String SERVER_TYPE_CONF_KEY =
134       "hbase.regionserver.thrift.server.type";
135 
136   static final String BIND_CONF_KEY = "hbase.regionserver.thrift.ipaddress";
137   static final String COMPACT_CONF_KEY = "hbase.regionserver.thrift.compact";
138   static final String FRAMED_CONF_KEY = "hbase.regionserver.thrift.framed";
139   static final String MAX_FRAME_SIZE_CONF_KEY = "hbase.regionserver.thrift.framed.max_frame_size_in_mb";
140   static final String PORT_CONF_KEY = "hbase.regionserver.thrift.port";
141   static final String COALESCE_INC_KEY = "hbase.regionserver.thrift.coalesceIncrement";
142 
143   /**
144    * Thrift quality of protection configuration key. Valid values can be:
145    * auth-conf: authentication, integrity and confidentiality checking
146    * auth-int: authentication and integrity checking
147    * auth: authentication only
148    *
149    * This is used to authenticate the callers and support impersonation.
150    * The thrift server and the HBase cluster must run in secure mode.
151    */
152   static final String THRIFT_QOP_KEY = "hbase.thrift.security.qop";
153 
154   private static final String DEFAULT_BIND_ADDR = "0.0.0.0";
155   public static final int DEFAULT_LISTEN_PORT = 9090;
156   private final int listenPort;
157 
158   private Configuration conf;
159   volatile TServer tserver;
160   private final Hbase.Iface handler;
161   private final ThriftMetrics metrics;
162   private final HBaseHandler hbaseHandler;
163   private final UserGroupInformation realUser;
164 
165   private final String qop;
166   private String host;
167 
168   /** An enum of server implementation selections */
169   enum ImplType {
170     HS_HA("hsha", true, THsHaServer.class, true),
171     NONBLOCKING("nonblocking", true, TNonblockingServer.class, true),
172     THREAD_POOL("threadpool", false, TBoundedThreadPoolServer.class, true),
173     THREADED_SELECTOR(
174         "threadedselector", true, TThreadedSelectorServer.class, true);
175 
176     public static final ImplType DEFAULT = THREAD_POOL;
177 
178     final String option;
179     final boolean isAlwaysFramed;
180     final Class<? extends TServer> serverClass;
181     final boolean canSpecifyBindIP;
182 
183     ImplType(String option, boolean isAlwaysFramed,
184         Class<? extends TServer> serverClass, boolean canSpecifyBindIP) {
185       this.option = option;
186       this.isAlwaysFramed = isAlwaysFramed;
187       this.serverClass = serverClass;
188       this.canSpecifyBindIP = canSpecifyBindIP;
189     }
190 
191     /**
192      * @return <code>-option</code> so we can get the list of options from
193      *         {@link #values()}
194      */
195     @Override
196     public String toString() {
197       return "-" + option;
198     }
199 
200     String getDescription() {
201       StringBuilder sb = new StringBuilder("Use the " +
202           serverClass.getSimpleName());
203       if (isAlwaysFramed) {
204         sb.append(" This implies the framed transport.");
205       }
206       if (this == DEFAULT) {
207         sb.append("This is the default.");
208       }
209       return sb.toString();
210     }
211 
212     static OptionGroup createOptionGroup() {
213       OptionGroup group = new OptionGroup();
214       for (ImplType t : values()) {
215         group.addOption(new Option(t.option, t.getDescription()));
216       }
217       return group;
218     }
219 
220     static ImplType getServerImpl(Configuration conf) {
221       String confType = conf.get(SERVER_TYPE_CONF_KEY, THREAD_POOL.option);
222       for (ImplType t : values()) {
223         if (confType.equals(t.option)) {
224           return t;
225         }
226       }
227       throw new AssertionError("Unknown server ImplType.option:" + confType);
228     }
229 
230     static void setServerImpl(CommandLine cmd, Configuration conf) {
231       ImplType chosenType = null;
232       int numChosen = 0;
233       for (ImplType t : values()) {
234         if (cmd.hasOption(t.option)) {
235           chosenType = t;
236           ++numChosen;
237         }
238       }
239       if (numChosen < 1) {
240         LOG.info("Using default thrift server type");
241         chosenType = DEFAULT;
242       } else if (numChosen > 1) {
243         throw new AssertionError("Exactly one option out of " +
244           Arrays.toString(values()) + " has to be specified");
245       }
246       LOG.info("Using thrift server type " + chosenType.option);
247       conf.set(SERVER_TYPE_CONF_KEY, chosenType.option);
248     }
249 
250     public String simpleClassName() {
251       return serverClass.getSimpleName();
252     }
253 
254     public static List<String> serversThatCannotSpecifyBindIP() {
255       List<String> l = new ArrayList<String>();
256       for (ImplType t : values()) {
257         if (!t.canSpecifyBindIP) {
258           l.add(t.simpleClassName());
259         }
260       }
261       return l;
262     }
263 
264   }
265 
266   public ThriftServerRunner(Configuration conf) throws IOException {
267     UserProvider userProvider = UserProvider.instantiate(conf);
268     // login the server principal (if using secure Hadoop)
269     boolean securityEnabled = userProvider.isHadoopSecurityEnabled()
270       && userProvider.isHBaseSecurityEnabled();
271     if (securityEnabled) {
272       host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
273         conf.get("hbase.thrift.dns.interface", "default"),
274         conf.get("hbase.thrift.dns.nameserver", "default")));
275       userProvider.login("hbase.thrift.keytab.file",
276         "hbase.thrift.kerberos.principal", host);
277     }
278     this.conf = HBaseConfiguration.create(conf);
279     this.listenPort = conf.getInt(PORT_CONF_KEY, DEFAULT_LISTEN_PORT);
280     this.metrics = new ThriftMetrics(conf, ThriftMetrics.ThriftServerType.ONE);
281     this.hbaseHandler = new HBaseHandler(conf, userProvider);
282     this.hbaseHandler.initMetrics(metrics);
283     this.handler = HbaseHandlerMetricsProxy.newInstance(
284       hbaseHandler, metrics, conf);
285     this.realUser = userProvider.getCurrent().getUGI();
286     qop = conf.get(THRIFT_QOP_KEY);
287     if (qop != null) {
288       if (!qop.equals("auth") && !qop.equals("auth-int")
289           && !qop.equals("auth-conf")) {
290         throw new IOException("Invalid " + THRIFT_QOP_KEY + ": " + qop
291           + ", it must be 'auth', 'auth-int', or 'auth-conf'");
292       }
293       if (!securityEnabled) {
294         throw new IOException("Thrift server must"
295           + " run in secure mode to support authentication");
296       }
297     }
298   }
299 
300   /*
301    * Runs the Thrift server
302    */
303   @Override
304   public void run() {
305     realUser.doAs(
306       new PrivilegedAction<Object>() {
307         @Override
308         public Object run() {
309           try {
310             setupServer();
311             tserver.serve();
312           } catch (Exception e) {
313             LOG.fatal("Cannot run ThriftServer", e);
314             // Crash the process if the ThriftServer is not running
315             System.exit(-1);
316           }
317           return null;
318         }
319       });
320   }
321 
322   public void shutdown() {
323     if (tserver != null) {
324       tserver.stop();
325       tserver = null;
326     }
327   }
328 
329   /**
330    * Setting up the thrift TServer
331    */
332   private void setupServer() throws Exception {
333     // Construct correct ProtocolFactory
334     TProtocolFactory protocolFactory;
335     if (conf.getBoolean(COMPACT_CONF_KEY, false)) {
336       LOG.debug("Using compact protocol");
337       protocolFactory = new TCompactProtocol.Factory();
338     } else {
339       LOG.debug("Using binary protocol");
340       protocolFactory = new TBinaryProtocol.Factory();
341     }
342 
343     final TProcessor p = new Hbase.Processor<Hbase.Iface>(handler);
344     ImplType implType = ImplType.getServerImpl(conf);
345     TProcessor processor = p;
346 
347     // Construct correct TransportFactory
348     TTransportFactory transportFactory;
349     if (conf.getBoolean(FRAMED_CONF_KEY, false) || implType.isAlwaysFramed) {
350       if (qop != null) {
351         throw new RuntimeException("Thrift server authentication"
352           + " doesn't work with framed transport yet");
353       }
354       transportFactory = new TFramedTransport.Factory(
355           conf.getInt(MAX_FRAME_SIZE_CONF_KEY, 2)  * 1024 * 1024);
356       LOG.debug("Using framed transport");
357     } else if (qop == null) {
358       transportFactory = new TTransportFactory();
359     } else {
360       // Extract the name from the principal
361       String name = SecurityUtil.getUserFromPrincipal(
362         conf.get("hbase.thrift.kerberos.principal"));
363       Map<String, String> saslProperties = new HashMap<String, String>();
364       saslProperties.put(Sasl.QOP, qop);
365       TSaslServerTransport.Factory saslFactory = new TSaslServerTransport.Factory();
366       saslFactory.addServerDefinition("GSSAPI", name, host, saslProperties,
367         new SaslGssCallbackHandler() {
368           @Override
369           public void handle(Callback[] callbacks)
370               throws UnsupportedCallbackException {
371             AuthorizeCallback ac = null;
372             for (Callback callback : callbacks) {
373               if (callback instanceof AuthorizeCallback) {
374                 ac = (AuthorizeCallback) callback;
375               } else {
376                 throw new UnsupportedCallbackException(callback,
377                     "Unrecognized SASL GSSAPI Callback");
378               }
379             }
380             if (ac != null) {
381               String authid = ac.getAuthenticationID();
382               String authzid = ac.getAuthorizationID();
383               if (!authid.equals(authzid)) {
384                 ac.setAuthorized(false);
385               } else {
386                 ac.setAuthorized(true);
387                 String userName = SecurityUtil.getUserFromPrincipal(authzid);
388                 LOG.info("Effective user: " + userName);
389                 ac.setAuthorizedID(userName);
390               }
391             }
392           }
393         });
394       transportFactory = saslFactory;
395 
396       // Create a processor wrapper, to get the caller
397       processor = new TProcessor() {
398         @Override
399         public boolean process(TProtocol inProt,
400             TProtocol outProt) throws TException {
401           TSaslServerTransport saslServerTransport =
402             (TSaslServerTransport)inProt.getTransport();
403           SaslServer saslServer = saslServerTransport.getSaslServer();
404           String principal = saslServer.getAuthorizationID();
405           hbaseHandler.setEffectiveUser(principal);
406           return p.process(inProt, outProt);
407         }
408       };
409     }
410 
411     if (conf.get(BIND_CONF_KEY) != null && !implType.canSpecifyBindIP) {
412       LOG.error("Server types " + Joiner.on(", ").join(
413           ImplType.serversThatCannotSpecifyBindIP()) + " don't support IP " +
414           "address binding at the moment. See " +
415           "https://issues.apache.org/jira/browse/HBASE-2155 for details.");
416       throw new RuntimeException(
417           "-" + BIND_CONF_KEY + " not supported with " + implType);
418     }
419 
420     if (implType == ImplType.HS_HA || implType == ImplType.NONBLOCKING ||
421         implType == ImplType.THREADED_SELECTOR) {
422 
423       InetAddress listenAddress = getBindAddress(conf);
424       TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(
425           new InetSocketAddress(listenAddress, listenPort));
426 
427       if (implType == ImplType.NONBLOCKING) {
428         TNonblockingServer.Args serverArgs =
429             new TNonblockingServer.Args(serverTransport);
430         serverArgs.processor(processor)
431                   .transportFactory(transportFactory)
432                   .protocolFactory(protocolFactory);
433         tserver = new TNonblockingServer(serverArgs);
434       } else if (implType == ImplType.HS_HA) {
435         THsHaServer.Args serverArgs = new THsHaServer.Args(serverTransport);
436         CallQueue callQueue =
437             new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
438         ExecutorService executorService = createExecutor(
439             callQueue, serverArgs.getWorkerThreads());
440         serverArgs.executorService(executorService)
441                   .processor(processor)
442                   .transportFactory(transportFactory)
443                   .protocolFactory(protocolFactory);
444         tserver = new THsHaServer(serverArgs);
445       } else { // THREADED_SELECTOR
446         TThreadedSelectorServer.Args serverArgs =
447             new HThreadedSelectorServerArgs(serverTransport, conf);
448         CallQueue callQueue =
449             new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
450         ExecutorService executorService = createExecutor(
451             callQueue, serverArgs.getWorkerThreads());
452         serverArgs.executorService(executorService)
453                   .processor(processor)
454                   .transportFactory(transportFactory)
455                   .protocolFactory(protocolFactory);
456         tserver = new TThreadedSelectorServer(serverArgs);
457       }
458       LOG.info("starting HBase " + implType.simpleClassName() +
459           " server on " + Integer.toString(listenPort));
460     } else if (implType == ImplType.THREAD_POOL) {
461       // Thread pool server. Get the IP address to bind to.
462       InetAddress listenAddress = getBindAddress(conf);
463 
464       TServerTransport serverTransport = new TServerSocket(
465           new InetSocketAddress(listenAddress, listenPort));
466 
467       TBoundedThreadPoolServer.Args serverArgs =
468           new TBoundedThreadPoolServer.Args(serverTransport, conf);
469       serverArgs.processor(processor)
470                 .transportFactory(transportFactory)
471                 .protocolFactory(protocolFactory);
472       LOG.info("starting " + ImplType.THREAD_POOL.simpleClassName() + " on "
473           + listenAddress + ":" + Integer.toString(listenPort)
474           + "; " + serverArgs);
475       TBoundedThreadPoolServer tserver =
476           new TBoundedThreadPoolServer(serverArgs, metrics);
477       this.tserver = tserver;
478     } else {
479       throw new AssertionError("Unsupported Thrift server implementation: " +
480           implType.simpleClassName());
481     }
482 
483     // A sanity check that we instantiated the right type of server.
484     if (tserver.getClass() != implType.serverClass) {
485       throw new AssertionError("Expected to create Thrift server class " +
486           implType.serverClass.getName() + " but got " +
487           tserver.getClass().getName());
488     }
489 
490 
491 
492     registerFilters(conf);
493   }
494 
495   ExecutorService createExecutor(BlockingQueue<Runnable> callQueue,
496                                  int workerThreads) {
497     ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
498     tfb.setDaemon(true);
499     tfb.setNameFormat("thrift-worker-%d");
500     return new ThreadPoolExecutor(workerThreads, workerThreads,
501             Long.MAX_VALUE, TimeUnit.SECONDS, callQueue, tfb.build());
502   }
503 
504   private InetAddress getBindAddress(Configuration conf)
505       throws UnknownHostException {
506     String bindAddressStr = conf.get(BIND_CONF_KEY, DEFAULT_BIND_ADDR);
507     return InetAddress.getByName(bindAddressStr);
508   }
509 
510   protected static class ResultScannerWrapper {
511 
512     private final ResultScanner scanner;
513     private final boolean sortColumns;
514     public ResultScannerWrapper(ResultScanner resultScanner,
515                                 boolean sortResultColumns) {
516       scanner = resultScanner;
517       sortColumns = sortResultColumns;
518    }
519 
520     public ResultScanner getScanner() {
521       return scanner;
522     }
523 
524     public boolean isColumnSorted() {
525       return sortColumns;
526     }
527   }
528 
529   /**
530    * The HBaseHandler is a glue object that connects Thrift RPC calls to the
531    * HBase client API primarily defined in the HBaseAdmin and HTable objects.
532    */
533   public static class HBaseHandler implements Hbase.Iface {
534     protected Configuration conf;
535     protected final Log LOG = LogFactory.getLog(this.getClass().getName());
536 
537     // nextScannerId and scannerMap are used to manage scanner state
538     protected int nextScannerId = 0;
539     protected HashMap<Integer, ResultScannerWrapper> scannerMap = null;
540     private ThriftMetrics metrics = null;
541 
542     private final ConnectionCache connectionCache;
543     IncrementCoalescer coalescer = null;
544 
545     static final String CLEANUP_INTERVAL = "hbase.thrift.connection.cleanup-interval";
546     static final String MAX_IDLETIME = "hbase.thrift.connection.max-idletime";
547 
548     /**
549      * Returns a list of all the column families for a given htable.
550      *
551      * @param table
552      * @throws IOException
553      */
554     byte[][] getAllColumns(HTable table) throws IOException {
555       HColumnDescriptor[] cds = table.getTableDescriptor().getColumnFamilies();
556       byte[][] columns = new byte[cds.length][];
557       for (int i = 0; i < cds.length; i++) {
558         columns[i] = Bytes.add(cds[i].getName(),
559             KeyValue.COLUMN_FAMILY_DELIM_ARRAY);
560       }
561       return columns;
562     }
563 
564     /**
565      * Creates and returns an HTable instance from a given table name.
566      *
567      * @param tableName
568      *          name of table
569      * @return HTable object
570      * @throws IOException
571      * @throws IOError
572      */
573     public HTableInterface getTable(final byte[] tableName) throws
574         IOException {
575       String table = Bytes.toString(tableName);
576       return connectionCache.getTable(table);
577     }
578 
579     public HTableInterface getTable(final ByteBuffer tableName) throws IOException {
580       return getTable(getBytes(tableName));
581     }
582 
583     /**
584      * Assigns a unique ID to the scanner and adds the mapping to an internal
585      * hash-map.
586      *
587      * @param scanner
588      * @return integer scanner id
589      */
590     protected synchronized int addScanner(ResultScanner scanner,boolean sortColumns) {
591       int id = nextScannerId++;
592       ResultScannerWrapper resultScannerWrapper = new ResultScannerWrapper(scanner, sortColumns);
593       scannerMap.put(id, resultScannerWrapper);
594       return id;
595     }
596 
597     /**
598      * Returns the scanner associated with the specified ID.
599      *
600      * @param id
601      * @return a Scanner, or null if ID was invalid.
602      */
603     protected synchronized ResultScannerWrapper getScanner(int id) {
604       return scannerMap.get(id);
605     }
606 
607     /**
608      * Removes the scanner associated with the specified ID from the internal
609      * id->scanner hash-map.
610      *
611      * @param id
612      * @return a Scanner, or null if ID was invalid.
613      */
614     protected synchronized ResultScannerWrapper removeScanner(int id) {
615       return scannerMap.remove(id);
616     }
617 
618     protected HBaseHandler(final Configuration c,
619         final UserProvider userProvider) throws IOException {
620       this.conf = c;
621       scannerMap = new HashMap<Integer, ResultScannerWrapper>();
622       this.coalescer = new IncrementCoalescer(this);
623 
624       int cleanInterval = conf.getInt(CLEANUP_INTERVAL, 10 * 1000);
625       int maxIdleTime = conf.getInt(MAX_IDLETIME, 10 * 60 * 1000);
626       connectionCache = new ConnectionCache(
627         conf, userProvider, cleanInterval, maxIdleTime);
628     }
629 
630     /**
631      * Obtain HBaseAdmin. Creates the instance if it is not already created.
632      */
633     private HBaseAdmin getHBaseAdmin() throws IOException {
634       return connectionCache.getAdmin();
635     }
636 
637     void setEffectiveUser(String effectiveUser) {
638       connectionCache.setEffectiveUser(effectiveUser);
639     }
640 
641     @Override
642     public void enableTable(ByteBuffer tableName) throws IOError {
643       try{
644         getHBaseAdmin().enableTable(getBytes(tableName));
645       } catch (IOException e) {
646         LOG.warn(e.getMessage(), e);
647         throw new IOError(e.getMessage());
648       }
649     }
650 
651     @Override
652     public void disableTable(ByteBuffer tableName) throws IOError{
653       try{
654         getHBaseAdmin().disableTable(getBytes(tableName));
655       } catch (IOException e) {
656         LOG.warn(e.getMessage(), e);
657         throw new IOError(e.getMessage());
658       }
659     }
660 
661     @Override
662     public boolean isTableEnabled(ByteBuffer tableName) throws IOError {
663       try {
664         return HTable.isTableEnabled(this.conf, getBytes(tableName));
665       } catch (IOException e) {
666         LOG.warn(e.getMessage(), e);
667         throw new IOError(e.getMessage());
668       }
669     }
670 
671     @Override
672     public void compact(ByteBuffer tableNameOrRegionName) throws IOError {
673       try{
674         getHBaseAdmin().compact(getBytes(tableNameOrRegionName));
675       } catch (InterruptedException e) {
676         throw new IOError(e.getMessage());
677       } catch (IOException e) {
678         LOG.warn(e.getMessage(), e);
679         throw new IOError(e.getMessage());
680       }
681     }
682 
683     @Override
684     public void majorCompact(ByteBuffer tableNameOrRegionName) throws IOError {
685       try{
686         getHBaseAdmin().majorCompact(getBytes(tableNameOrRegionName));
687       } catch (InterruptedException e) {
688         LOG.warn(e.getMessage(), e);
689         throw new IOError(e.getMessage());
690       } catch (IOException e) {
691         LOG.warn(e.getMessage(), e);
692         throw new IOError(e.getMessage());
693       }
694     }
695 
696     @Override
697     public List<ByteBuffer> getTableNames() throws IOError {
698       try {
699         TableName[] tableNames = this.getHBaseAdmin().listTableNames();
700         ArrayList<ByteBuffer> list = new ArrayList<ByteBuffer>(tableNames.length);
701         for (int i = 0; i < tableNames.length; i++) {
702           list.add(ByteBuffer.wrap(tableNames[i].getName()));
703         }
704         return list;
705       } catch (IOException e) {
706         LOG.warn(e.getMessage(), e);
707         throw new IOError(e.getMessage());
708       }
709     }
710 
711     /**
712      * @return the list of regions in the given table, or an empty list if the table does not exist
713      */
714     @Override
715     public List<TRegionInfo> getTableRegions(ByteBuffer tableName)
716     throws IOError {
717       try {
718         HTableInterface table;
719         try {
720           table = getTable(tableName);
721         } catch (TableNotFoundException ex) {
722           return new ArrayList<TRegionInfo>();
723         }
724         Map<HRegionInfo, ServerName> regionLocations = ((HTable)table).getRegionLocations();
725         List<TRegionInfo> results = new ArrayList<TRegionInfo>();
726         for (Map.Entry<HRegionInfo, ServerName> entry :
727             regionLocations.entrySet()) {
728           HRegionInfo info = entry.getKey();
729           ServerName serverName = entry.getValue();
730           TRegionInfo region = new TRegionInfo();
731           region.serverName = ByteBuffer.wrap(
732               Bytes.toBytes(serverName.getHostname()));
733           region.port = serverName.getPort();
734           region.startKey = ByteBuffer.wrap(info.getStartKey());
735           region.endKey = ByteBuffer.wrap(info.getEndKey());
736           region.id = info.getRegionId();
737           region.name = ByteBuffer.wrap(info.getRegionName());
738           region.version = info.getVersion();
739           results.add(region);
740         }
741         return results;
742       } catch (TableNotFoundException e) {
743         // Return empty list for non-existing table
744         return Collections.emptyList();
745       } catch (IOException e){
746         LOG.warn(e.getMessage(), e);
747         throw new IOError(e.getMessage());
748       }
749     }
750 
751     @Deprecated
752     @Override
753     public List<TCell> get(
754         ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
755         Map<ByteBuffer, ByteBuffer> attributes)
756         throws IOError {
757       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
758       if (famAndQf.length == 1) {
759         return get(tableName, row, famAndQf[0], null, attributes);
760       }
761       if (famAndQf.length == 2) {
762         return get(tableName, row, famAndQf[0], famAndQf[1], attributes);
763       }
764       throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
765     }
766 
767     /**
768      * Note: this internal interface is slightly different from public APIs in regard to handling
769      * of the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather,
770      * we respect qual == null as a request for the entire column family. The caller (
771      * {@link #get(ByteBuffer, ByteBuffer, ByteBuffer, Map)}) interface IS consistent in that the
772      * column is parse like normal.
773      */
774     protected List<TCell> get(ByteBuffer tableName,
775                               ByteBuffer row,
776                               byte[] family,
777                               byte[] qualifier,
778                               Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
779       HTableInterface table = null;
780       try {
781         table = getTable(tableName);
782         Get get = new Get(getBytes(row));
783         addAttributes(get, attributes);
784         if (qualifier == null) {
785           get.addFamily(family);
786         } else {
787           get.addColumn(family, qualifier);
788         }
789         Result result = table.get(get);
790         return ThriftUtilities.cellFromHBase(result.rawCells());
791       } catch (IOException e) {
792         LOG.warn(e.getMessage(), e);
793         throw new IOError(Throwables.getStackTraceAsString(e));
794       } finally {
795         closeTable(table);
796       }
797     }
798 
799     @Deprecated
800     @Override
801     public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
802         int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
803       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
804       if(famAndQf.length == 1) {
805         return getVer(tableName, row, famAndQf[0], null, numVersions, attributes);
806       }
807       if (famAndQf.length == 2) {
808         return getVer(tableName, row, famAndQf[0], famAndQf[1], numVersions, attributes);
809       }
810       throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
811 
812     }
813 
814     /**
815      * Note: this public interface is slightly different from public Java APIs in regard to
816      * handling of the qualifier. Here we differ from the public Java API in that null != byte[0].
817      * Rather, we respect qual == null as a request for the entire column family. If you want to
818      * access the entire column family, use
819      * {@link #getVer(ByteBuffer, ByteBuffer, ByteBuffer, int, Map)} with a {@code column} value
820      * that lacks a {@code ':'}.
821      */
822     public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, byte[] family,
823         byte[] qualifier, int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
824       HTableInterface table = null;
825       try {
826         table = getTable(tableName);
827         Get get = new Get(getBytes(row));
828         addAttributes(get, attributes);
829         if (null == qualifier) {
830           get.addFamily(family);
831         } else {
832           get.addColumn(family, qualifier);
833         }
834         get.setMaxVersions(numVersions);
835         Result result = table.get(get);
836         return ThriftUtilities.cellFromHBase(result.rawCells());
837       } catch (IOException e) {
838         LOG.warn(e.getMessage(), e);
839         throw new IOError(Throwables.getStackTraceAsString(e));
840       } finally{
841         closeTable(table);
842       }
843     }
844 
845     @Deprecated
846     @Override
847     public List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
848         long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
849       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
850       if (famAndQf.length == 1) {
851         return getVerTs(tableName, row, famAndQf[0], null, timestamp, numVersions, attributes);
852       }
853       if (famAndQf.length == 2) {
854         return getVerTs(tableName, row, famAndQf[0], famAndQf[1], timestamp, numVersions,
855           attributes);
856       }
857       throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
858     }
859 
860     /**
861      * Note: this internal interface is slightly different from public APIs in regard to handling
862      * of the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather,
863      * we respect qual == null as a request for the entire column family. The caller (
864      * {@link #getVerTs(ByteBuffer, ByteBuffer, ByteBuffer, long, int, Map)}) interface IS
865      * consistent in that the column is parse like normal.
866      */
867     protected List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, byte[] family,
868         byte[] qualifier, long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes)
869         throws IOError {
870       HTableInterface table = null;
871       try {
872         table = getTable(tableName);
873         Get get = new Get(getBytes(row));
874         addAttributes(get, attributes);
875         if (null == qualifier) {
876           get.addFamily(family);
877         } else {
878           get.addColumn(family, qualifier);
879         }
880         get.setTimeRange(0, timestamp);
881         get.setMaxVersions(numVersions);
882         Result result = table.get(get);
883         return ThriftUtilities.cellFromHBase(result.rawCells());
884       } catch (IOException e) {
885         LOG.warn(e.getMessage(), e);
886         throw new IOError(Throwables.getStackTraceAsString(e));
887       } finally{
888         closeTable(table);
889       }
890     }
891 
892     @Override
893     public List<TRowResult> getRow(ByteBuffer tableName, ByteBuffer row,
894         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
895       return getRowWithColumnsTs(tableName, row, null,
896                                  HConstants.LATEST_TIMESTAMP,
897                                  attributes);
898     }
899 
900     @Override
901     public List<TRowResult> getRowWithColumns(ByteBuffer tableName,
902                                               ByteBuffer row,
903         List<ByteBuffer> columns,
904         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
905       return getRowWithColumnsTs(tableName, row, columns,
906                                  HConstants.LATEST_TIMESTAMP,
907                                  attributes);
908     }
909 
910     @Override
911     public List<TRowResult> getRowTs(ByteBuffer tableName, ByteBuffer row,
912         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
913       return getRowWithColumnsTs(tableName, row, null,
914                                  timestamp, attributes);
915     }
916 
917     @Override
918     public List<TRowResult> getRowWithColumnsTs(
919         ByteBuffer tableName, ByteBuffer row, List<ByteBuffer> columns,
920         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
921       HTableInterface table = null;
922       try {
923         table = getTable(tableName);
924         if (columns == null) {
925           Get get = new Get(getBytes(row));
926           addAttributes(get, attributes);
927           get.setTimeRange(0, timestamp);
928           Result result = table.get(get);
929           return ThriftUtilities.rowResultFromHBase(result);
930         }
931         Get get = new Get(getBytes(row));
932         addAttributes(get, attributes);
933         for(ByteBuffer column : columns) {
934           byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
935           if (famAndQf.length == 1) {
936               get.addFamily(famAndQf[0]);
937           } else {
938               get.addColumn(famAndQf[0], famAndQf[1]);
939           }
940         }
941         get.setTimeRange(0, timestamp);
942         Result result = table.get(get);
943         return ThriftUtilities.rowResultFromHBase(result);
944       } catch (IOException e) {
945         LOG.warn(e.getMessage(), e);
946         throw new IOError(Throwables.getStackTraceAsString(e));
947       } finally{
948         closeTable(table);
949       }
950     }
951 
952     @Override
953     public List<TRowResult> getRows(ByteBuffer tableName,
954                                     List<ByteBuffer> rows,
955         Map<ByteBuffer, ByteBuffer> attributes)
956         throws IOError {
957       return getRowsWithColumnsTs(tableName, rows, null,
958                                   HConstants.LATEST_TIMESTAMP,
959                                   attributes);
960     }
961 
962     @Override
963     public List<TRowResult> getRowsWithColumns(ByteBuffer tableName,
964                                                List<ByteBuffer> rows,
965         List<ByteBuffer> columns,
966         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
967       return getRowsWithColumnsTs(tableName, rows, columns,
968                                   HConstants.LATEST_TIMESTAMP,
969                                   attributes);
970     }
971 
972     @Override
973     public List<TRowResult> getRowsTs(ByteBuffer tableName,
974                                       List<ByteBuffer> rows,
975         long timestamp,
976         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
977       return getRowsWithColumnsTs(tableName, rows, null,
978                                   timestamp, attributes);
979     }
980 
981     @Override
982     public List<TRowResult> getRowsWithColumnsTs(ByteBuffer tableName,
983                                                  List<ByteBuffer> rows,
984         List<ByteBuffer> columns, long timestamp,
985         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
986       HTableInterface table = null;
987       try {
988         List<Get> gets = new ArrayList<Get>(rows.size());
989         table = getTable(tableName);
990         if (metrics != null) {
991           metrics.incNumRowKeysInBatchGet(rows.size());
992         }
993         for (ByteBuffer row : rows) {
994           Get get = new Get(getBytes(row));
995           addAttributes(get, attributes);
996           if (columns != null) {
997 
998             for(ByteBuffer column : columns) {
999               byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
1000               if (famAndQf.length == 1) {
1001                 get.addFamily(famAndQf[0]);
1002               } else {
1003                 get.addColumn(famAndQf[0], famAndQf[1]);
1004               }
1005             }
1006           }
1007           get.setTimeRange(0, timestamp);
1008           gets.add(get);
1009         }
1010         Result[] result = table.get(gets);
1011         return ThriftUtilities.rowResultFromHBase(result);
1012       } catch (IOException e) {
1013         LOG.warn(e.getMessage(), e);
1014         throw new IOError(Throwables.getStackTraceAsString(e));
1015       } finally{
1016         closeTable(table);
1017       }
1018     }
1019 
1020     @Override
1021     public void deleteAll(
1022         ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
1023         Map<ByteBuffer, ByteBuffer> attributes)
1024         throws IOError {
1025       deleteAllTs(tableName, row, column, HConstants.LATEST_TIMESTAMP,
1026                   attributes);
1027     }
1028 
1029     @Override
1030     public void deleteAllTs(ByteBuffer tableName,
1031                             ByteBuffer row,
1032                             ByteBuffer column,
1033         long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
1034       HTableInterface table = null;
1035       try {
1036         table = getTable(tableName);
1037         Delete delete  = new Delete(getBytes(row));
1038         addAttributes(delete, attributes);
1039         byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
1040         if (famAndQf.length == 1) {
1041           delete.deleteFamily(famAndQf[0], timestamp);
1042         } else {
1043           delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
1044         }
1045         table.delete(delete);
1046 
1047       } catch (IOException e) {
1048         LOG.warn(e.getMessage(), e);
1049         throw new IOError(Throwables.getStackTraceAsString(e));
1050       } finally {
1051         closeTable(table);
1052       }
1053     }
1054 
1055     @Override
1056     public void deleteAllRow(
1057         ByteBuffer tableName, ByteBuffer row,
1058         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
1059       deleteAllRowTs(tableName, row, HConstants.LATEST_TIMESTAMP, attributes);
1060     }
1061 
1062     @Override
1063     public void deleteAllRowTs(
1064         ByteBuffer tableName, ByteBuffer row, long timestamp,
1065         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
1066       HTableInterface table = null;
1067       try {
1068         table = getTable(tableName);
1069         Delete delete  = new Delete(getBytes(row), timestamp);
1070         addAttributes(delete, attributes);
1071         table.delete(delete);
1072       } catch (IOException e) {
1073         LOG.warn(e.getMessage(), e);
1074         throw new IOError(Throwables.getStackTraceAsString(e));
1075       } finally {
1076         closeTable(table);
1077       }
1078     }
1079 
1080     @Override
1081     public void createTable(ByteBuffer in_tableName,
1082         List<ColumnDescriptor> columnFamilies) throws IOError,
1083         IllegalArgument, AlreadyExists {
1084       byte [] tableName = getBytes(in_tableName);
1085       try {
1086         if (getHBaseAdmin().tableExists(tableName)) {
1087           throw new AlreadyExists("table name already in use");
1088         }
1089         HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
1090         for (ColumnDescriptor col : columnFamilies) {
1091           HColumnDescriptor colDesc = ThriftUtilities.colDescFromThrift(col);
1092           desc.addFamily(colDesc);
1093         }
1094         getHBaseAdmin().createTable(desc);
1095       } catch (IOException e) {
1096         LOG.warn(e.getMessage(), e);
1097         throw new IOError(e.getMessage());
1098       } catch (IllegalArgumentException e) {
1099         LOG.warn(e.getMessage(), e);
1100         throw new IllegalArgument(e.getMessage());
1101       }
1102     }
1103 
1104     @Override
1105     public void deleteTable(ByteBuffer in_tableName) throws IOError {
1106       byte [] tableName = getBytes(in_tableName);
1107       if (LOG.isDebugEnabled()) {
1108         LOG.debug("deleteTable: table=" + Bytes.toString(tableName));
1109       }
1110       try {
1111         if (!getHBaseAdmin().tableExists(tableName)) {
1112           throw new IOException("table does not exist");
1113         }
1114         getHBaseAdmin().deleteTable(tableName);
1115       } catch (IOException e) {
1116         LOG.warn(e.getMessage(), e);
1117         throw new IOError(e.getMessage());
1118       }
1119     }
1120 
1121     @Override
1122     public void mutateRow(ByteBuffer tableName, ByteBuffer row,
1123         List<Mutation> mutations, Map<ByteBuffer, ByteBuffer> attributes)
1124         throws IOError, IllegalArgument {
1125       mutateRowTs(tableName, row, mutations, HConstants.LATEST_TIMESTAMP,
1126                   attributes);
1127     }
1128 
1129     @Override
1130     public void mutateRowTs(ByteBuffer tableName, ByteBuffer row,
1131         List<Mutation> mutations, long timestamp,
1132         Map<ByteBuffer, ByteBuffer> attributes)
1133         throws IOError, IllegalArgument {
1134       HTableInterface table = null;
1135       try {
1136         table = getTable(tableName);
1137         Put put = new Put(getBytes(row), timestamp);
1138         addAttributes(put, attributes);
1139 
1140         Delete delete = new Delete(getBytes(row));
1141         addAttributes(delete, attributes);
1142         if (metrics != null) {
1143           metrics.incNumRowKeysInBatchMutate(mutations.size());
1144         }
1145 
1146         // I apologize for all this mess :)
1147         for (Mutation m : mutations) {
1148           byte[][] famAndQf = KeyValue.parseColumn(getBytes(m.column));
1149           if (m.isDelete) {
1150             if (famAndQf.length == 1) {
1151               delete.deleteFamily(famAndQf[0], timestamp);
1152             } else {
1153               delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
1154             }
1155             delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL
1156                 : Durability.SKIP_WAL);
1157           } else {
1158             if(famAndQf.length == 1) {
1159               LOG.warn("No column qualifier specified. Delete is the only mutation supported "
1160                   + "over the whole column family.");
1161             } else {
1162               put.addImmutable(famAndQf[0], famAndQf[1],
1163                   m.value != null ? getBytes(m.value)
1164                       : HConstants.EMPTY_BYTE_ARRAY);
1165             }
1166             put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1167           }
1168         }
1169         if (!delete.isEmpty())
1170           table.delete(delete);
1171         if (!put.isEmpty())
1172           table.put(put);
1173       } catch (IOException e) {
1174         LOG.warn(e.getMessage(), e);
1175         throw new IOError(e.getMessage());
1176       } catch (IllegalArgumentException e) {
1177         LOG.warn(e.getMessage(), e);
1178         throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1179       } finally{
1180         closeTable(table);
1181       }
1182     }
1183 
1184     @Override
1185     public void mutateRows(ByteBuffer tableName, List<BatchMutation> rowBatches,
1186         Map<ByteBuffer, ByteBuffer> attributes)
1187         throws IOError, IllegalArgument, TException {
1188       mutateRowsTs(tableName, rowBatches, HConstants.LATEST_TIMESTAMP, attributes);
1189     }
1190 
1191     @Override
1192     public void mutateRowsTs(
1193         ByteBuffer tableName, List<BatchMutation> rowBatches, long timestamp,
1194         Map<ByteBuffer, ByteBuffer> attributes)
1195         throws IOError, IllegalArgument, TException {
1196       List<Put> puts = new ArrayList<Put>();
1197       List<Delete> deletes = new ArrayList<Delete>();
1198 
1199       for (BatchMutation batch : rowBatches) {
1200         byte[] row = getBytes(batch.row);
1201         List<Mutation> mutations = batch.mutations;
1202         Delete delete = new Delete(row);
1203         addAttributes(delete, attributes);
1204         Put put = new Put(row, timestamp);
1205         addAttributes(put, attributes);
1206         for (Mutation m : mutations) {
1207           byte[][] famAndQf = KeyValue.parseColumn(getBytes(m.column));
1208           if (m.isDelete) {
1209             // no qualifier, family only.
1210             if (famAndQf.length == 1) {
1211               delete.deleteFamily(famAndQf[0], timestamp);
1212             } else {
1213               delete.deleteColumns(famAndQf[0], famAndQf[1], timestamp);
1214             }
1215             delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL
1216                 : Durability.SKIP_WAL);
1217           } else {
1218             if (famAndQf.length == 1) {
1219               LOG.warn("No column qualifier specified. Delete is the only mutation supported "
1220                   + "over the whole column family.");
1221             }
1222             if (famAndQf.length == 2) {
1223               put.addImmutable(famAndQf[0], famAndQf[1],
1224                   m.value != null ? getBytes(m.value)
1225                       : HConstants.EMPTY_BYTE_ARRAY);
1226             } else {
1227               throw new IllegalArgumentException("Invalid famAndQf provided.");
1228             }
1229             put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1230           }
1231         }
1232         if (!delete.isEmpty())
1233           deletes.add(delete);
1234         if (!put.isEmpty())
1235           puts.add(put);
1236       }
1237       HTableInterface table = null;
1238       try {
1239         table = getTable(tableName);
1240         if (!puts.isEmpty())
1241           table.put(puts);
1242         if (!deletes.isEmpty())
1243           table.delete(deletes);
1244 
1245       } catch (IOException e) {
1246         LOG.warn(e.getMessage(), e);
1247         throw new IOError(e.getMessage());
1248       } catch (IllegalArgumentException e) {
1249         LOG.warn(e.getMessage(), e);
1250         throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1251       } finally{
1252         closeTable(table);
1253       }
1254     }
1255 
1256     @Deprecated
1257     @Override
1258     public long atomicIncrement(
1259         ByteBuffer tableName, ByteBuffer row, ByteBuffer column, long amount)
1260             throws IOError, IllegalArgument, TException {
1261       byte [][] famAndQf = KeyValue.parseColumn(getBytes(column));
1262       if(famAndQf.length == 1) {
1263         return atomicIncrement(tableName, row, famAndQf[0], HConstants.EMPTY_BYTE_ARRAY, amount);
1264       }
1265       return atomicIncrement(tableName, row, famAndQf[0], famAndQf[1], amount);
1266     }
1267 
1268     protected long atomicIncrement(ByteBuffer tableName, ByteBuffer row,
1269         byte [] family, byte [] qualifier, long amount)
1270         throws IOError, IllegalArgument, TException {
1271       HTableInterface table = null;
1272       try {
1273         table = getTable(tableName);
1274         return table.incrementColumnValue(
1275             getBytes(row), family, qualifier, amount);
1276       } catch (IOException e) {
1277         LOG.warn(e.getMessage(), e);
1278         throw new IOError(Throwables.getStackTraceAsString(e));
1279       } finally {
1280         closeTable(table);
1281       }
1282     }
1283 
1284     @Override
1285     public void scannerClose(int id) throws IOError, IllegalArgument {
1286       LOG.debug("scannerClose: id=" + id);
1287       ResultScannerWrapper resultScannerWrapper = getScanner(id);
1288       if (resultScannerWrapper == null) {
1289         String message = "scanner ID is invalid";
1290         LOG.warn(message);
1291         throw new IllegalArgument("scanner ID is invalid");
1292       }
1293       resultScannerWrapper.getScanner().close();
1294       removeScanner(id);
1295     }
1296 
1297     @Override
1298     public List<TRowResult> scannerGetList(int id,int nbRows)
1299         throws IllegalArgument, IOError {
1300       LOG.debug("scannerGetList: id=" + id);
1301       ResultScannerWrapper resultScannerWrapper = getScanner(id);
1302       if (null == resultScannerWrapper) {
1303         String message = "scanner ID is invalid";
1304         LOG.warn(message);
1305         throw new IllegalArgument("scanner ID is invalid");
1306       }
1307 
1308       Result [] results = null;
1309       try {
1310         results = resultScannerWrapper.getScanner().next(nbRows);
1311         if (null == results) {
1312           return new ArrayList<TRowResult>();
1313         }
1314       } catch (IOException e) {
1315         LOG.warn(e.getMessage(), e);
1316         throw new IOError(e.getMessage());
1317       }
1318       return ThriftUtilities.rowResultFromHBase(results, resultScannerWrapper.isColumnSorted());
1319     }
1320 
1321     @Override
1322     public List<TRowResult> scannerGet(int id) throws IllegalArgument, IOError {
1323       return scannerGetList(id,1);
1324     }
1325 
1326     @Override
1327     public int scannerOpenWithScan(ByteBuffer tableName, TScan tScan,
1328         Map<ByteBuffer, ByteBuffer> attributes)
1329         throws IOError {
1330       HTableInterface table = null;
1331       try {
1332         table = getTable(tableName);
1333         Scan scan = new Scan();
1334         addAttributes(scan, attributes);
1335         if (tScan.isSetStartRow()) {
1336           scan.setStartRow(tScan.getStartRow());
1337         }
1338         if (tScan.isSetStopRow()) {
1339           scan.setStopRow(tScan.getStopRow());
1340         }
1341         if (tScan.isSetTimestamp()) {
1342           scan.setTimeRange(0, tScan.getTimestamp());
1343         }
1344         if (tScan.isSetCaching()) {
1345           scan.setCaching(tScan.getCaching());
1346         }
1347         if (tScan.isSetBatchSize()) {
1348           scan.setBatch(tScan.getBatchSize());
1349         }
1350         if (tScan.isSetColumns() && tScan.getColumns().size() != 0) {
1351           for(ByteBuffer column : tScan.getColumns()) {
1352             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1353             if(famQf.length == 1) {
1354               scan.addFamily(famQf[0]);
1355             } else {
1356               scan.addColumn(famQf[0], famQf[1]);
1357             }
1358           }
1359         }
1360         if (tScan.isSetFilterString()) {
1361           ParseFilter parseFilter = new ParseFilter();
1362           scan.setFilter(
1363               parseFilter.parseFilterString(tScan.getFilterString()));
1364         }
1365         if (tScan.isSetReversed()) {
1366           scan.setReversed(tScan.isReversed());
1367         }
1368         return addScanner(table.getScanner(scan), tScan.sortColumns);
1369       } catch (IOException e) {
1370         LOG.warn(e.getMessage(), e);
1371         throw new IOError(Throwables.getStackTraceAsString(e));
1372       } finally{
1373         closeTable(table);
1374       }
1375     }
1376 
1377     @Override
1378     public int scannerOpen(ByteBuffer tableName, ByteBuffer startRow,
1379         List<ByteBuffer> columns,
1380         Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
1381       HTableInterface table = null;
1382       try {
1383         table = getTable(tableName);
1384         Scan scan = new Scan(getBytes(startRow));
1385         addAttributes(scan, attributes);
1386         if(columns != null && columns.size() != 0) {
1387           for(ByteBuffer column : columns) {
1388             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1389             if(famQf.length == 1) {
1390               scan.addFamily(famQf[0]);
1391             } else {
1392               scan.addColumn(famQf[0], famQf[1]);
1393             }
1394           }
1395         }
1396         return addScanner(table.getScanner(scan), false);
1397       } catch (IOException e) {
1398         LOG.warn(e.getMessage(), e);
1399         throw new IOError(Throwables.getStackTraceAsString(e));
1400       } finally{
1401         closeTable(table);
1402       }
1403     }
1404 
1405     @Override
1406     public int scannerOpenWithStop(ByteBuffer tableName, ByteBuffer startRow,
1407         ByteBuffer stopRow, List<ByteBuffer> columns,
1408         Map<ByteBuffer, ByteBuffer> attributes)
1409         throws IOError, TException {
1410       HTableInterface table = null;
1411       try {
1412         table = getTable(tableName);
1413         Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1414         addAttributes(scan, attributes);
1415         if(columns != null && columns.size() != 0) {
1416           for(ByteBuffer column : columns) {
1417             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1418             if(famQf.length == 1) {
1419               scan.addFamily(famQf[0]);
1420             } else {
1421               scan.addColumn(famQf[0], famQf[1]);
1422             }
1423           }
1424         }
1425         return addScanner(table.getScanner(scan), false);
1426       } catch (IOException e) {
1427         LOG.warn(e.getMessage(), e);
1428         throw new IOError(Throwables.getStackTraceAsString(e));
1429       } finally{
1430         closeTable(table);
1431       }
1432     }
1433 
1434     @Override
1435     public int scannerOpenWithPrefix(ByteBuffer tableName,
1436                                      ByteBuffer startAndPrefix,
1437                                      List<ByteBuffer> columns,
1438         Map<ByteBuffer, ByteBuffer> attributes)
1439         throws IOError, TException {
1440       HTableInterface table = null;
1441       try {
1442         table = getTable(tableName);
1443         Scan scan = new Scan(getBytes(startAndPrefix));
1444         addAttributes(scan, attributes);
1445         Filter f = new WhileMatchFilter(
1446             new PrefixFilter(getBytes(startAndPrefix)));
1447         scan.setFilter(f);
1448         if (columns != null && columns.size() != 0) {
1449           for(ByteBuffer column : columns) {
1450             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1451             if(famQf.length == 1) {
1452               scan.addFamily(famQf[0]);
1453             } else {
1454               scan.addColumn(famQf[0], famQf[1]);
1455             }
1456           }
1457         }
1458         return addScanner(table.getScanner(scan), false);
1459       } catch (IOException e) {
1460         LOG.warn(e.getMessage(), e);
1461         throw new IOError(Throwables.getStackTraceAsString(e));
1462       } finally{
1463         closeTable(table);
1464       }
1465     }
1466 
1467     @Override
1468     public int scannerOpenTs(ByteBuffer tableName, ByteBuffer startRow,
1469         List<ByteBuffer> columns, long timestamp,
1470         Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
1471       HTableInterface table = null;
1472       try {
1473         table = getTable(tableName);
1474         Scan scan = new Scan(getBytes(startRow));
1475         addAttributes(scan, attributes);
1476         scan.setTimeRange(0, timestamp);
1477         if (columns != null && columns.size() != 0) {
1478           for (ByteBuffer column : columns) {
1479             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1480             if(famQf.length == 1) {
1481               scan.addFamily(famQf[0]);
1482             } else {
1483               scan.addColumn(famQf[0], famQf[1]);
1484             }
1485           }
1486         }
1487         return addScanner(table.getScanner(scan), false);
1488       } catch (IOException e) {
1489         LOG.warn(e.getMessage(), e);
1490         throw new IOError(Throwables.getStackTraceAsString(e));
1491       } finally{
1492         closeTable(table);
1493       }
1494     }
1495 
1496     @Override
1497     public int scannerOpenWithStopTs(ByteBuffer tableName, ByteBuffer startRow,
1498         ByteBuffer stopRow, List<ByteBuffer> columns, long timestamp,
1499         Map<ByteBuffer, ByteBuffer> attributes)
1500         throws IOError, TException {
1501       HTableInterface table = null;
1502       try {
1503         table = getTable(tableName);
1504         Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1505         addAttributes(scan, attributes);
1506         scan.setTimeRange(0, timestamp);
1507         if (columns != null && columns.size() != 0) {
1508           for (ByteBuffer column : columns) {
1509             byte [][] famQf = KeyValue.parseColumn(getBytes(column));
1510             if(famQf.length == 1) {
1511               scan.addFamily(famQf[0]);
1512             } else {
1513               scan.addColumn(famQf[0], famQf[1]);
1514             }
1515           }
1516         }
1517         scan.setTimeRange(0, timestamp);
1518         return addScanner(table.getScanner(scan), false);
1519       } catch (IOException e) {
1520         LOG.warn(e.getMessage(), e);
1521         throw new IOError(Throwables.getStackTraceAsString(e));
1522       } finally{
1523         closeTable(table);
1524       }
1525     }
1526 
1527     @Override
1528     public Map<ByteBuffer, ColumnDescriptor> getColumnDescriptors(
1529         ByteBuffer tableName) throws IOError, TException {
1530       HTableInterface table = null;
1531       try {
1532         TreeMap<ByteBuffer, ColumnDescriptor> columns =
1533           new TreeMap<ByteBuffer, ColumnDescriptor>();
1534 
1535         table = getTable(tableName);
1536         HTableDescriptor desc = table.getTableDescriptor();
1537 
1538         for (HColumnDescriptor e : desc.getFamilies()) {
1539           ColumnDescriptor col = ThriftUtilities.colDescFromHbase(e);
1540           columns.put(col.name, col);
1541         }
1542         return columns;
1543       } catch (IOException e) {
1544         LOG.warn(e.getMessage(), e);
1545         throw new IOError(Throwables.getStackTraceAsString(e));
1546       } finally {
1547         closeTable(table);
1548       }
1549     }
1550 
1551     @Override
1552     public List<TCell> getRowOrBefore(ByteBuffer tableName, ByteBuffer row,
1553         ByteBuffer family) throws IOError {
1554       HTableInterface table = null;
1555       try {
1556         table = getTable(getBytes(tableName));
1557         Result result = table.getRowOrBefore(getBytes(row), getBytes(family));
1558         return ThriftUtilities.cellFromHBase(result.rawCells());
1559       } catch (IOException e) {
1560         LOG.warn(e.getMessage(), e);
1561         throw new IOError(e.getMessage());
1562       } finally {
1563         closeTable(table);
1564       }
1565     }
1566 
1567     @Override
1568     public TRegionInfo getRegionInfo(ByteBuffer searchRow) throws IOError {
1569       HTableInterface table = null;
1570       try {
1571         table = getTable(TableName.META_TABLE_NAME.getName());
1572         byte[] row = getBytes(searchRow);
1573         Result startRowResult = table.getRowOrBefore(
1574           row, HConstants.CATALOG_FAMILY);
1575 
1576         if (startRowResult == null) {
1577           throw new IOException("Cannot find row in "+ TableName.META_TABLE_NAME+", row="
1578                                 + Bytes.toStringBinary(row));
1579         }
1580 
1581         // find region start and end keys
1582         HRegionInfo regionInfo = HRegionInfo.getHRegionInfo(startRowResult);
1583         if (regionInfo == null) {
1584           throw new IOException("HRegionInfo REGIONINFO was null or " +
1585                                 " empty in Meta for row="
1586                                 + Bytes.toStringBinary(row));
1587         }
1588         TRegionInfo region = new TRegionInfo();
1589         region.setStartKey(regionInfo.getStartKey());
1590         region.setEndKey(regionInfo.getEndKey());
1591         region.id = regionInfo.getRegionId();
1592         region.setName(regionInfo.getRegionName());
1593         region.version = regionInfo.getVersion();
1594 
1595         // find region assignment to server
1596         ServerName serverName = HRegionInfo.getServerName(startRowResult);
1597         if (serverName != null) {
1598           region.setServerName(Bytes.toBytes(serverName.getHostname()));
1599           region.port = serverName.getPort();
1600         }
1601         return region;
1602       } catch (IOException e) {
1603         LOG.warn(e.getMessage(), e);
1604         throw new IOError(Throwables.getStackTraceAsString(e));
1605       } finally {
1606         closeTable(table);
1607       }
1608     }
1609 
1610     private void closeTable(HTableInterface table) throws IOError
1611     {
1612       try{
1613         if(table != null){
1614           table.close();
1615         }
1616       } catch (IOException e){
1617         LOG.error(e.getMessage(), e);
1618         throw new IOError(Throwables.getStackTraceAsString(e));
1619       }
1620     }
1621 
1622     private void initMetrics(ThriftMetrics metrics) {
1623       this.metrics = metrics;
1624     }
1625 
1626     @Override
1627     public void increment(TIncrement tincrement) throws IOError, TException {
1628 
1629       if (tincrement.getRow().length == 0 || tincrement.getTable().length == 0) {
1630         throw new TException("Must supply a table and a row key; can't increment");
1631       }
1632 
1633       if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1634         this.coalescer.queueIncrement(tincrement);
1635         return;
1636       }
1637 
1638       HTableInterface table = null;
1639       try {
1640         table = getTable(tincrement.getTable());
1641         Increment inc = ThriftUtilities.incrementFromThrift(tincrement);
1642         table.increment(inc);
1643       } catch (IOException e) {
1644         LOG.warn(e.getMessage(), e);
1645         throw new IOError(Throwables.getStackTraceAsString(e));
1646       } finally{
1647         closeTable(table);
1648       }
1649     }
1650 
1651     @Override
1652     public void incrementRows(List<TIncrement> tincrements) throws IOError, TException {
1653       if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1654         this.coalescer.queueIncrements(tincrements);
1655         return;
1656       }
1657       for (TIncrement tinc : tincrements) {
1658         increment(tinc);
1659       }
1660     }
1661   }
1662 
1663   /**
1664    * Adds all the attributes into the Operation object
1665    */
1666   private static void addAttributes(OperationWithAttributes op,
1667     Map<ByteBuffer, ByteBuffer> attributes) {
1668     if (attributes == null || attributes.size() == 0) {
1669       return;
1670     }
1671     for (Map.Entry<ByteBuffer, ByteBuffer> entry : attributes.entrySet()) {
1672       String name = Bytes.toStringBinary(getBytes(entry.getKey()));
1673       byte[] value =  getBytes(entry.getValue());
1674       op.setAttribute(name, value);
1675     }
1676   }
1677 
1678   public static void registerFilters(Configuration conf) {
1679     String[] filters = conf.getStrings("hbase.thrift.filters");
1680     if(filters != null) {
1681       for(String filterClass: filters) {
1682         String[] filterPart = filterClass.split(":");
1683         if(filterPart.length != 2) {
1684           LOG.warn("Invalid filter specification " + filterClass + " - skipping");
1685         } else {
1686           ParseFilter.registerFilter(filterPart[0], filterPart[1]);
1687         }
1688       }
1689     }
1690   }
1691 }