package com.rubiconproject.oss.kv.backends; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.pool.BasePoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import com.rubiconproject.oss.kv.BaseManagedKeyValueStore; import com.rubiconproject.oss.kv.KeyValueStoreException; import com.rubiconproject.oss.kv.ManagedKeyValueStore; import com.rubiconproject.oss.kv.annotations.Configurable; import com.rubiconproject.oss.kv.annotations.Configurable.Type; import com.rubiconproject.oss.kv.gen.Constants; import com.rubiconproject.oss.kv.gen.GetResult; import com.rubiconproject.oss.kv.gen.KeyValueService; import com.rubiconproject.oss.kv.gen.KeyValueStoreIOException; import com.rubiconproject.oss.kv.transcoder.SerializableTranscoder; import com.rubiconproject.oss.kv.transcoder.Transcoder; public class ThriftKeyValueStore extends BaseManagedKeyValueStore implements ManagedKeyValueStore { public static final String IDENTIFIER = "thrift"; private Log log = LogFactory.getLog(getClass()); private GenericObjectPool connectionPool; private Transcoder defaultTranscoder = new SerializableTranscoder(); private String host = "localhost"; private int port = Constants.DEFAULT_PORT; private boolean lifo = true; private int maxActive = 100; private int maxIdle = 100; private long maxWait = -1; private boolean testWhileIdle = false; private long timeBetweenEvictionRunsMillis = -1; public ThriftKeyValueStore() { } public ThriftKeyValueStore(String host, int port) { this.host = host; this.port = port; } @Configurable(name = "host", accepts = Type.StringType) public void setHost(String host) { this.host = host; } @Configurable(name = "port", accepts = Type.IntType) public void setPort(int port) { this.port = port; } @Configurable(name = "lifo", accepts = Type.BooleanType) public void setLifo(boolean lifo) { this.lifo = lifo; } @Configurable(name = "maxActive", accepts = Type.IntType) public void setMaxActive(int maxActive) { this.maxActive = maxActive; } @Configurable(name = "maxIdle", accepts = Type.IntType) public void setMaxIdle(int maxIdle) { this.maxIdle = maxIdle; } @Configurable(name = "testWhileIdle", accepts = Type.LongType) public void setMaxWait(long maxWait) { this.maxWait = maxWait; } @Configurable(name = "testWhileIdle", accepts = Type.BooleanType) public void setTestWhileIdle(boolean testWhileIdle) { this.testWhileIdle = testWhileIdle; } @Configurable(name = "timeBetweenEvictionRunsMillis", accepts = Type.LongType) public void setTimeBetweenEvictionRunsMillis(long millis) { this.timeBetweenEvictionRunsMillis = millis; } public String getIdentifier() { return IDENTIFIER; } public void start() throws IOException { log.trace("start()"); connectionPool = new GenericObjectPool(new TConnectionFactory(host, port), maxActive, GenericObjectPool.WHEN_EXHAUSTED_FAIL, maxWait, maxIdle); connectionPool.setLifo(lifo); super.start(); } public boolean exists(String key) throws KeyValueStoreException, IOException { log.trace("exists()"); assertReadable(); TConnection tconn = null; try { tconn = getTConnection(); boolean e = tconn.kv.exists(key); return e; } catch (TTransportException e) { log.error("TTransportException inside exists()", e); throw new IOException(e); } catch (KeyValueStoreIOException e) { log.error("KeyValueStoreIOException inside exists()", e); throw new IOException(e); } catch (com.rubiconproject.oss.kv.gen.KeyValueStoreException e) { log.error("KeyValueStoreException inside exists()", e); throw new KeyValueStoreException(e); } catch (TException e) { log.error("TException inside exists()", e); throw new IOException(e); } catch (Exception e) { log.error("Exception inside exists()", e); throw new IOException(e); } finally { closeTConnection(tconn); } } public Object get(String key) throws KeyValueStoreException, IOException { log.trace("get()"); assertReadable(); return get(key, defaultTranscoder); } public Object get(String key, Transcoder transcoder) throws KeyValueStoreException, IOException { log.trace("get()"); assertReadable(); TConnection tconn = null; try { tconn = getTConnection(); GetResult result = tconn.kv.getValue(key); if (!result.isExists()) return null; else { byte[] data = result.getData(); Object obj = transcoder.decode(data); return obj; } } catch (TTransportException e) { log.error("TTransportException inside get()", e); throw new IOException(e); } catch (KeyValueStoreIOException e) { log.error("KeyValueStoreIOException inside get()", e); throw new IOException(e); } catch (com.rubiconproject.oss.kv.gen.KeyValueStoreException e) { log.error("KeyValueStoreException inside get()", e); throw new KeyValueStoreException(e); } catch (TException e) { log.error("TException inside get()", e); throw new IOException(e); } catch (Exception e) { log.error("Exception inside get()", e); throw new IOException(e); } finally { closeTConnection(tconn); } } public Map<String, Object> getBulk(String... keys) throws KeyValueStoreException, IOException { List<String> coll = Arrays.asList(keys); return getBulk(coll); } public Map<String, Object> getBulk(List<String> keys) throws KeyValueStoreException, IOException { log.trace("getBulk()"); return getBulk(keys, defaultTranscoder); } public Map<String, Object> getBulk(List<String> keys, Transcoder transcoder) throws KeyValueStoreException, IOException { log.trace("getBulk()"); assertReadable(); TConnection tconn = null; try { tconn = getTConnection(); Map<String, GetResult> results = tconn.kv.getBulk(keys); Map<String, Object> retval = new HashMap<String, Object>(results .size()); for (Map.Entry<String, GetResult> entry : results.entrySet()) { byte[] data = entry.getValue().getData(); Object obj = transcoder.decode(data); retval.put(entry.getKey(), obj); } return retval; } catch (TTransportException e) { log.error("TTransportException inside getBulk()", e); throw new IOException(e); } catch (KeyValueStoreIOException e) { log.error("KeyValueStoreIOException inside getBulk()", e); throw new IOException(e); } catch (com.rubiconproject.oss.kv.gen.KeyValueStoreException e) { log.error("KeyValueStoreException inside getBulk()", e); throw new KeyValueStoreException(e); } catch (TException e) { log.error("TException inside getBulk()", e); throw new IOException(e); } catch (Exception e) { log.error("Exception inside getBulk()", e); throw new IOException(e); } finally { closeTConnection(tconn); } } public void set(String key, Object value) throws KeyValueStoreException, IOException { log.trace("set()"); assertWriteable(); set(key, value, defaultTranscoder); } public void set(String key, Object value, Transcoder transcoder) throws KeyValueStoreException, IOException { log.trace("set()"); assertWriteable(); TConnection tconn = null; try { tconn = getTConnection(); byte[] data = transcoder.encode(value); ByteBuffer buff = ByteBuffer.wrap(data); tconn.kv.setValue(key, buff); } catch (TTransportException e) { log.error("TTransportException inside set()", e); throw new IOException(e); } catch (KeyValueStoreIOException e) { log.error("KeyValueStoreIOException inside set()", e); throw new IOException(e); } catch (com.rubiconproject.oss.kv.gen.KeyValueStoreException e) { log.error("KeyValueStoreException inside set()", e); throw new KeyValueStoreException(e); } catch (TException e) { log.error("TException inside set()", e); throw new IOException(e); } catch (Exception e) { log.error("Exception inside set()", e); throw new IOException(e); } finally { closeTConnection(tconn); } } public void delete(String key) throws KeyValueStoreException, IOException { log.trace("delete()"); assertWriteable(); TConnection tconn = null; try { tconn = getTConnection(); tconn.kv.deleteValue(key); } catch (TTransportException e) { log.error("TTransportException inside delete()", e); throw new IOException(e); } catch (KeyValueStoreIOException e) { log.error("KeyValueStoreIOException inside delete()", e); throw new IOException(e); } catch (com.rubiconproject.oss.kv.gen.KeyValueStoreException e) { log.error("KeyValueStoreException inside delete()", e); throw new KeyValueStoreException(e); } catch (TException e) { log.error("TException inside delete()", e); throw new IOException(e); } catch (Exception e) { log.error("Exception inside delete()", e); throw new IOException(e); } finally { closeTConnection(tconn); } } private TConnection getTConnection() throws Exception { log.trace("connect()"); TConnection tc = (TConnection) connectionPool.borrowObject(); return tc; } private void closeTConnection(TConnection tconn) { log.trace("disconnect()"); try { connectionPool.returnObject(tconn); } catch (Exception e) { log.warn("Exception calling transport.close()", e); } } private static class TConnectionFactory extends BasePoolableObjectFactory { private String server; private int port; public TConnectionFactory(String server, int port) { this.server = server; this.port = port; } /** * Create a new object. */ public Object makeObject() throws Exception { TSocket socket = new TSocket(server, port); TFramedTransport framed = new TFramedTransport(socket); TProtocol protocol = new TBinaryProtocol(framed); KeyValueService.Iface kv = new KeyValueService.Client(protocol); framed.open(); return new TConnection(socket, kv); } /** * Uninitialize an instance to be returned to the pool. * * @param obj * the instance to be passivated */ public void passivateObject(Object obj) throws Exception { } } private static class TConnection { public TTransport transport; public KeyValueService.Iface kv; public TConnection(TTransport transport, KeyValueService.Iface kv) { this.transport = transport; this.kv = kv; } } }