/* * Copyright 2008, Unitils.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.unitils.database.transaction.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.unitils.core.UnitilsException; import org.unitils.database.transaction.UnitilsTransactionManager; import javax.sql.DataSource; import java.util.*; /** * Implements transactions for unit tests, by delegating to a spring * <code>PlatformTransactionManager</code>. The concrete implementation of * <code>PlatformTransactionManager</code> that is used depends on the test * class. If a custom <code>PlatformTransactionManager</code> was configured * in a spring <code>ApplicationContext</code>, this one is used. If not, a * suitable subclass of <code>PlatformTransactionManager</code> is created, * depending on the configuration of a test. E.g. if some ORM persistence unit * was configured on the test, a <code>PlatformTransactionManager</code> that * can offer transactional behavior for such a persistence unit is used. If no * such configuration is found, a <code>DataSourceTransactionManager</code> is * used. * * @author Filip Neven * @author Tim Ducheyne */ public class DefaultUnitilsTransactionManager implements UnitilsTransactionManager { /** * The logger instance for this class */ private static Log logger = LogFactory.getLog(DefaultUnitilsTransactionManager.class); protected Map<Object, Boolean> testObjectTransactionActiveMap = new HashMap<Object, Boolean>(); /** * ThreadLocal for holding the TransactionStatus that keeps track of the * current test's transaction status */ protected Map<Object, TransactionStatus> testObjectTransactionStatusMap = new HashMap<Object, TransactionStatus>(); /** * ThreadLocal for holding the PlatformTransactionManager that is used by * the current test */ protected Map<Object, PlatformTransactionManager> testObjectPlatformTransactionManagerMap = new HashMap<Object, PlatformTransactionManager>(); /** * Set of possible providers of a spring * <code>PlatformTransactionManager</code>, not null */ protected List<UnitilsTransactionManagementConfiguration> transactionManagementConfigurations; public void init(Set<UnitilsTransactionManagementConfiguration> transactionManagementConfigurations) { setTransactionManagementConfigurations(transactionManagementConfigurations); } /** * Returns the given datasource, wrapped in a spring * <code>TransactionAwareDataSourceProxy</code> */ public DataSource getTransactionalDataSource(DataSource dataSource) { return new TransactionAwareDataSourceProxy(dataSource); } /** * Starts the transaction. Starts a transaction on the * PlatformTransactionManager that is configured for the given testObject. * * @param testObject The test object, not null */ public void startTransaction(Object testObject) { UnitilsTransactionManagementConfiguration transactionManagementConfiguration = getTransactionManagementConfiguration(testObject); if (transactionManagementConfiguration.isTransactionalResourceAvailable(testObject)) { testObjectTransactionActiveMap.put(testObject, Boolean.TRUE); doStartTransaction(testObject, transactionManagementConfiguration); } else { testObjectTransactionActiveMap.put(testObject, Boolean.FALSE); } } public void activateTransactionIfNeeded(Object testObject) { if (testObjectTransactionActiveMap.containsKey(testObject) && !testObjectTransactionActiveMap.get(testObject)) { testObjectTransactionActiveMap.put(testObject, Boolean.TRUE); UnitilsTransactionManagementConfiguration transactionManagementConfiguration = getTransactionManagementConfiguration(testObject); doStartTransaction(testObject, transactionManagementConfiguration); } } protected void doStartTransaction(Object testObject, UnitilsTransactionManagementConfiguration transactionManagementConfiguration) { logger.debug("Starting transaction"); PlatformTransactionManager platformTransactionManager = transactionManagementConfiguration.getSpringPlatformTransactionManager(testObject); testObjectPlatformTransactionManagerMap.put(testObject, platformTransactionManager); TransactionStatus transactionStatus = platformTransactionManager.getTransaction(createTransactionDefinition(testObject)); testObjectTransactionStatusMap.put(testObject, transactionStatus); } /** * Commits the transaction. Uses the PlatformTransactionManager and transaction * that is associated with the given test object. * * @param testObject The test object, not null */ public void commit(Object testObject) { if (!testObjectTransactionActiveMap.containsKey(testObject)) { throw new UnitilsException("Trying to commit, while no transaction is currently active"); } TransactionStatus transactionStatus = testObjectTransactionStatusMap.get(testObject); if (testObjectTransactionActiveMap.get(testObject)) { logger.debug("Committing transaction"); testObjectPlatformTransactionManagerMap.get(testObject).commit(transactionStatus); } testObjectTransactionActiveMap.remove(testObject); testObjectTransactionStatusMap.remove(testObject); testObjectPlatformTransactionManagerMap.remove(testObject); } /** * Rolls back the transaction. Uses the PlatformTransactionManager and transaction * that is associated with the given test object. * * @param testObject The test object, not null */ public void rollback(Object testObject) { if (!testObjectTransactionActiveMap.containsKey(testObject)) { throw new UnitilsException("Trying to rollback, while no transaction is currently active"); } TransactionStatus transactionStatus = testObjectTransactionStatusMap.get(testObject); if (testObjectTransactionActiveMap.get(testObject)) { logger.debug("Rolling back transaction"); testObjectPlatformTransactionManagerMap.get(testObject).rollback(transactionStatus); } testObjectTransactionActiveMap.remove(testObject); testObjectTransactionStatusMap.remove(testObject); testObjectPlatformTransactionManagerMap.remove(testObject); } /** * Returns a <code>TransactionDefinition</code> object containing the * necessary transaction parameters. Simply returns a default * <code>DefaultTransactionDefinition</code> object with the 'propagation * required' attribute * * @param testObject The test object, not null * @return The default TransactionDefinition */ protected TransactionDefinition createTransactionDefinition(Object testObject) { return new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED); } protected UnitilsTransactionManagementConfiguration getTransactionManagementConfiguration(Object testObject) { for (UnitilsTransactionManagementConfiguration transactionManagementConfiguration : transactionManagementConfigurations) { if (transactionManagementConfiguration.isApplicableFor(testObject)) { return transactionManagementConfiguration; } } throw new UnitilsException("No applicable transaction management configuration found for test " + testObject.getClass()); } protected void setTransactionManagementConfigurations(Set<UnitilsTransactionManagementConfiguration> transactionManagementConfigurationsSet) { List<UnitilsTransactionManagementConfiguration> configurations = new ArrayList<UnitilsTransactionManagementConfiguration>(); configurations.addAll(transactionManagementConfigurationsSet); Collections.sort(configurations, new Comparator<UnitilsTransactionManagementConfiguration>() { public int compare(UnitilsTransactionManagementConfiguration o1, UnitilsTransactionManagementConfiguration o2) { return o2.getPreference().compareTo(o1.getPreference()); } }); this.transactionManagementConfigurations = configurations; } }