package com.brightgenerous.injection.jdbc.guice;
import static com.brightgenerous.commons.ObjectUtils.*;
import static com.brightgenerous.commons.StringUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.aopalliance.intercept.MethodInterceptor;
import com.brightgenerous.commons.EqualsUtils;
import com.brightgenerous.commons.HashCodeUtils;
import com.brightgenerous.commons.ToStringUtils;
import com.brightgenerous.injection.Filter;
import com.brightgenerous.injection.ImplResolver;
import com.brightgenerous.injection.jdbc.SqlSession;
import com.brightgenerous.injection.jdbc.SqlSessionManager;
import com.brightgenerous.injection.jdbc.ThreadLocalSqlSession;
import com.brightgenerous.injection.jdbc.Transactional;
import com.brightgenerous.injection.jdbc.TransactionalMethodInterceptor;
import com.brightgenerous.resolver.ResolverUtils;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
import com.google.inject.matcher.Matchers;
public abstract class InjectorFactory implements com.brightgenerous.datasource.InjectorFactory,
Serializable {
private static final long serialVersionUID = -6126615644662058251L;
private volatile InjectorConfig config;
private transient volatile Injector injector;
private transient volatile Injector injectorRollback;
protected InjectorFactory() {
}
protected InjectorConfig getConfig() {
if (config == null) {
synchronized (this) {
if (config == null) {
config = createConfig();
if (config == null) {
throw new IllegalStateException();
}
}
}
}
return config;
}
protected abstract InjectorConfig createConfig();
@Override
public Injector getInjector() {
return getInjector(false);
}
@Override
public Injector getInjector(boolean rollbackOnly) {
if (rollbackOnly) {
return injectorRollback();
}
return injector();
}
protected Injector injector() {
if (injector == null) {
synchronized (this) {
if (injector == null) {
injector = createInjector(getConfig(), false);
}
}
}
return injector;
}
protected Injector injectorRollback() {
if (injectorRollback == null) {
synchronized (this) {
if (injectorRollback == null) {
injectorRollback = createInjector(getConfig(), true);
}
}
}
return injectorRollback;
}
@Override
public void initialize() {
onInitialize();
}
protected void onInitialize() {
}
@Override
public void verify() {
SqlSession sqlSession = getInjector().getInstance(SqlSession.class);
if (sqlSession != null) {
try (SqlSession ss = sqlSession) {
if (!ss.isStarted()) {
ss.start(null);
}
Connection connection = ss.getConnection();
if (connection != null) {
try (Connection conn = connection) {
DatabaseMetaData dmd = conn.getMetaData();
if (dmd != null) {
if (!compareBeanToTable(dmd, getBeanClasses(getConfig()))) {
throw new IllegalStateException("compareBeanToTable returns false.");
}
}
ss.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
}
@SuppressWarnings("unused")
protected boolean compareBeanToTable(DatabaseMetaData dmd, Set<Class<?>> beanClasses)
throws SQLException {
return true;
}
@Override
public void destroy() {
onDestroy();
config = null;
injector = null;
injectorRollback = null;
}
protected void onDestroy() {
}
protected Injector createInjector(final InjectorConfig config, final boolean rollbackOnly) {
return Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
{
bind(ThreadLocalSqlSession.class).in(Scopes.SINGLETON);
bind(SqlSession.class).to(ThreadLocalSqlSession.class);
bind(SqlSessionManager.class).to(ThreadLocalSqlSession.class);
bind(DataSource.class).toProvider(new Provider<DataSource>() {
@Override
public DataSource get() {
try {
return (DataSource) new InitialContext().lookup(config
.getDataSourceName());
} catch (NamingException e) {
throw new RuntimeException(e);
}
}
}).in(Scopes.SINGLETON);
}
{
Set<Class<?>> mapperClazzs = getMapperClasses(config);
if (isNotNoSize(mapperClazzs)) {
ImplResolver resolver = config.getMapperImplResolver();
if (resolver == null) {
for (Class<?> mapperClazz : mapperClazzs) {
bind(mapperClazz);
}
} else {
Map<Class<?>, Set<Class<?>>> implClazzMap = getImplClassMap(resolver,
mapperClazzs);
for (Entry<Class<?>, Set<Class<?>>> e : implClazzMap.entrySet()) {
@SuppressWarnings("rawtypes")
Class implClazz = e.getKey();
Set<Class<?>> superClazzs = e.getValue();
for (Class<?> superClazz : superClazzs) {
bind(superClazz).to(implClazz);
}
}
}
}
}
{
final Set<Class<?>> transactionClazzs = getTransactionClasses(config);
if (isNotNoSize(transactionClazzs)) {
MethodInterceptor selectInterceptor;
MethodInterceptor updateInterceptor;
if (rollbackOnly) {
selectInterceptor = new TransactionalMethodInterceptor(true) {
@Transactional(rollbackOnly = true)
@Override
protected void setting() {
}
};
updateInterceptor = new TransactionalMethodInterceptor() {
@Transactional(rollbackOnly = true)
@Override
protected void setting() {
}
};
} else {
selectInterceptor = new TransactionalMethodInterceptor(true);
updateInterceptor = new TransactionalMethodInterceptor();
}
binder().requestInjection(selectInterceptor);
binder().requestInjection(updateInterceptor);
Matcher<Class<?>> classMatcher = new AbstractMatcher<Class<?>>() {
@Override
public boolean matches(Class<?> arg0) {
if (arg0.isAnnotation() || arg0.isEnum()) {
return false;
}
return transactionClazzs.contains(arg0);
}
};
Matcher<Method> methodMatcher = new AbstractMatcher<Method>() {
private final Filter<Method> filter = config
.getTransactionMethodFilter();
@Override
public boolean matches(Method arg0) {
return (filter == null) || filter.filter(arg0);
}
};
binder().bindInterceptor(classMatcher, Matchers.not(methodMatcher),
selectInterceptor);
binder().bindInterceptor(classMatcher, methodMatcher, updateInterceptor);
for (Class<?> transactionClazz : transactionClazzs) {
bind(transactionClazz).in(Scopes.SINGLETON);
}
}
}
}
});
}
private static Set<Class<?>> getTransactionClasses(InjectorConfig config) {
Set<Class<?>> ret = new HashSet<>();
{
Set<String> packages = getPackageNames(config.getTransactionPackages());
if (isNotNoSize(packages)) {
final Filter<Class<?>> filter = config.getTransactionClassFilter();
for (String pkg : packages) {
final String regex = getPackageRegex(pkg);
ret.addAll(ResolverUtils.find(new com.brightgenerous.resolver.Matcher() {
@Override
public boolean matches(Class<?> arg0) {
if (arg0.isAnnotation() || arg0.isEnum()) {
return false;
}
if ((filter != null) && !filter.filter(arg0)) {
return false;
}
return arg0.getName().matches(regex);
}
}, pkg));
}
}
}
return ret;
}
private static Set<Class<?>> getMapperClasses(InjectorConfig config) {
Set<Class<?>> ret = new HashSet<>();
{
Set<String> packages = getPackageNames(config.getMapperPackages());
if (isNotNoSize(packages)) {
final Filter<Class<?>> filter = config.getMapperClassFilter();
for (String pkg : packages) {
final String regex = getPackageRegex(pkg);
ret.addAll(ResolverUtils.find(new com.brightgenerous.resolver.Matcher() {
@Override
public boolean matches(Class<?> arg0) {
if (arg0.isAnnotation() || arg0.isEnum()) {
return false;
}
if ((filter != null) && !filter.filter(arg0)) {
return false;
}
return arg0.getName().matches(regex);
}
}, pkg));
}
}
}
return ret;
}
private static Set<Class<?>> getBeanClasses(InjectorConfig config) {
Set<Class<?>> ret = new HashSet<>();
{
Set<String> packages = getPackageNames(config.getBeanPackages());
if (isNotNoSize(packages)) {
final Filter<Class<?>> filter = config.getBeanClassFilter();
for (String pkg : packages) {
final String regex = getPackageRegex(pkg);
ret.addAll(ResolverUtils.find(new com.brightgenerous.resolver.Matcher() {
@Override
public boolean matches(Class<?> arg0) {
if (arg0.isAnnotation() || arg0.isEnum() || arg0.isAnonymousClass()
|| !Modifier.isPublic(arg0.getModifiers())) {
return false;
}
if ((filter != null) && !filter.filter(arg0)) {
return false;
}
return arg0.getName().matches(regex);
}
}, pkg));
}
}
}
return ret;
}
private static Map<Class<?>, Set<Class<?>>> getImplClassMap(ImplResolver resolver,
Set<Class<?>> superClasses) {
if (resolver == null) {
return null;
}
Map<Class<?>, Set<Class<?>>> ret = new HashMap<>();
if (isNotNoSize(superClasses)) {
for (Class<?> superClass : superClasses) {
Class<?> implClass = resolver.getImplClass(superClass);
if (implClass == null) {
continue;
}
Set<Class<?>> ics = ret.get(implClass);
if (ics == null) {
ics = new HashSet<>();
ret.put(implClass, ics);
}
ics.add(superClass);
}
}
return ret;
}
private static Set<String> getPackageNames(Class<?>... packages) {
Set<String> ret = new HashSet<>();
if (isNotNoSize(packages)) {
for (Class<?> pkg : packages) {
Package p = pkg.getPackage();
if (p == null) {
throw new IllegalStateException(String.format("Can not use Package %s", pkg));
}
String pName = p.getName();
if (pName == null) {
throw new IllegalStateException(String.format("Can not use Package %s", pkg));
}
ret.add(pName);
}
}
return ret;
}
private static String getPackageRegex(String pkg) {
if (isEmpty(pkg)) {
return null;
}
return getPackageRegex(Arrays.asList(pkg));
}
private static String getPackageRegex(Collection<String> packages) {
if (isNoSize(packages)) {
return null;
}
final String regex;
{
StringBuilder sb = new StringBuilder();
sb.append("^(");
boolean first = true;
for (String pkg : packages) {
if (isEmpty(pkg)) {
continue;
}
if (first) {
first = false;
} else {
sb.append("|");
}
sb.append(pkg).append(".[^.]*");
}
sb.append(")$");
regex = sb.toString();
}
return regex;
}
@Override
public int hashCode() {
if (HashCodeUtils.resolved()) {
return HashCodeUtils.hashCodeAlt(null, this);
}
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (EqualsUtils.resolved()) {
return EqualsUtils.equalsAlt(null, this, obj);
}
return super.equals(obj);
}
@Override
public String toString() {
if (ToStringUtils.resolved()) {
return ToStringUtils.toStringAlt(this);
}
return super.toString();
}
}