package com.brightgenerous.injection.mybatis.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 org.aopalliance.intercept.MethodInterceptor; import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.SqlSessionManager; import org.apache.ibatis.session.TransactionIsolationLevel; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.apache.ibatis.type.TypeHandler; import org.mybatis.guice.MyBatisModule; import org.mybatis.guice.datasource.dbcp.BasicDataSourceProvider; import org.mybatis.guice.transactional.Transactional; 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.mybatis.TransactionalMethodInterceptor; import com.brightgenerous.resolver.Matcher; import com.brightgenerous.resolver.ResolverUtils; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Scopes; import com.google.inject.matcher.AbstractMatcher; import com.google.inject.name.Names; public abstract class InjectorFactory implements com.brightgenerous.datasource.InjectorFactory, Serializable { private static final long serialVersionUID = -1832713081625601163L; 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() { SqlSessionManager sqlSessionManager = getInjector().getInstance(SqlSessionManager.class); if (sqlSessionManager != null) { try (SqlSessionManager ssm = sqlSessionManager) { if (!ssm.isManagedSessionStarted()) { ssm.startManagedSession(ExecutorType.SIMPLE, (TransactionIsolationLevel) null); } Connection connection = ssm.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."); } } ssm.rollback(true); } 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 MyBatisModule() { @Override protected void initialize() { if (config.getDataSourceProviderType() != null) { bindDataSourceProviderType(config.getDataSourceProviderType()); } else { bindDataSourceProviderType(BasicDataSourceProvider.class); } if (config.getTransactionFactoryType() != null) { bindTransactionFactoryType(config.getTransactionFactoryType()); } else { bindTransactionFactoryType(JdbcTransactionFactory.class); } { // properties if ((config.getDbProperties() != null) && !config.getDbProperties().isEmpty()) { Names.bindProperties(binder(), config.getDbProperties()); } if (isNotEmpty(config.getEnvironmentId())) { environmentId(config.getEnvironmentId()); } if (config.getCacheEnabled() != null) { useCacheEnabled(config.getCacheEnabled().booleanValue()); } if (config.getLazyLoadingEnabled() != null) { lazyLoadingEnabled(config.getLazyLoadingEnabled().booleanValue()); } if (config.getAggressiveLazyLoading() != null) { aggressiveLazyLoading(config.getAggressiveLazyLoading().booleanValue()); } if (config.getMultipleResultSetsEnabled() != null) { multipleResultSetsEnabled(config.getMultipleResultSetsEnabled() .booleanValue()); } if (config.getUseColumnLabel() != null) { useColumnLabel(config.getUseColumnLabel().booleanValue()); } if (config.getUseGeneratedKeys() != null) { useGeneratedKeys(config.getUseGeneratedKeys().booleanValue()); } if (config.getAutoMappingBehavior() != null) { autoMappingBehavior(config.getAutoMappingBehavior()); } if (config.getDefaultExecutorType() != null) { executorType(config.getDefaultExecutorType()); } if (config.getMapUnderscoreToCamelCase() != null) { mapUnderscoreToCamelCase(config.getMapUnderscoreToCamelCase() .booleanValue()); } if (config.getLocalCacheScope() != null) { localCacheScope(config.getLocalCacheScope()); } if (config.getFailFast() != null) { failFast(config.getFailFast().booleanValue()); } } { Set<Class<?>> beanClazzs = getBeanClasses(config); if (isNotNoSize(beanClazzs)) { addSimpleAliases(beanClazzs); } } { Set<Class<?>> mapperClazzs = getMapperClasses(config); if (isNotNoSize(mapperClazzs)) { ImplResolver resolver = config.getMapperImplResolver(); if (resolver == null) { addMapperClasses(mapperClazzs); } else { Map<Class<?>, Set<Class<?>>> implClazzMap = getImplClassMap(resolver, mapperClazzs); addMapperClasses(implClazzMap.keySet()); 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); } } } } } { Collection<Class<? extends TypeHandler<?>>> typeHandlers = config .getTypeHandlerClasses(); if (isNotNoSize(typeHandlers)) { addTypeHandlersClasses(typeHandlers); } } } }, new AbstractModule() { @Override protected void configure() { final Set<Class<?>> transactionClazzs = getTransactionClasses(config); if (isNotNoSize(transactionClazzs)) { MethodInterceptor updateInterceptor; if (rollbackOnly) { updateInterceptor = new TransactionalMethodInterceptor() { @Transactional(rollbackOnly = true) @Override protected void setting() { } }; } else { updateInterceptor = new TransactionalMethodInterceptor(); } binder().requestInjection(updateInterceptor); binder().bindInterceptor(new AbstractMatcher<Class<?>>() { @Override public boolean matches(Class<?> arg0) { if (arg0.isAnnotation() || arg0.isEnum()) { return false; } return transactionClazzs.contains(arg0); } }, new AbstractMatcher<Method>() { private final Filter<Method> filter = config.getTransactionMethodFilter(); @Override public boolean matches(Method arg0) { return (filter == null) || filter.filter(arg0); } }, 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 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 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 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(); } }