package org.n3r.eql.mtcp; import com.codahale.metrics.Gauge; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Slf4jReporter; import com.google.common.cache.*; import org.n3r.eql.config.EqlConfig; import org.n3r.eql.mtcp.utils.Mtcps; import org.n3r.eql.util.S; import org.slf4j.LoggerFactory; import javax.sql.DataSource; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; import static com.google.common.base.Preconditions.checkNotNull; public class MtcpDataSourceHandler implements InvocationHandler { final TenantPropertiesConfigurator tenantPropertiesConfigurator; final EqlConfig eqlConfig; final ScheduledExecutorService destroyScheduler; final MetricRegistry metricsRegistry; final LoadingCache<String/*tenant id*/, DataSourceConfigurator /*tenant ds*/> mtcpCache; public MtcpDataSourceHandler(EqlConfig eqlConfig) { this.eqlConfig = eqlConfig; tenantPropertiesConfigurator = createMtcpTenantPropertiesConfigurator(eqlConfig); mtcpCache = createMtcpCache(eqlConfig); destroyScheduler = Executors.newSingleThreadScheduledExecutor(); destroyScheduler.scheduleWithFixedDelay(new Runnable() { @Override public void run() { mtcpCache.cleanUp(); } }, 600, 600, TimeUnit.SECONDS); metricsRegistry = new MetricRegistry(); metricsRegistry.register(name(MtcpDataSourceHandler.class.getSimpleName(), "cacheCount"), new Gauge<Long>() { @Override public Long getValue() { return mtcpCache.size(); } }); // TODO: Metric Reported should be configurated from outside. // ConsoleReporter reporter = ConsoleReporter.forRegistry(metricsRegistry).build(); // reporter.start(10, TimeUnit.SECONDS); final Slf4jReporter reporter = Slf4jReporter.forRegistry(metricsRegistry) .outputTo(LoggerFactory.getLogger(MtcpDataSourceHandler.class)) .convertRatesTo(TimeUnit.SECONDS) .convertDurationsTo(TimeUnit.MILLISECONDS) .build(); reporter.start(1, TimeUnit.MINUTES); } private LoadingCache<String, DataSourceConfigurator> createMtcpCache(EqlConfig eqlConfig) { String key = "mtcpCacheSpec"; String mtcpCacheSpec = eqlConfig.getStr(key); checkNotNull(mtcpCacheSpec, "%s should not be empty!", key); return CacheBuilder.from(mtcpCacheSpec) .removalListener(new RemovalListener<String, DataSourceConfigurator>() { @Override public void onRemoval(RemovalNotification<String, DataSourceConfigurator> notification) { String tenantId = notification.getKey(); DataSourceConfigurator dataSourceConfigurator = notification.getValue(); dataSourceConfigurator.destory(tenantId, metricsRegistry); } }) .build(new CacheLoader<String, DataSourceConfigurator>() { @Override public DataSourceConfigurator load(String tenantId) throws Exception { return createTenantDataSource(tenantId); } }); } private TenantPropertiesConfigurator createMtcpTenantPropertiesConfigurator(EqlConfig eqlConfig) { String key = "tenantPropertiesConfigurator.spec"; String impl = eqlConfig.getStr(key); checkNotNull(impl, "%s should not be empty!", key); return Mtcps.createObjectBySpec(impl, TenantPropertiesConfigurator.class); } private DataSource getTenantDataSource() { String tenantId = MtcpContext.getTenantId(); checkNotNull(tenantId, "there is no tenant id set in current thread local"); return mtcpCache.getUnchecked(tenantId).getDataSource(); } private DataSourceConfigurator createTenantDataSource(String tenantId) { String key = "dataSourceConfigurator.spec"; String impl = eqlConfig.getStr(key); if (S.isBlank(impl)) impl = "@com.github.bingoohuang.mtcp.impl.DruidDataSourceConfigurator"; DataSourceConfigurator dataSourceConfigurator = Mtcps.createObjectBySpec(impl, DataSourceConfigurator.class); Map<String, String> props = Mtcps.merge(eqlConfig.params(), tenantPropertiesConfigurator.getTenantProperties(tenantId)); dataSourceConfigurator.prepare(tenantId, props, metricsRegistry, destroyScheduler); return dataSourceConfigurator; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(getTenantDataSource(), args); } public DataSource newMtcpDataSource() { ClassLoader cl = getClass().getClassLoader(); return (DataSource) Proxy.newProxyInstance(cl, new Class[]{DataSource.class}, this); } }