From 983ec0ebc82519e9e99d71f08a00c4f395a0e121 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 22 Aug 2014 10:09:14 -0700 Subject: [PATCH] Add Atomikos support classes Add support classes for the Atomikos JTA library, including: - A Spring friendly ConnectionFactoryBean and DataSourceBean. - A PostProcessor to apply "depends-on" ordering automatically. - A bindable properties class for type-safe configuration. See gh-947 --- spring-boot-dependencies/pom.xml | 22 ++ spring-boot/pom.xml | 20 ++ .../AtomikosConnectionFactoryBean.java | 54 ++++ .../jta/atomikos/AtomikosDataSourceBean.java | 53 ++++ ...ikosDependsOnBeanFactoryPostProcessor.java | 113 +++++++++ .../jta/atomikos/AtomikosLoggingLevel.java | 43 ++++ .../boot/jta/atomikos/AtomikosProperties.java | 237 ++++++++++++++++++ .../boot/jta/package-info.java | 21 ++ .../AtomikosConnectionFactoryBeanTests.java | 61 +++++ .../atomikos/AtomikosDataSourceBeanTests.java | 60 +++++ ...ependsOnBeanFactoryPostProcessorTests.java | 99 ++++++++ .../jta/atomikos/AtomikosPropertiesTests.java | 77 ++++++ 12 files changed, 860 insertions(+) create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBean.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBean.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessor.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosLoggingLevel.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/package-info.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBeanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessorTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 9a5707e86e7..1811d1b3191 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -48,6 +48,7 @@ 5.9.1 1.8.2 + 3.9.3 3.0.2 1.9.2 3.2.1 @@ -85,6 +86,7 @@ 1.2.2 0.9.1 1.2 + 1.1 4.11 3.0.8 1.2.17 @@ -350,6 +352,21 @@ logback-classic ${logback.version} + + com.atomikos + transactions-jdbc + ${atomikos.version} + + + com.atomikos + transactions-jms + ${atomikos.version} + + + com.atomikos + transactions-jta + ${atomikos.version} + com.codahale.metrics metrics-graphite @@ -482,6 +499,11 @@ jstl ${jstl.version} + + javax.transaction + jta + ${jta.version} + jaxen jaxen diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 86d4e5f2df7..d37027dc2c1 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -34,11 +34,31 @@ logback-classic true + + com.atomikos + transactions-jms + true + + + com.atomikos + transactions-jta + true + + + com.atomikos + transactions-jdbc + true + com.fasterxml.jackson.core jackson-databind true + + javax.jms + jms-api + true + javax.servlet javax.servlet-api diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBean.java new file mode 100644 index 00000000000..9559ed1b5fe --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBean.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; + +/** + * Spring friendly version of {@link com.atomikos.jms.AtomikosConnectionFactoryBean}. + * + * @author Phillip Webb + * @since 1.2.0 + */ +public class AtomikosConnectionFactoryBean extends + com.atomikos.jms.AtomikosConnectionFactoryBean implements BeanNameAware, + InitializingBean, DisposableBean { + + private String beanName; + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (!StringUtils.hasLength(getUniqueResourceName())) { + setUniqueResourceName(this.beanName); + } + init(); + } + + @Override + public void destroy() throws Exception { + close(); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBean.java new file mode 100644 index 00000000000..d601aaf37a0 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBean.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; + +/** + * Spring friendly version of {@link com.atomikos.jdbc.AtomikosDataSourceBean}. + * + * @author Phillip Webb + * @since 1.2.0 + */ +public class AtomikosDataSourceBean extends com.atomikos.jdbc.AtomikosDataSourceBean + implements BeanNameAware, InitializingBean, DisposableBean { + + private String beanName; + + @Override + public void setBeanName(String name) { + this.beanName = name; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (!StringUtils.hasLength(getUniqueResourceName())) { + setUniqueResourceName(this.beanName); + } + init(); + } + + @Override + public void destroy() throws Exception { + close(); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessor.java new file mode 100644 index 00000000000..4d0fdc22ddc --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessor.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.Ordered; + +import com.atomikos.icatch.jta.UserTransactionManager; + +/** + * {@link BeanFactoryPostProcessor} to automatically setup the recommended + * {@link BeanDefinition#setDependsOn(String[]) dependsOn} settings for correct Atomikos + * ordering. + * + * @author Phillip Webb + * @since 1.2.0 + */ +public class AtomikosDependsOnBeanFactoryPostProcessor implements + BeanFactoryPostProcessor, Ordered { + + private static final String[] NO_BEANS = {}; + + private int order = Ordered.LOWEST_PRECEDENCE; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + String[] transactionManagers = beanFactory.getBeanNamesForType( + UserTransactionManager.class, true, false); + for (String transactionManager : transactionManagers) { + addTransactionManagerDependencies(beanFactory, transactionManager); + } + addMessageDrivenContainerDependencies(beanFactory, transactionManagers); + } + + private void addTransactionManagerDependencies( + ConfigurableListableBeanFactory beanFactory, String transactionManager) { + BeanDefinition bean = beanFactory.getBeanDefinition(transactionManager); + Set dependsOn = new LinkedHashSet(asList(bean.getDependsOn())); + int initialSize = dependsOn.size(); + addDependencies(beanFactory, "javax.jms.ConnectionFactory", dependsOn); + addDependencies(beanFactory, "javax.sql.DataSource", dependsOn); + if (dependsOn.size() != initialSize) { + bean.setDependsOn(dependsOn.toArray(new String[dependsOn.size()])); + } + } + + private void addMessageDrivenContainerDependencies( + ConfigurableListableBeanFactory beanFactory, String[] transactionManagers) { + String[] messageDrivenContainers = getBeanNamesForType(beanFactory, + "com.atomikos.jms.extra.MessageDrivenContainer"); + for (String messageDrivenContainer : messageDrivenContainers) { + BeanDefinition bean = beanFactory.getBeanDefinition(messageDrivenContainer); + Set dependsOn = new LinkedHashSet(asList(bean.getDependsOn())); + dependsOn.addAll(asList(transactionManagers)); + bean.setDependsOn(dependsOn.toArray(new String[dependsOn.size()])); + } + } + + private void addDependencies(ConfigurableListableBeanFactory beanFactory, + String type, Set dependsOn) { + dependsOn.addAll(asList(getBeanNamesForType(beanFactory, type))); + } + + private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, + String type) { + try { + return beanFactory.getBeanNamesForType(Class.forName(type), true, false); + } + catch (ClassNotFoundException ex) { + // Ignore + } + return NO_BEANS; + } + + private List asList(String[] array) { + return (array == null ? Collections. emptyList() : Arrays.asList(array)); + } + + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosLoggingLevel.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosLoggingLevel.java new file mode 100644 index 00000000000..39e5fca7595 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosLoggingLevel.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +/** + * Logging levels supported by Atomikos. + * + * @author Phillip Webb + * @see AtomikosProperties + * @since 1.2.0 + */ +public enum AtomikosLoggingLevel { + + /** + * Debug Level. + */ + DEBUG, + + /** + * Info Level. + */ + INFO, + + /** + * Warning Level. + */ + WARN + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java new file mode 100644 index 00000000000..5222846035c --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java @@ -0,0 +1,237 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * Bean friendly variant of Atomikos configuration + * properties. Allows for setter based configuration and is amiable to relaxed data + * binding. + * + * @author Phillip Webb + * @see #asProperties() + * @since 1.2.0 + */ +public class AtomikosProperties { + + private final Map values = new TreeMap(); + + /** + * Specifies the transaction manager implementation that should be started. There is + * no default value and this must be set. Generally, + * {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory} is the + * value you should set. + * @param service the service + */ + public void setService(String service) { + set("service", service); + } + + /** + * Specifies the maximum timeout (in milliseconds) that can be allowed for + * transactions. Defaults to {@literal 300000}. This means that calls to + * UserTransaction.setTransactionTimeout() with a value higher than configured here + * will be max'ed to this value. + * @param maxTimeout the max timeout + */ + public void setMaxTimeout(long maxTimeout) { + set("max_timeout", maxTimeout); + } + + /** + * The default timeout for JTA transactions (optional, defaults to {@literal 10000} + * ms). + * @param defaultJtaTimeout the default JTA timeout + */ + public void setDefaultJtaTimeout(long defaultJtaTimeout) { + set("default_jta_timeout", defaultJtaTimeout); + } + + /** + * Specifies the maximum number of active transactions. Defaults to {@literal 50}. A + * negative value means infinite amount. You will get an {@code IllegalStateException} + * with error message "Max number of active transactions reached" if you call + * {@code UserTransaction.begin()} while there are already n concurrent transactions + * running, n being this value. + * @param maxActivities the max activities + */ + public void setMaxActives(int maxActivities) { + set("max_actives", maxActivities); + } + + /** + * Specifies if disk logging should be enabled or not. Defaults to true. It is useful + * for JUnit testing, or to profile code without seeing the transaction manager's + * activity as a hot spot but this should never be disabled on production or data + * integrity cannot be guaranteed. + * @param enableLogging if logging is enabled + */ + public void setEnableLogging(boolean enableLogging) { + set("enable_logging", enableLogging); + } + + /** + * Specifies the transaction manager's unique name. Defaults to the machine's IP + * address. If you plan to run more than one transaction manager against one database + * you must set this property to a unique value or you might run into duplicate + * transaction ID (XID) problems that can be quite subtle (example: + * {@literal http://fogbugz.atomikos.com/default.asp?community.6.2225.7}). If multiple + * instances need to use the same properties file then the easiest way to ensure + * uniqueness for this property is by referencing a system property specified at VM + * startup. + * @param uniqueName the unique name + */ + public void setTransactionManagerUniqueName(String uniqueName) { + set("tm_unique_name", uniqueName); + } + + /** + * Specifies if subtransactions should be joined when possible. Defaults to true. When + * false, no attempt to call {@code XAResource.start(TM_JOIN)} will be made for + * different but related subtransctions. This setting has no effect on resource access + * within one and the same transaction. If you don't use subtransactions then this + * setting can be ignored. + * @param serialJtaTransactions if serial JTA transaction are supported + */ + public void setSerialJtaTransactions(boolean serialJtaTransactions) { + set("serial_jta_transactions", serialJtaTransactions); + } + + /** + * Specifies whether VM shutdown should trigger forced shutdown of the transaction + * core. Defaults to false. + * @param forceShutdownOnVmExit + */ + public void setForceShutdownOnVmExit(boolean forceShutdownOnVmExit) { + set("force_shutdown_on_vm_exit", forceShutdownOnVmExit); + } + + /** + * Specifies the transactions log file base name. Defaults to {@literal tmlog}. The + * transactions logs are stored in files using this name appended with a number and + * the extension {@literal .log}. At checkpoint, a new transactions log file is + * created and the number is incremented. + * @param logBaseName the log base name + */ + public void setLogBaseName(String logBaseName) { + set("log_base_name", logBaseName); + } + + /** + * Specifies the directory in which the log files should be stored. Defaults to the + * current working directory. This directory should be a stable storage like a SAN, + * RAID or at least backed up location. The transactions logs files are as important + * as the data themselves to guarantee consistency in case of failures. + * @param logBaseDir the log base dir + */ + public void setLogBaseDir(String logBaseDir) { + set("log_base_dir", logBaseDir); + } + + /** + * Specifies the interval between checkpoints. A checkpoint reduces the log file size + * at the expense of adding some overhead in the runtime. Defaults to {@literal 500}. + * @param checkpointInterval the checkpoint interval + */ + public void setCheckpointInterval(long checkpointInterval) { + set("checkpoint_interval", checkpointInterval); + } + + /** + * Specifies the console log level. Defaults to {@link AtomikosLoggingLevel#WARN}. + * @param consoleLogLevel the console log level + */ + public void setConsoleLogLevel(AtomikosLoggingLevel consoleLogLevel) { + set("console_log_level", consoleLogLevel); + } + + /** + * Specifies the directory in which to store the debug log files. Defaults to the + * current working directory. + * @param outputDir the output dir + */ + public void setOutputDir(String outputDir) { + set("output_dir", outputDir); + } + + /** + * Specifies the debug logs file name. Defaults to {@literal tm.out}. + * @param consoleFileName the console file name + */ + public void setConsoleFileName(String consoleFileName) { + set("console_file_name", consoleFileName); + } + + /** + * Specifies how many debug logs files can be created. Defaults to {@literal 1}. + * @param consoleFileCount the console file count + */ + public void setConsoleFileCount(int consoleFileCount) { + set("console_file_count", consoleFileCount); + } + + /** + * Specifies how many bytes can be stored at most in debug logs files. Defaults to + * {@literal -1}. Negative values means unlimited. + * @param consoleFileLimit the console file limit + */ + public void setConsoleFileLimit(int consoleFileLimit) { + set("console_file_limit", consoleFileLimit); + } + + /** + * Specifies whether or not to use different (and concurrent) threads for two-phase + * commit on the participating resources. Setting this to {@literal true} implies that + * the commit is more efficient since waiting for acknowledgements is done in + * parallel. Defaults to {@literal true}. If you set this to {@literal false}, then + * commits will happen in the order that resources are accessed within the + * transaction. + * @param threadedTwoPhaseCommit if threaded two phase commits should be used + */ + public void setThreadedTwoPhaseCommit(boolean threadedTwoPhaseCommit) { + set("threaded_2pc", threadedTwoPhaseCommit); + } + + private void set(String key, Object value) { + set("com.atomikos.icatch.", key, value); + } + + private void set(String keyPrefix, String key, Object value) { + if (value != null) { + this.values.put(keyPrefix + key, value.toString()); + } + else { + this.values.remove(keyPrefix + key); + } + } + + /** + * Returns the properties as a {@link Properties} object that can be used with + * Atomikos. + * @return the properties + */ + public Properties asProperties() { + Properties properties = new Properties(); + properties.putAll(this.values); + return properties; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/package-info.java b/spring-boot/src/main/java/org/springframework/boot/jta/package-info.java new file mode 100644 index 00000000000..3ca0cc266ba --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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. + */ + +/** + * Support for the Java Transaction API. + */ +package org.springframework.boot.jta; + diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBeanTests.java new file mode 100644 index 00000000000..55b7f0bcf79 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosConnectionFactoryBeanTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import javax.jms.JMSException; + +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link AtomikosConnectionFactoryBean}. + * + * @author Phillip Webb + */ +public class AtomikosConnectionFactoryBeanTests { + + @Test + public void beanMethods() throws Exception { + MockAtomikosConnectionFactoryBean bean = spy(new MockAtomikosConnectionFactoryBean()); + bean.setBeanName("bean"); + bean.afterPropertiesSet(); + assertThat(bean.getUniqueResourceName(), equalTo("bean")); + verify(bean).init(); + verify(bean, never()).close(); + bean.destroy(); + verify(bean).close(); + } + + private static class MockAtomikosConnectionFactoryBean extends + AtomikosConnectionFactoryBean { + + @Override + public synchronized void init() throws JMSException { + } + + @Override + public synchronized void close() { + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java new file mode 100644 index 00000000000..10348aa5bf7 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDataSourceBeanTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import org.junit.Test; + +import com.atomikos.jdbc.AtomikosSQLException; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link AtomikosDataSourceBean}. + * + * @author Phillip Webb + */ +public class AtomikosDataSourceBeanTests { + + @Test + public void beanMethods() throws Exception { + MockAtomikosDataSourceBean bean = spy(new MockAtomikosDataSourceBean()); + bean.setBeanName("bean"); + bean.afterPropertiesSet(); + assertThat(bean.getUniqueResourceName(), equalTo("bean")); + verify(bean).init(); + verify(bean, never()).close(); + bean.destroy(); + verify(bean).close(); + } + + private static class MockAtomikosDataSourceBean extends AtomikosDataSourceBean { + + @Override + public synchronized void init() throws AtomikosSQLException { + } + + @Override + public synchronized void close() { + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessorTests.java new file mode 100644 index 00000000000..485f46d9539 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosDependsOnBeanFactoryPostProcessorTests.java @@ -0,0 +1,99 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import java.util.Arrays; +import java.util.HashSet; + +import javax.jms.ConnectionFactory; +import javax.sql.DataSource; + +import org.junit.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.atomikos.icatch.jta.UserTransactionManager; +import com.atomikos.jms.extra.MessageDrivenContainer; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AtomikosDependsOnBeanFactoryPostProcessor}. + * + * @author Phillip Webb + */ +public class AtomikosDependsOnBeanFactoryPostProcessorTests { + + private AnnotationConfigApplicationContext context; + + @Test + public void setsDependsOn() { + this.context = new AnnotationConfigApplicationContext(Config.class); + assertDependsOn("dataSource"); + assertDependsOn("connectionFactory"); + assertDependsOn("userTransactionManager", "dataSource", "connectionFactory"); + assertDependsOn("messageDrivenContainer", "userTransactionManager"); + this.context.close(); + } + + private void assertDependsOn(String bean, String... expected) { + BeanDefinition definition = this.context.getBeanDefinition(bean); + if (definition.getDependsOn() == null) { + assertTrue("No dependsOn expected for " + bean, expected.length == 0); + return; + } + HashSet dependsOn = new HashSet(Arrays.asList(definition + .getDependsOn())); + assertThat(dependsOn, equalTo(new HashSet(Arrays.asList(expected)))); + } + + @Configuration + static class Config { + + @Bean + public DataSource dataSource() { + return mock(DataSource.class); + } + + @Bean + public ConnectionFactory connectionFactory() { + return mock(ConnectionFactory.class); + } + + @Bean + public UserTransactionManager userTransactionManager() { + return mock(UserTransactionManager.class); + } + + @Bean + public MessageDrivenContainer messageDrivenContainer() { + return mock(MessageDrivenContainer.class); + } + + @Bean + public static AtomikosDependsOnBeanFactoryPostProcessor atomikosPostProcessor() { + return new AtomikosDependsOnBeanFactoryPostProcessor(); + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java new file mode 100644 index 00000000000..3060a60cb3c --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * 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.springframework.boot.jta.atomikos; + +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Tests for ;@link AtomikosProperties}. + * + * @author Phillip Webb + */ +public class AtomikosPropertiesTests { + + private AtomikosProperties properties = new AtomikosProperties(); + + @Test + public void testProperties() { + this.properties.setService("service"); + this.properties.setMaxTimeout(1L); + this.properties.setDefaultJtaTimeout(2L); + this.properties.setMaxActives(3); + this.properties.setEnableLogging(true); + this.properties.setTransactionManagerUniqueName("uniqueName"); + this.properties.setSerialJtaTransactions(true); + this.properties.setForceShutdownOnVmExit(true); + this.properties.setLogBaseName("logBaseName"); + this.properties.setLogBaseDir("logBaseDir"); + this.properties.setCheckpointInterval(4); + this.properties.setConsoleLogLevel(AtomikosLoggingLevel.WARN); + this.properties.setOutputDir("outputDir"); + this.properties.setConsoleFileName("consoleFileName"); + this.properties.setConsoleFileCount(5); + this.properties.setConsoleFileLimit(6); + this.properties.setThreadedTwoPhaseCommit(true); + + assertThat(this.properties.asProperties().size(), equalTo(17)); + assertProperty("com.atomikos.icatch.service", "service"); + assertProperty("com.atomikos.icatch.max_timeout", "1"); + assertProperty("com.atomikos.icatch.default_jta_timeout", "2"); + assertProperty("com.atomikos.icatch.max_actives", "3"); + assertProperty("com.atomikos.icatch.enable_logging", "true"); + assertProperty("com.atomikos.icatch.tm_unique_name", "uniqueName"); + assertProperty("com.atomikos.icatch.serial_jta_transactions", "true"); + assertProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "true"); + assertProperty("com.atomikos.icatch.log_base_name", "logBaseName"); + assertProperty("com.atomikos.icatch.log_base_dir", "logBaseDir"); + assertProperty("com.atomikos.icatch.checkpoint_interval", "4"); + assertProperty("com.atomikos.icatch.console_log_level", "WARN"); + assertProperty("com.atomikos.icatch.output_dir", "outputDir"); + assertProperty("com.atomikos.icatch.console_file_name", "consoleFileName"); + assertProperty("com.atomikos.icatch.console_file_count", "5"); + assertProperty("com.atomikos.icatch.console_file_limit", "6"); + assertProperty("com.atomikos.icatch.threaded_2pc", "true"); + } + + private void assertProperty(String key, String value) { + assertThat(this.properties.asProperties().getProperty(key), equalTo(value)); + } + +}