Package org.grails.transaction

Source Code of org.grails.transaction.ChainedTransactionManagerPostProcessor

package org.grails.transaction;

import grails.config.Config;
import groovy.util.ConfigObject;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.grails.config.PropertySourcesConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.ClassUtils;

/**
*  A {@link BeanDefinitionRegistryPostProcessor} for using the "Best Effort 1 Phase Commit" (BE1PC) in Grails
*  applications when there are multiple data sources. 
*  When the context contains multiple transactionManager beans, the bean with the name "transactionManager"
*  will be renamed to "$primaryTransactionManager" and a new ChainedTransactionManager bean will be added with the name
*  "transactionManager". All transactionManager beans will be registered in the ChainedTransactionManager bean.
*
*  The post processor checks if the previous transactionManager bean is an instance of {@link JtaTransactionManager}.
*  In that case it will not do anything since it's assumed that JTA/XA is handling transactions spanning multiple datasources.
*  For performance reasons an additional dataSource can be marked as non-transactional by adding a property 'transactional = false' in
*  it's dataSource configuration. This will leave the dataSource out of the transactions initiated by Grails transactions.
*  This is the default behaviour in Grails versions before Grails 2.3.6 . 
* @author Lari Hotari
* @since 2.3.6
*
*/
public class ChainedTransactionManagerPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {
    private static final String TRANSACTIONAL = "transactional";
    private static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME_WHITELIST_PATTERN = "(?i).*transactionManager(_.+)?";
    private static final String DEFAULT_TRANSACTION_MANAGER_INTERNAL_BEAN_NAME_BLACKLIST_PATTERN = "(?i)chainedTransactionManagerPostProcessor|transactionManagerPostProcessor|.*PostProcessor";
    private String beanNameWhitelistPattern = DEFAULT_TRANSACTION_MANAGER_BEAN_NAME_WHITELIST_PATTERN;
    private String beanNameBlacklistPattern = null;
    private String beanNameInternalBlacklistPattern = DEFAULT_TRANSACTION_MANAGER_INTERNAL_BEAN_NAME_BLACKLIST_PATTERN;
    private static final Pattern SUFFIX_PATTERN = Pattern.compile("^transactionManager_(.+)$");
    private static final String PRIMARY_TRANSACTION_MANAGER = "$primaryTransactionManager";
    private static final String TRANSACTION_MANAGER = "transactionManager";
    private static final String READONLY = "readOnly";
   
    private Config config;
   
    public ChainedTransactionManagerPostProcessor(Config config) {
        this(config, null, null);
    }
   
    public ChainedTransactionManagerPostProcessor(Config config, String whitelistPattern, String blacklistPattern) {
        this.config = config;
        if (whitelistPattern != null) {
            beanNameWhitelistPattern = whitelistPattern;
        }
        if (blacklistPattern != null) {
            beanNameBlacklistPattern = blacklistPattern;
        }
    }
   
