/**
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.thrift2;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.appendFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.compareOpFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.deleteFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.deletesFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.getFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.getsFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.incrementFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.putFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.putsFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.resultFromHBase;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.resultsFromHBase;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.rowMutationsFromThrift;
import static org.apache.hadoop.hbase.thrift2.ThriftUtilities.scanFromThrift;
import static org.apache.thrift.TBaseHelper.byteBufferToByteArray;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.security.UserProvider;
import org.apache.hadoop.hbase.thrift.ThriftMetrics;
import org.apache.hadoop.hbase.thrift2.generated.TAppend;
import org.apache.hadoop.hbase.thrift2.generated.TCompareOp;
import org.apache.hadoop.hbase.thrift2.generated.TDelete;
import org.apache.hadoop.hbase.thrift2.generated.TGet;
import org.apache.hadoop.hbase.thrift2.generated.THBaseService;
import org.apache.hadoop.hbase.thrift2.generated.THRegionLocation;
import org.apache.hadoop.hbase.thrift2.generated.TIOError;
import org.apache.hadoop.hbase.thrift2.generated.TIllegalArgument;
import org.apache.hadoop.hbase.thrift2.generated.TIncrement;
import org.apache.hadoop.hbase.thrift2.generated.TPut;
import org.apache.hadoop.hbase.thrift2.generated.TResult;
import org.apache.hadoop.hbase.thrift2.generated.TRowMutations;
import org.apache.hadoop.hbase.thrift2.generated.TScan;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.ConnectionCache;
import org.apache.thrift.TException;
/**
* This class is a glue object that connects Thrift RPC calls to the HBase client API primarily
* defined in the Table interface.
*/
@InterfaceAudience.Private
@SuppressWarnings("deprecation")
public class ThriftHBaseServiceHandler implements THBaseService.Iface {
// TODO: Size of pool configuraple
private static final Log LOG = LogFactory.getLog(ThriftHBaseServiceHandler.class);
// nextScannerId and scannerMap are used to manage scanner state
// TODO: Cleanup thread for Scanners, Scanner id wrap
private final AtomicInteger nextScannerId = new AtomicInteger(0);
private final Map<Integer, ResultScanner> scannerMap = new ConcurrentHashMap<>();
private final ConnectionCache connectionCache;
static final String CLEANUP_INTERVAL = "hbase.thrift.connection.cleanup-interval";
static final String MAX_IDLETIME = "hbase.thrift.connection.max-idletime";
public static THBaseService.Iface newInstance(
THBaseService.Iface handler, ThriftMetrics metrics) {
return (THBaseService.Iface) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
new Class[] { THBaseService.Iface.class }, new THBaseServiceMetricsProxy(handler, metrics));
}
private static final class THBaseServiceMetricsProxy implements InvocationHandler {
private final THBaseService.Iface handler;
private final ThriftMetrics metrics;
private THBaseServiceMetricsProxy(THBaseService.Iface handler, ThriftMetrics metrics) {
this.handler = handler;
this.metrics = metrics;
}
@Override
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
Object result;
long start = now();
try {
result = m.invoke(handler, args);
} catch (InvocationTargetException e) {
metrics.exception(e.getCause());
throw e.getTargetException();
} catch (Exception e) {
metrics.exception(e);
throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
} finally {
long processTime = now() - start;
metrics.incMethodTime(m.getName(), processTime);
}
return result;
}
}
private static class TIOErrorWithCause extends TIOError {
private Throwable cause;
public TIOErrorWithCause(Throwable cause) {
super();
this.cause = cause;
}
@Override
public Throwable getCause() {
return cause;
}
@Override
public boolean equals(Object other) {
if (super.equals(other) &&
other instanceof TIOErrorWithCause) {
Throwable otherCause = ((TIOErrorWithCause) other).getCause();
if (this.getCause() != null) {
return otherCause != null && this.getCause().equals(otherCause);
} else {
return otherCause == null;
}
}
return false;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (cause != null ? cause.hashCode() : 0);
return result;
}
}
private static long now() {
return System.nanoTime();
}
ThriftHBaseServiceHandler(final Configuration conf,
final UserProvider userProvider) throws IOException {
int cleanInterval = conf.getInt(CLEANUP_INTERVAL, 10 * 1000);
int maxIdleTime = conf.getInt(MAX_IDLETIME, 10 * 60 * 1000);
connectionCache = new ConnectionCache(
conf, userProvider, cleanInterval, maxIdleTime);
}
private Table getTable(ByteBuffer tableName) {
try {
return connectionCache.getTable(Bytes.toString(byteBufferToByteArray(tableName)));
} catch (IOException ie) {
throw new RuntimeException(ie);
}
}
private RegionLocator getLocator(ByteBuffer tableName) {
try {
return connectionCache.getRegionLocator(byteBufferToByteArray(tableName));
} catch (IOException ie) {
throw new RuntimeException(ie);
}
}
private void closeTable(Table table) throws TIOError {
try {
table.close();
} catch (IOException e) {
throw getTIOError(e);
}
}
private TIOError getTIOError(IOException e) {
TIOError err = new TIOErrorWithCause(e);
err.setMessage(e.getMessage());
return err;
}
/**
* Assigns a unique ID to the scanner and adds the mapping to an internal HashMap.
* @param scanner to add
* @return Id for this Scanner
*/
private int addScanner(ResultScanner scanner) {
int id = nextScannerId.getAndIncrement();
scannerMap.put(id, scanner);
return id;
}
/**
* Returns the Scanner associated with the specified Id.
* @param id of the Scanner to get
* @return a Scanner, or null if the Id is invalid
*/
private ResultScanner getScanner(int id) {
return scannerMap.get(id);
}
void setEffectiveUser(String effectiveUser) {
connectionCache.setEffectiveUser(effectiveUser);
}
/**
* Removes the scanner associated with the specified ID from the internal HashMap.
* @param id of the Scanner to remove
* @return the removed Scanner, or <code>null</code> if the Id is invalid
*/
protected ResultScanner removeScanner(int id) {
return scannerMap.remove(id);
}
@Override
public boolean exists(ByteBuffer table, TGet get) throws TIOError, TException {
Table htable = getTable(table);
try {
return htable.exists(getFromThrift(get));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public List<Boolean> existsAll(ByteBuffer table, List<TGet> gets) throws TIOError, TException {
Table htable = getTable(table);
try {
boolean[] exists = htable.existsAll(getsFromThrift(gets));
List<Boolean> result = new ArrayList<>(exists.length);
for (boolean exist : exists) {
result.add(exist);
}
return result;
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public TResult get(ByteBuffer table, TGet get) throws TIOError, TException {
Table htable = getTable(table);
try {
return resultFromHBase(htable.get(getFromThrift(get)));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public List<TResult> getMultiple(ByteBuffer table, List<TGet> gets) throws TIOError, TException {
Table htable = getTable(table);
try {
return resultsFromHBase(htable.get(getsFromThrift(gets)));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public void put(ByteBuffer table, TPut put) throws TIOError, TException {
Table htable = getTable(table);
try {
htable.put(putFromThrift(put));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public boolean checkAndPut(ByteBuffer table, ByteBuffer row, ByteBuffer family,
ByteBuffer qualifier, ByteBuffer value, TPut put) throws TIOError, TException {
Table htable = getTable(table);
try {
return htable.checkAndPut(byteBufferToByteArray(row), byteBufferToByteArray(family),
byteBufferToByteArray(qualifier), (value == null) ? null : byteBufferToByteArray(value),
putFromThrift(put));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public void putMultiple(ByteBuffer table, List<TPut> puts) throws TIOError, TException {
Table htable = getTable(table);
try {
htable.put(putsFromThrift(puts));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public void deleteSingle(ByteBuffer table, TDelete deleteSingle) throws TIOError, TException {
Table htable = getTable(table);
try {
htable.delete(deleteFromThrift(deleteSingle));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public List<TDelete> deleteMultiple(ByteBuffer table, List<TDelete> deletes) throws TIOError,
TException {
Table htable = getTable(table);
try {
htable.delete(deletesFromThrift(deletes));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
return Collections.emptyList();
}
@Override
public boolean checkAndMutate(ByteBuffer table, ByteBuffer row, ByteBuffer family,
ByteBuffer qualifier, TCompareOp compareOp, ByteBuffer value, TRowMutations rowMutations)
throws TIOError, TException {
try (final Table htable = getTable(table)) {
return htable.checkAndMutate(byteBufferToByteArray(row), byteBufferToByteArray(family),
byteBufferToByteArray(qualifier), compareOpFromThrift(compareOp),
byteBufferToByteArray(value), rowMutationsFromThrift(rowMutations));
} catch (IOException e) {
throw getTIOError(e);
}
}
@Override
public boolean checkAndDelete(ByteBuffer table, ByteBuffer row, ByteBuffer family,
ByteBuffer qualifier, ByteBuffer value, TDelete deleteSingle) throws TIOError, TException {
Table htable = getTable(table);
try {
if (value == null) {
return htable.checkAndDelete(byteBufferToByteArray(row), byteBufferToByteArray(family),
byteBufferToByteArray(qualifier), null, deleteFromThrift(deleteSingle));
} else {
return htable.checkAndDelete(byteBufferToByteArray(row), byteBufferToByteArray(family),
byteBufferToByteArray(qualifier), byteBufferToByteArray(value),
deleteFromThrift(deleteSingle));
}
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public TResult increment(ByteBuffer table, TIncrement increment) throws TIOError, TException {
Table htable = getTable(table);
try {
return resultFromHBase(htable.increment(incrementFromThrift(increment)));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public TResult append(ByteBuffer table, TAppend append) throws TIOError, TException {
Table htable = getTable(table);
try {
return resultFromHBase(htable.append(appendFromThrift(append)));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public int openScanner(ByteBuffer table, TScan scan) throws TIOError, TException {
Table htable = getTable(table);
ResultScanner resultScanner = null;
try {
resultScanner = htable.getScanner(scanFromThrift(scan));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
return addScanner(resultScanner);
}
@Override
public List<TResult> getScannerRows(int scannerId, int numRows) throws TIOError,
TIllegalArgument, TException {
ResultScanner scanner = getScanner(scannerId);
if (scanner == null) {
TIllegalArgument ex = new TIllegalArgument();
ex.setMessage("Invalid scanner Id");
throw ex;
}
try {
connectionCache.updateConnectionAccessTime();
return resultsFromHBase(scanner.next(numRows));
} catch (IOException e) {
throw getTIOError(e);
}
}
@Override
public List<TResult> getScannerResults(ByteBuffer table, TScan scan, int numRows)
throws TIOError, TException {
Table htable = getTable(table);
List<TResult> results = null;
ResultScanner scanner = null;
try {
scanner = htable.getScanner(scanFromThrift(scan));
results = resultsFromHBase(scanner.next(numRows));
} catch (IOException e) {
throw getTIOError(e);
} finally {
if (scanner != null) {
scanner.close();
}
closeTable(htable);
}
return results;
}
@Override
public void closeScanner(int scannerId) throws TIOError, TIllegalArgument, TException {
LOG.debug("scannerClose: id=" + scannerId);
ResultScanner scanner = getScanner(scannerId);
if (scanner == null) {
String message = "scanner ID is invalid";
LOG.warn(message);
TIllegalArgument ex = new TIllegalArgument();
ex.setMessage("Invalid scanner Id");
throw ex;
}
scanner.close();
removeScanner(scannerId);
}
@Override
public void mutateRow(ByteBuffer table, TRowMutations rowMutations) throws TIOError, TException {
Table htable = getTable(table);
try {
htable.mutateRow(rowMutationsFromThrift(rowMutations));
} catch (IOException e) {
throw getTIOError(e);
} finally {
closeTable(htable);
}
}
@Override
public List<THRegionLocation> getAllRegionLocations(ByteBuffer table)
throws TIOError, TException {
RegionLocator locator = null;
try {
locator = getLocator(table);
return ThriftUtilities.regionLocationsFromHBase(locator.getAllRegionLocations());
} catch (IOException e) {
throw getTIOError(e);
} finally {
if (locator != null) {
try {
locator.close();
} catch (IOException e) {
LOG.warn("Couldn't close the locator.", e);
}
}
}
}
@Override
public THRegionLocation getRegionLocation(ByteBuffer table, ByteBuffer row, boolean reload)
throws TIOError, TException {
RegionLocator locator = null;
try {
locator = getLocator(table);
byte[] rowBytes = byteBufferToByteArray(row);
HRegionLocation hrl = locator.getRegionLocation(rowBytes, reload);
return ThriftUtilities.regionLocationFromHBase(hrl);
} catch (IOException e) {
throw getTIOError(e);
} finally {
if (locator != null) {
try {
locator.close();
} catch (IOException e) {
LOG.warn("Couldn't close the locator.", e);
}
}
}
}
}