package com.netflix.evcache.pool;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.config.ConfigurationManager;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicStringProperty;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.DiscoveryManager;
import com.netflix.evcache.EVCacheImpl;
import com.netflix.evcache.EVCacheInMemoryCache;
import com.netflix.evcache.connection.DefaultFactoryProvider;
import com.netflix.evcache.connection.IConnectionFactoryProvider;
import com.netflix.evcache.event.EVCacheEventListener;
import com.netflix.evcache.util.EVCacheConfig;
import net.spy.memcached.transcoders.Transcoder;
/**
* A manager that holds Pools for each EVCache app. When this class is
* initialized all the EVCache apps defined in the property evcache.appsToInit
* will be initialized and added to the pool. If a service knows all the EVCache
* app it uses, then it can define this property and pass a list of EVCache apps
* that needs to be initialized.
*
* An EVCache app can also be initialized by Injecting
* <code>EVCacheClientPoolManager</code> and calling <code>
* initEVCache(<app name>)
* </code>
*
* This typically should be done in the client libraries that need to initialize
* an EVCache app. For Example VHSViewingHistoryLibrary in its initLibrary
* initializes EVCACHE_VH by calling
*
* <pre>
* {@literal @}Inject
* public VHSViewingHistoryLibrary(EVCacheClientPoolManager instance,...) {
* ....
* instance.initEVCache("EVCACHE_VH");
* ...
* }
* </pre>
*
* @author smadappa
*
*/
@SuppressWarnings("deprecation")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings({ "PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS", "DM_CONVERT_CASE",
"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" })
@Singleton
public class EVCacheClientPoolManager {
private static final Logger log = LoggerFactory.getLogger(EVCacheClientPoolManager.class);
private static final DynamicIntProperty defaultReadTimeout = EVCacheConfig.getInstance().getDynamicIntProperty("default.read.timeout", 20);
private final DynamicIntProperty defaultRefreshInterval = EVCacheConfig.getInstance().getDynamicIntProperty("EVCacheClientPoolManager.refresh.interval", 60);
private static final DynamicStringProperty logEnabledApps = EVCacheConfig.getInstance().getDynamicStringProperty("EVCacheClientPoolManager.log.apps", "*");
@Deprecated
private volatile static EVCacheClientPoolManager instance;
private final Map<String, EVCacheClientPool> poolMap = new ConcurrentHashMap<String, EVCacheClientPool>();
private final Map<EVCacheClientPool, ScheduledFuture<?>> scheduledTaskMap = new HashMap<EVCacheClientPool, ScheduledFuture<?>>();
private final ScheduledThreadPoolExecutor _scheduler;
private final ThreadPoolExecutor refreshAsnycPool;
private final DiscoveryClient discoveryClient;
private final ApplicationInfoManager applicationInfoManager;
private final List<EVCacheEventListener> evcacheEventListenerList;
private final Provider<IConnectionFactoryProvider> connectionFactoryprovider;
@Inject
public EVCacheClientPoolManager(ApplicationInfoManager applicationInfoManager, DiscoveryClient discoveryClient, Provider<IConnectionFactoryProvider> connectionFactoryprovider) {
instance = this;
this.applicationInfoManager = applicationInfoManager;
this.discoveryClient = discoveryClient;
this.connectionFactoryprovider = connectionFactoryprovider;
this.evcacheEventListenerList = new ArrayList<EVCacheEventListener>();
final int poolSize = EVCacheConfig.getInstance().getDynamicIntProperty("default.refresher.poolsize", 1).get();
final ThreadFactory asyncFactory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat( "EVCacheClientPool-AsyncPoolRefresher-%d").build();
this.refreshAsnycPool = new ThreadPoolExecutor(1,1,30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10), asyncFactory, new ThreadPoolExecutor.DiscardPolicy());
final ThreadFactory factory = new ThreadFactoryBuilder().setDaemon(true).setNameFormat("EVCacheClientPoolManager-refresher-%d").build();
_scheduler = new ScheduledThreadPoolExecutor(poolSize, factory);
defaultRefreshInterval.addCallback(new Runnable() {
public void run() {
refreshScheduler();
}
});
initAtStartup();
}
public IConnectionFactoryProvider getConnectionFactoryProvider() {
return connectionFactoryprovider.get();
}
public void addEVCacheEventListener(EVCacheEventListener listener) {
this.evcacheEventListenerList.add(listener);
}
public void removeEVCacheEventListener(EVCacheEventListener listener) {
this.evcacheEventListenerList.remove(listener);
}
public List<EVCacheEventListener> getEVCacheEventListeners() {
return this.evcacheEventListenerList;
}
private void refreshScheduler() {
for (Iterator<EVCacheClientPool> itr = scheduledTaskMap.keySet().iterator(); itr.hasNext();) {
final EVCacheClientPool pool = itr.next();
final ScheduledFuture<?> task = scheduledTaskMap.get(pool);
itr.remove();
task.cancel(true);
scheduleRefresh(pool);
}
}
/**
* @deprecated. Please use DependencyInjection (@Inject) to obtain
* {@link EVCacheClientPoolManager}. The use of this can result in
* unintended behavior where you will not be able to talk to evcache
* instances.
*/
@Deprecated
public static EVCacheClientPoolManager getInstance() {
if (instance == null) {
new EVCacheClientPoolManager(null, null, new DefaultFactoryProvider());
if (!EVCacheConfig.getInstance().getDynamicBooleanProperty("evcache.use.simple.node.list.provider", false).get()) {
log.warn("Please make sure EVCacheClientPoolManager is injected first. This is not the appropriate way to init EVCacheClientPoolManager."
+ " If you are using simple node list provider please set evcache.use.simple.node.list.provider property to true.", new Exception());
}
}
return instance;
}
public ApplicationInfoManager getApplicationInfoManager() {
return this.applicationInfoManager;
}
public DiscoveryClient getDiscoveryClient() {
DiscoveryClient client = discoveryClient;
if (client == null) client = DiscoveryManager.getInstance().getDiscoveryClient();
return client;
}
/**
* TODO Move to @PostConstruct, so that a non-static EVCacheConfig can be injected by DI, so that Configuration
* subsystem can be properly setup via the DI system.
*/
public void initAtStartup() {
//final String appsToInit = ConfigurationManager.getConfigInstance().getString("evcache.appsToInit");
final String appsToInit = EVCacheConfig.getInstance().getDynamicStringProperty("evcache.appsToInit", "").get();
if (appsToInit != null && appsToInit.length() > 0) {
final StringTokenizer apps = new StringTokenizer(appsToInit, ",");
while (apps.hasMoreTokens()) {
final String app = getAppName(apps.nextToken());
if (log.isDebugEnabled()) log.debug("Initializing EVCache - " + app);
initEVCache(app);
}
}
}
/**
* Will init the given EVCache app call. If one is already initialized for
* the given app method returns without doing anything.
*
* @param app
* - name of the evcache app
*/
public final synchronized void initEVCache(String app) {
if (app == null || (app = app.trim()).length() == 0) throw new IllegalArgumentException(
"param app name null or space");
final String APP = getAppName(app);
if (poolMap.containsKey(APP)) return;
final EVCacheNodeList provider;
if (EVCacheConfig.getInstance().getChainedBooleanProperty(APP + ".use.simple.node.list.provider", "evcache.use.simple.node.list.provider", false).get()) {
provider = new SimpleNodeListProvider(APP + "-NODES");
} else {
provider = new DiscoveryNodeListProvider(applicationInfoManager, discoveryClient, APP);
}
final EVCacheClientPool pool = new EVCacheClientPool(APP, provider, refreshAsnycPool, this);
scheduleRefresh(pool);
poolMap.put(APP, pool);
}
private void scheduleRefresh(EVCacheClientPool pool) {
final ScheduledFuture<?> task = _scheduler.scheduleWithFixedDelay(pool, 30, defaultRefreshInterval.get(),
TimeUnit.SECONDS);
scheduledTaskMap.put(pool, task);
}
/**
* Given the appName get the EVCacheClientPool. If the app is already
* created then will return the existing instance. If not one will be
* created and returned.
*
* @param _app
* - name of the evcache app
* @return the Pool for the give app.
* @throws IOException
*/
public EVCacheClientPool getEVCacheClientPool(String _app) {
final String app = getAppName(_app);
final EVCacheClientPool evcacheClientPool = poolMap.get(app);
if (evcacheClientPool != null) return evcacheClientPool;
initEVCache(app);
return poolMap.get(app);
}
public Map<String, EVCacheClientPool> getAllEVCacheClientPool() {
return new HashMap<String, EVCacheClientPool>(poolMap);
}
@PreDestroy
public void shutdown() {
_scheduler.shutdown();
for (EVCacheClientPool pool : poolMap.values()) {
pool.shutdown();
}
}
public boolean shouldLog(String appName) {
if ("*".equals(logEnabledApps.get())) return true;
if (logEnabledApps.get().indexOf(appName) != -1) return true;
return false;
}
public static DynamicIntProperty getDefaultReadTimeout() {
return defaultReadTimeout;
}
public DynamicIntProperty getDefaultRefreshInterval() {
return defaultRefreshInterval;
}
private String getAppName(String _app) {
_app = _app.toUpperCase();
final String app = EVCacheConfig.getInstance().getDynamicStringProperty("EVCacheClientPoolManager." + _app + ".alias", _app).get().toUpperCase();
if (log.isDebugEnabled()) log.debug("Original App Name : " + _app + "; Alias App Name : " + app);
return app;
}
private WriteLock writeLock = new ReentrantReadWriteLock().writeLock();
private final Map<String, EVCacheInMemoryCache<?>> inMemoryMap = new ConcurrentHashMap<String, EVCacheInMemoryCache<?>>();
@SuppressWarnings("unchecked")
public <T> EVCacheInMemoryCache<T> createInMemoryCache(String appName, Transcoder<T> tc, EVCacheImpl impl) {
EVCacheInMemoryCache<T> cache = (EVCacheInMemoryCache<T>) inMemoryMap.get(appName);
if(cache == null) {
writeLock.lock();
if((cache = getInMemoryCache(appName)) == null) {
cache = new EVCacheInMemoryCache<T>(appName, tc, impl);
inMemoryMap.put(appName, cache);
}
writeLock.unlock();
}
return cache;
}
@SuppressWarnings("unchecked")
public <T> EVCacheInMemoryCache<T> getInMemoryCache(String appName) {
return (EVCacheInMemoryCache<T>) inMemoryMap.get(appName);
}
}