package com.wizecommerce.hecuba.datastax;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Cluster.Builder;
import com.datastax.driver.core.HostDistance;
import com.datastax.driver.core.PoolingOptions;
import com.datastax.driver.core.ProtocolOptions.Compression;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SocketOptions;
import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy;
import com.datastax.driver.core.policies.LatencyAwarePolicy;
import com.datastax.driver.core.policies.LoadBalancingPolicy;
import com.datastax.driver.core.policies.RoundRobinPolicy;
import com.datastax.driver.core.policies.TokenAwarePolicy;
import com.wizecommerce.hecuba.datastax.SessionCachingKey.ClusterCachingKey;
/**
* This class is to be used for internal purposes of hecuba client. This <b>singleton</b> implementation will
* cache cluster and sessions according to datastax driver rules. i.e Use one Cluster instance per (physical)
* cluster (per application lifetime). Use at most one Session per keyspace
*
* @author anschauhan
*
*/
@ThreadSafe
public class DataStaxBasedSessionObjectFactory {
private static final Logger logger = LoggerFactory.getLogger(DataStaxBasedSessionObjectFactory.class);
final private Map<ClusterCachingKey, Cluster> clusterCache = new HashMap<>();
final private Map<SessionCachingKey, Session> sessionCache = new HashMap<>();
final private Map<SessionCachingKey, AtomicInteger> usersPerSession = new HashMap<>();
// Initializing egarly since its very light weight, until clusters and session are built. Guarantees
// thread safety.
private static final DataStaxBasedSessionObjectFactory instance = new DataStaxBasedSessionObjectFactory();
private DataStaxBasedSessionObjectFactory() {
}
public static DataStaxBasedSessionObjectFactory getInstance() {
return instance;
}
/**
* Call returnSession() everytime getSession() is called.
*
* @param key
* @return
*/
public Session getSession(SessionCachingKey key) {
Session session = sessionCache.get(key);
if (session == null) {
synchronized (sessionCache) {
// Double check locking
if (session == null) {
Cluster cluster = getCluster(key.getClusterKey());
session = cluster.connect(key.getKeySpace());
sessionCache.put(key, session);
usersPerSession.put(key, new AtomicInteger());
logger.info("New session created with properties: "+key);
}
}
}
usersPerSession.get(key).incrementAndGet();
return session;
}
public void returnSession(SessionCachingKey key) {
int count = usersPerSession.get(key).decrementAndGet();
if (count <= 0) {
Session session = sessionCache.remove(key);
//Synchronize so we don't close already closed session.
synchronized (usersPerSession) {
if (session != null) {
session.close();
}
}
}
}
private Cluster getCluster(ClusterCachingKey key) {
// No need of synchronizing here as getSession is already synchronized.
Cluster cluster = clusterCache.get(key);
if (cluster == null) {
synchronized (clusterCache) {
if (cluster == null) {
Map<String, Object> properties = key.getProperties();
LoadBalancingPolicy loadBalancingPolicy;
Object property = properties.get("dataCenter");
if (property != null) {
loadBalancingPolicy = new DCAwareRoundRobinPolicy((String) property);
} else {
loadBalancingPolicy = new RoundRobinPolicy();
}
loadBalancingPolicy = new TokenAwarePolicy(loadBalancingPolicy);
loadBalancingPolicy = LatencyAwarePolicy.builder(loadBalancingPolicy).build();
Builder builder = Cluster.builder().addContactPoints(key.getLocationUrls())
.withLoadBalancingPolicy(loadBalancingPolicy);
property = properties.get("port");
if (property != null) {
builder.withPort((Integer) property);
}
property = properties.get("username");
if (property != null) {
Object pass = properties.get("password");
if (pass != null) {
builder.withCredentials((String) property, (String) pass);
}
}
property = properties.get("compressionEnabled");
if (property != null && (Boolean) property) {
builder.withCompression(Compression.LZ4);
}
SocketOptions socketOptions = null;
property = properties.get("readTimeout");
if (property != null) {
socketOptions = new SocketOptions();
socketOptions.setReadTimeoutMillis((Integer) property);
}
property = properties.get("connectTimeout");
if (property != null) {
if (socketOptions == null) {
socketOptions = new SocketOptions();
}
socketOptions.setConnectTimeoutMillis((Integer) property);
}
if (socketOptions != null) {
builder.withSocketOptions(socketOptions);
}
PoolingOptions poolingOptions = null;
property = properties.get("maxConnectionsPerHost");
if (property != null) {
poolingOptions = new PoolingOptions();
Integer maxConnectionsPerHost = (Integer) property;
poolingOptions.setMaxConnectionsPerHost(HostDistance.REMOTE, maxConnectionsPerHost);
poolingOptions.setMaxConnectionsPerHost(HostDistance.LOCAL, maxConnectionsPerHost);
}
if (poolingOptions != null) {
builder.withPoolingOptions(poolingOptions);
}
cluster = builder.build();
clusterCache.put(key, cluster);
logger.info("New cluster created with properties: "+key);
}
}
}
return cluster;
}
}