    public ChainedTransactionManagerPostProcessor() {
        this(new PropertySourcesConfig(), null, null);
    }
   
    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected Map<String, Map> readDataSourceConfig() {
        Map<String, Map> dsConfigs = new LinkedHashMap<String, Map>();
        if (config != null) {
            if(config.containsKey("dataSource")) {
                dsConfigs.put("", (Map)config.get("dataSource"));
            }
            for(Map.Entry<String, Object> entry : config.entrySet()) {
                String name=String.valueOf(entry.getKey());
                Object value = entry.getValue();
                if(name.startsWith("dataSource_") && value instanceof Map) {
                    dsConfigs.put(name.substring("dataSource_".length()), (Map) value);
                }
            }
        }
        return dsConfigs;
    }
   
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
       
    }

    protected void registerAdditionalTransactionManagers(BeanDefinitionRegistry registry, BeanDefinition chainedTransactionManagerBeanDefinition, ManagedList<RuntimeBeanReference> transactionManagerRefs) {
        String[] allBeanNames = registry.getBeanDefinitionNames();
        Map<String, Map> dsConfigs=readDataSourceConfig();
        for (String beanName : allBeanNames) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
            if(!TRANSACTION_MANAGER.equals(beanName) && !PRIMARY_TRANSACTION_MANAGER.equals(beanName) && isValidTransactionManagerBeanDefinition(beanName, beanDefinition)) {
                String suffix = resolveDataSourceSuffix(beanName);
                if (!isNotTransactional(dsConfigs, suffix)) {
                    transactionManagerRefs.add(new RuntimeBeanReference(beanName));
                }
            }
        }
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if (registry.containsBeanDefinition(TRANSACTION_MANAGER) && countChainableTransactionManagerBeans(registry) > 1 && !hasJtaOrChainedTransactionManager(registry)) {
            BeanDefinition chainedTransactionManagerBeanDefinition = addChainedTransactionManager(registry);
            ManagedList<RuntimeBeanReference> transactionManagerRefs = createTransactionManagerBeanReferences(chainedTransactionManagerBeanDefinition);
            registerAdditionalTransactionManagers(registry, chainedTransactionManagerBeanDefinition, transactionManagerRefs);
        }
    }

    protected ManagedList<RuntimeBeanReference> createTransactionManagerBeanReferences(
            BeanDefinition chainedTransactionManagerBeanDefinition) {
        ManagedList<RuntimeBeanReference> transactionManagerRefs = new ManagedList<RuntimeBeanReference>();
        ConstructorArgumentValues constructorValues=chainedTransactionManagerBeanDefinition.getConstructorArgumentValues();
        constructorValues.addIndexedArgumentValue(0, transactionManagerRefs);
        transactionManagerRefs.add(new RuntimeBeanReference(PRIMARY_TRANSACTION_MANAGER));
        return transactionManagerRefs;
    }

    protected BeanDefinition addChainedTransactionManager(BeanDefinitionRegistry registry) {
        renameBean(TRANSACTION_MANAGER, PRIMARY_TRANSACTION_MANAGER, registry);
        BeanDefinition beanDefinition = new RootBeanDefinition(ChainedTransactionManager.class);
        registry.registerBeanDefinition(TRANSACTION_MANAGER, beanDefinition);
        return beanDefinition;
    }

    protected boolean hasJtaOrChainedTransactionManager(BeanDefinitionRegistry registry) {
        Class<?> transactionManagerBeanClass = resolveTransactionManagerClass(registry);
        if (transactionManagerBeanClass == null) {
            return false;
        }
        boolean isJtaTransactionManager = JtaTransactionManager.class.isAssignableFrom(transactionManagerBeanClass);
        boolean isChainedTransactionManager = ChainedTransactionManager.class.isAssignableFrom(transactionManagerBeanClass);
        return isJtaTransactionManager || isChainedTransactionManager;
    }

    protected Class<?> resolveTransactionManagerClass(BeanDefinitionRegistry registry) {
        if(!registry.containsBeanDefinition(TRANSACTION_MANAGER)) {
            return null;
        }
        BeanDefinition transactionManagerBeanDefinition = registry.getBeanDefinition(TRANSACTION_MANAGER);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();           
        Class<?> transactionManagerBeanClass = ClassUtils.resolveClassName(transactionManagerBeanDefinition.getBeanClassName(), classLoader);
        return transactionManagerBeanClass;
    }

    protected int countChainableTransactionManagerBeans(BeanDefinitionRegistry registry) {
        Map<String, Map> dsConfigs=readDataSourceConfig();
        int transactionManagerBeanCount=0;
        for (String beanName : registry.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
            if(isValidTransactionManagerBeanDefinition(beanName, beanDefinition)) {
                String suffix = resolveDataSourceSuffix(beanName);
                if (beanName.equals(TRANSACTION_MANAGER) || !isNotTransactional(dsConfigs, suffix)) {
                    transactionManagerBeanCount++;
                }
            }
        }
        return transactionManagerBeanCount;
    }

    protected boolean isValidTransactionManagerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        return beanName.matches(beanNameWhitelistPattern) && (beanNameBlacklistPattern==null || !beanName.matches(beanNameBlacklistPattern)) && !beanName.matches(beanNameInternalBlacklistPattern);
    }
   
    protected boolean isNotTransactional(Map<String, Map> dsConfigs, String suffix) {
        if (suffix == null) {
            return false;
        }
        Map dsConfig = dsConfigs.get(suffix);
        if (dsConfig != null) {
            if(dsConfig.containsKey(TRANSACTIONAL)) {
                Object transactionalValue = dsConfig.get(TRANSACTIONAL);
                if (transactionalValue instanceof Boolean) {
                    return !((Boolean)transactionalValue).booleanValue();
                }
            }
            if(dsConfig.containsKey(READONLY)) {
                Object readOnlyValue = dsConfig.get(READONLY);
                if (readOnlyValue instanceof Boolean) {
                    return ((Boolean)readOnlyValue).booleanValue();
                }
            }
        }
        return false;
    }

    protected String resolveDataSourceSuffix(String transactionManagerBeanName) {
        if(TRANSACTION_MANAGER.equals(transactionManagerBeanName)) {
            return "";
        } else {
            Matcher matcher=SUFFIX_PATTERN.matcher(transactionManagerBeanName);
            if(matcher.matches()) {
                return matcher.group(1);
            }
        }
        return null;
    }

    private static boolean renameBean(String oldName, String newName, BeanDefinitionRegistry registry) {
        if(!registry.containsBeanDefinition(oldName)) {
            return false;
        }
        // remove link to child beans
        Set<String> previousChildBeans = new LinkedHashSet<String>();
        for (String bdName : registry.getBeanDefinitionNames()) {
            if (!oldName.equals(bdName)) {
                BeanDefinition bd = registry.getBeanDefinition(bdName);
                if (oldName.equals(bd.getParentName())) {
                    bd.setParentName(null);
                    previousChildBeans.add(bdName);
                }
            }
        }
        BeanDefinition oldBeanDefinition = registry.getBeanDefinition(oldName);
        registry.removeBeanDefinition(oldName);
        registry.registerBeanDefinition(newName, oldBeanDefinition);
        // re-link possible child beans to new parent name
        for(String bdName : previousChildBeans) {
            BeanDefinition bd = registry.getBeanDefinition(bdName);
            bd.setParentName(newName);
        }
        return true;
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    public void setConfig(Config config) {
        this.config = config;
    }

    public String getBeanNameWhitelistPattern() {
        return beanNameWhitelistPattern;
    }

    public void setBeanNameWhitelistPattern(String beanNameWhitelistPattern) {
        this.beanNameWhitelistPattern = beanNameWhitelistPattern;
    }

    public String getBeanNameBlacklistPattern() {
        return beanNameBlacklistPattern;
    }

    public void setBeanNameBlacklistPattern(String beanNameBlacklistPattern) {
        this.beanNameBlacklistPattern = beanNameBlacklistPattern;
    }

    public String getBeanNameInternalBlacklistPattern() {
        return beanNameInternalBlacklistPattern;
    }

    public void setBeanNameInternalBlacklistPattern(String beanNameInternalBlacklistPattern) {
        this.beanNameInternalBlacklistPattern = beanNameInternalBlacklistPattern;
    }
}
TOP

Related Classes of org.grails.transaction.ChainedTransactionManagerPostProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.