package org.malagu.multitenant.service; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.boot.model.naming.ImplicitNamingStrategy; import org.hibernate.boot.model.naming.PhysicalNamingStrategy; import org.malagu.multitenant.Constants; import org.malagu.multitenant.HibernateDefaultDdlAutoProvider; import org.malagu.multitenant.domain.Organization; import org.malagu.multitenant.listener.EntityManagerFactoryCreateListener; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; import org.springframework.boot.jdbc.SchemaManagementProvider; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder.Builder; import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.weaving.LoadTimeWeaverAware; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.ResourceLoader; import org.springframework.instrument.classloading.LoadTimeWeaver; import org.springframework.jdbc.datasource.SingleConnectionDataSource; import org.springframework.jndi.JndiLocatorDelegate; import org.springframework.orm.jpa.JpaVendorAdapter; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.stereotype.Service; import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * @author Kevin Yang (mailto:[email protected]) * @since 2017年11月24日 */ @Service public class EntityManagerFactoryServiceImpl implements EntityManagerFactoryService, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, ResourceLoaderAware, LoadTimeWeaverAware, InitializingBean { private Map<String, EntityManagerFactory> emfMap = new ConcurrentHashMap<String, EntityManagerFactory>(); @Autowired private DataSourceService dataSourceService; @Autowired private ScriptService scriptService; @Value("${bdf3.multitenant.dataScript:}") private String dataScript; @Autowired private EntityManagerFactory emf; @Autowired(required = false) private JtaTransactionManager jtaTransactionManager; private LoadTimeWeaver loadTimeWeaver; private ResourceLoader resourceLoader; private ClassLoader classLoader; private String beanName; private final HibernateDefaultDdlAutoProvider defaultDdlAutoProvider; private final PhysicalNamingStrategy physicalNamingStrategy; private final ImplicitNamingStrategy implicitNamingStrategy; private final List<HibernatePropertiesCustomizer> hibernatePropertiesCustomizers; @Autowired(required = false) private List<EntityManagerFactoryCreateListener> listeners; private static final Log logger = LogFactory .getLog(EntityManagerFactoryServiceImpl.class); private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform"; /** * {@code NoJtaPlatform} implementations for various Hibernate versions. */ private static final String[] NO_JTA_PLATFORM_CLASSES = { "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform", "org.hibernate.service.jta.platform.internal.NoJtaPlatform" }; /** * {@code WebSphereExtendedJtaPlatform} implementations for various Hibernate * versions. */ private static final String[] WEBSPHERE_JTA_PLATFORM_CLASSES = { "org.hibernate.engine.transaction.jta.platform.internal.WebSphereExtendedJtaPlatform", "org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform", }; @Autowired private JpaProperties properties; private BeanFactory beanFactory; @Autowired(required = false) private PersistenceUnitManager persistenceUnitManager; @Value("${bdf3.multitenant.packagesToScan:" + "com.bstek.bdf3.security.orm," + "com.bstek.bdf3.notice.domain," + "com.bstek.bdf3.dictionary.domain," + "com.bstek.bdf3.log.model," + "com.bstek.bdf3.importer.model," + "com.bstek.bdf3.profile.domain}") private String packagesToScan; @Value("${bdf3.multitenant.customPackagesToScan:}") private String customPackagesToScan; public EntityManagerFactoryServiceImpl( ObjectProvider<List<SchemaManagementProvider>> providers, ObjectProvider<PhysicalNamingStrategy> physicalNamingStrategy, ObjectProvider<ImplicitNamingStrategy> implicitNamingStrategy, ObjectProvider<List<HibernatePropertiesCustomizer>> hibernatePropertiesCustomizers) { this.defaultDdlAutoProvider = new HibernateDefaultDdlAutoProvider( providers.getIfAvailable(Collections::emptyList)); this.physicalNamingStrategy = physicalNamingStrategy.getIfAvailable(); this.implicitNamingStrategy = implicitNamingStrategy.getIfAvailable(); this.hibernatePropertiesCustomizers = hibernatePropertiesCustomizers .getIfAvailable(() -> Collections.emptyList()); } protected AbstractJpaVendorAdapter createJpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } private String[] mergePackagesToScan() { String[] packages = null; if (StringUtils.hasText(packagesToScan) && StringUtils.hasText(customPackagesToScan)) { packages = (packagesToScan + "," + customPackagesToScan).split(","); } else if (StringUtils.hasText(packagesToScan)) { packages = packagesToScan.split(","); } else if (StringUtils.hasText(customPackagesToScan)) { packages = customPackagesToScan.split(","); } return packages; } public JpaVendorAdapter getJpaVendorAdapter(DataSource dataSource) { AbstractJpaVendorAdapter adapter = createJpaVendorAdapter(); adapter.setShowSql(this.properties.isShowSql()); adapter.setDatabase(this.properties.determineDatabase(dataSource)); adapter.setDatabasePlatform(this.properties.getDatabasePlatform()); adapter.setGenerateDdl(this.properties.isGenerateDdl()); return adapter; } public EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(DataSource dataSource) { JpaVendorAdapter jpaVendorAdapter = getJpaVendorAdapter(dataSource); EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( jpaVendorAdapter, properties.getProperties(), this.persistenceUnitManager); return builder; } @Override public EntityManagerFactory getEntityManagerFactory(Organization organization) { return emfMap.get(organization.getId()); } @Override public EntityManagerFactory createEntityManagerFactory(Organization organization) { DataSource dataSource = dataSourceService.getOrCreateDataSource(organization); Map<String, Object> vendorProperties = getVendorProperties(dataSource); customizeVendorProperties(vendorProperties); LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = getEntityManagerFactoryBuilder(dataSource).dataSource(dataSource).packages(mergePackagesToScan()) .properties(vendorProperties).jta(isJta()).build(); entityManagerFactoryBean.setBeanClassLoader(classLoader); entityManagerFactoryBean.setBeanFactory(beanFactory); entityManagerFactoryBean.setBeanName(beanName); entityManagerFactoryBean.setLoadTimeWeaver(loadTimeWeaver); entityManagerFactoryBean.setResourceLoader(resourceLoader); entityManagerFactoryBean.setPersistenceUnitName(organization.getId()); entityManagerFactoryBean.afterPropertiesSet(); scriptService.runScripts(organization.getId(), dataSource, dataScript, "multitenant-data"); return entityManagerFactoryBean.getObject(); } protected Map<String, Object> getVendorProperties(DataSource dataSource) { String defaultDdlMode = this.defaultDdlAutoProvider .getDefaultDdlAuto(dataSource); Map<String, Object> vendorProperties = new LinkedHashMap<String, Object>(); vendorProperties.putAll(this.properties.getHibernateProperties(new HibernateSettings().ddlAuto(defaultDdlMode) .implicitNamingStrategy(this.implicitNamingStrategy) .physicalNamingStrategy(this.physicalNamingStrategy) .hibernatePropertiesCustomizers( this.hibernatePropertiesCustomizers))); return vendorProperties; } protected void customizeVendorProperties(Map<String, Object> vendorProperties) { if (!vendorProperties.containsKey(JTA_PLATFORM)) { configureJtaPlatform(vendorProperties); } } private void configureJtaPlatform(Map<String, Object> vendorProperties) throws LinkageError { JtaTransactionManager jtaTransactionManager = getJtaTransactionManager(); if (jtaTransactionManager != null) { if (runningOnWebSphere()) { configureWebSphereTransactionPlatform(vendorProperties); } else { configureSpringJtaPlatform(vendorProperties, jtaTransactionManager); } } else { vendorProperties.put(JTA_PLATFORM, getNoJtaPlatformManager()); } } private boolean runningOnWebSphere() { return ClassUtils.isPresent( "com.ibm.websphere.jtaextensions." + "ExtendedJTATransaction", getClass().getClassLoader()); } private void configureWebSphereTransactionPlatform( Map<String, Object> vendorProperties) { vendorProperties.put(JTA_PLATFORM, getWebSphereJtaPlatformManager()); } private Object getWebSphereJtaPlatformManager() { return getJtaPlatformManager(WEBSPHERE_JTA_PLATFORM_CLASSES); } private void configureSpringJtaPlatform(Map<String, Object> vendorProperties, JtaTransactionManager jtaTransactionManager) { try { vendorProperties.put(JTA_PLATFORM, new SpringJtaPlatform(jtaTransactionManager)); } catch (LinkageError ex) { if (!isUsingJndi()) { throw new IllegalStateException("Unable to set Hibernate JTA " + "platform, are you using the correct " + "version of Hibernate?", ex); } if (logger.isDebugEnabled()) { logger.debug("Unable to set Hibernate JTA platform : " + ex.getMessage()); } } } private boolean isUsingJndi() { try { return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable(); } catch (Error ex) { return false; } } private Object getNoJtaPlatformManager() { return getJtaPlatformManager(NO_JTA_PLATFORM_CLASSES); } private Object getJtaPlatformManager(String[] candidates) { for (String candidate : candidates) { try { return Class.forName(candidate).newInstance(); } catch (Exception ex) { // Continue searching } } throw new IllegalStateException("Could not configure JTA platform"); } protected JtaTransactionManager getJtaTransactionManager() { return this.jtaTransactionManager; } protected final boolean isJta() { return (this.jtaTransactionManager != null); } @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) { this.loadTimeWeaver = loadTimeWeaver; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public EntityManagerFactory getOrCreateEntityManagerFactory( Organization organization) { EntityManagerFactory emf = getEntityManagerFactory(organization); if (emf == null) { emf = createEntityManagerFactory(organization); emfMap.put(organization.getId(), emf); } return emf; } @Override public void generateTables(Organization organization) { SingleConnectionDataSource dataSource = dataSourceService.createSingleConnectionDataSource(organization); if (dataSource != null) { Map<String, Object> vendorProperties = getVendorProperties(dataSource); customizeVendorProperties(vendorProperties); Builder builder = getEntityManagerFactoryBuilder(dataSource).dataSource(dataSource).packages(packagesToScan.split(",")) .properties(vendorProperties).jta(isJta()); LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = builder.build(); publishEvent(organization, builder); entityManagerFactoryBean.setBeanClassLoader(classLoader); entityManagerFactoryBean.setBeanFactory(beanFactory); entityManagerFactoryBean.setBeanName(beanName); entityManagerFactoryBean.setLoadTimeWeaver(loadTimeWeaver); entityManagerFactoryBean.setResourceLoader(resourceLoader); entityManagerFactoryBean.afterPropertiesSet(); entityManagerFactoryBean.destroy(); dataSource.destroy(); } } @Override public EntityManagerFactory createTempEntityManagerFactory( Organization organization) { SingleConnectionDataSource dataSource = dataSourceService.createSingleConnectionDataSource(organization); if (dataSource != null) { Map<String, Object> vendorProperties = getVendorProperties(dataSource); customizeVendorProperties(vendorProperties); Builder builder = getEntityManagerFactoryBuilder(dataSource).dataSource(dataSource).packages(mergePackagesToScan()) .properties(vendorProperties).jta(isJta()); LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = builder.build(); entityManagerFactoryBean.setBeanClassLoader(classLoader); entityManagerFactoryBean.setBeanFactory(beanFactory); entityManagerFactoryBean.setBeanName(beanName); entityManagerFactoryBean.setLoadTimeWeaver(loadTimeWeaver); entityManagerFactoryBean.setResourceLoader(resourceLoader); entityManagerFactoryBean.afterPropertiesSet(); return entityManagerFactoryBean.getObject(); } return null; } @Override public void afterPropertiesSet() throws Exception { emfMap.put(Constants.MASTER, emf); if (listeners != null) { AnnotationAwareOrderComparator.sort(listeners); } } @Override public void removeEntityManagerFactory(Organization organization) { emfMap.remove(organization.getId()); dataSourceService.removeDataSource(organization); } private void publishEvent(Organization organization, Builder builder) { if (listeners != null) { for (EntityManagerFactoryCreateListener entityManagerFactoryCreateListener : listeners) { entityManagerFactoryCreateListener.onCreate(organization, builder); } } } }