diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index 1811d1b319..9cdc69299f 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -49,6 +49,7 @@
5.9.1
1.8.2
3.9.3
+ 2.1.4
3.0.2
1.9.2
3.2.1
@@ -387,6 +388,11 @@
metrics-servlets
${codahale-metrics.version}
+
+ org.codehaus.btm
+ btm
+ ${bitronix.version}
+
org.codehaus.janino
janino
diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml
index d37027dc2c..7036d0cb55 100644
--- a/spring-boot/pom.xml
+++ b/spring-boot/pom.xml
@@ -89,6 +89,11 @@
tomcat-embed-jasper
true
+
+ org.codehaus.btm
+ btm
+ true
+
org.codehaus.groovy
groovy
diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessor.java
new file mode 100644
index 0000000000..df800b196a
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.bitronix;
+
+import javax.transaction.TransactionManager;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.Ordered;
+
+/**
+ * {@link BeanFactoryPostProcessor} to automatically register the recommended
+ * {@link ConfigurableListableBeanFactory#registerDependentBean(String, String)
+ * dependencies} for correct Bitronix shutdown ordering. With Bitronix it appears that
+ * ConnectionFactory and DataSource beans must be shutdown before the
+ * {@link TransactionManager}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class BitronixDependentBeanFactoryPostProcessor 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(
+ TransactionManager.class, true, false);
+ for (String transactionManager : transactionManagers) {
+ addTransactionManagerDependencies(beanFactory, transactionManager);
+ }
+ }
+
+ private void addTransactionManagerDependencies(
+ ConfigurableListableBeanFactory beanFactory, String transactionManager) {
+ for (String dependentBeanName : getBeanNamesForType(beanFactory,
+ "javax.jms.ConnectionFactory")) {
+ beanFactory.registerDependentBean(transactionManager, dependentBeanName);
+ }
+ for (String dependentBeanName : getBeanNamesForType(beanFactory,
+ "javax.sql.DataSource")) {
+ beanFactory.registerDependentBean(transactionManager, dependentBeanName);
+ }
+ }
+
+ private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory,
+ String type) {
+ try {
+ return beanFactory.getBeanNamesForType(Class.forName(type), true, false);
+ }
+ catch (ClassNotFoundException ex) {
+ // Ignore
+ }
+ return NO_BEANS;
+ }
+
+ @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/bitronix/PoolingConnectionFactoryBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java
new file mode 100644
index 0000000000..215e82db2f
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBean.java
@@ -0,0 +1,141 @@
+/*
+ * 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.bitronix;
+
+import java.util.Properties;
+
+import javax.jms.JMSException;
+import javax.jms.XAConnection;
+import javax.jms.XAConnectionFactory;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.StringUtils;
+
+import bitronix.tm.resource.common.ResourceBean;
+import bitronix.tm.resource.common.XAStatefulHolder;
+import bitronix.tm.resource.jms.PoolingConnectionFactory;
+
+/**
+ * Spring friendly version of {@link PoolingConnectionFactory}. Provides sensible defaults
+ * and also supports direct wrapping of a {@link XAConnectionFactory} instance.
+ *
+ * @author Phillip Webb
+ * @author Josh Long
+ * @since 1.2.0
+ */
+public class PoolingConnectionFactoryBean extends PoolingConnectionFactory implements
+ BeanNameAware, InitializingBean, DisposableBean {
+
+ private static ThreadLocal source = new ThreadLocal();
+
+ private String beanName;
+
+ private XAConnectionFactory connectionFactory;
+
+ public PoolingConnectionFactoryBean() {
+ setMaxPoolSize(10);
+ setTestConnections(true);
+ setAutomaticEnlistingEnabled(true);
+ setAllowLocalTransactions(true);
+ }
+
+ @Override
+ public synchronized void init() {
+ source.set(this);
+ try {
+ super.init();
+ }
+ finally {
+ source.remove();
+ }
+ }
+
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (!StringUtils.hasLength(getUniqueName())) {
+ setUniqueName(this.beanName);
+ }
+ init();
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ close();
+ }
+
+ /**
+ * Set the {@link XAConnectionFactory} directly, instead of calling
+ * {@link #setClassName(String)}.
+ * @param connectionFactory the connection factory to use
+ */
+ public void setConnectionFactory(XAConnectionFactory connectionFactory) {
+ this.connectionFactory = connectionFactory;
+ setClassName(DirectXAConnectionFactory.class.getName());
+ setDriverProperties(new Properties());
+ }
+
+ protected final XAConnectionFactory getConnectionFactory() {
+ return this.connectionFactory;
+ }
+
+ @Override
+ public XAStatefulHolder createPooledConnection(Object xaFactory, ResourceBean bean)
+ throws Exception {
+ if (xaFactory instanceof DirectXAConnectionFactory) {
+ xaFactory = ((DirectXAConnectionFactory) xaFactory).getConnectionFactory();
+ }
+ return super.createPooledConnection(xaFactory, bean);
+ }
+
+ /**
+ * A {@link XAConnectionFactory} implementation that delegates to the
+ * {@link ThreadLocal} {@link PoolingConnectionFactoryBean}.
+ * @see PoolingConnectionFactoryBean#setConnectionFactory(XAConnectionFactory)
+ */
+ public static class DirectXAConnectionFactory implements XAConnectionFactory {
+
+ private final XAConnectionFactory connectionFactory;
+
+ public DirectXAConnectionFactory() {
+ this.connectionFactory = source.get().connectionFactory;
+ }
+
+ @Override
+ public XAConnection createXAConnection() throws JMSException {
+ return this.connectionFactory.createXAConnection();
+ }
+
+ @Override
+ public XAConnection createXAConnection(String userName, String password)
+ throws JMSException {
+ return this.connectionFactory.createXAConnection(userName, password);
+ }
+
+ public XAConnectionFactory getConnectionFactory() {
+ return this.connectionFactory;
+ }
+
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java
new file mode 100644
index 0000000000..c128ad8856
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBean.java
@@ -0,0 +1,161 @@
+/*
+ * 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.bitronix;
+
+import java.io.PrintWriter;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.StringUtils;
+
+import bitronix.tm.resource.common.ResourceBean;
+import bitronix.tm.resource.common.XAStatefulHolder;
+import bitronix.tm.resource.jdbc.PoolingDataSource;
+
+/**
+ * Spring friendly version of {@link PoolingDataSource}. Provides sensible defaults and
+ * also supports direct wrapping of a {@link XADataSource} instance.
+ *
+ * @author Phillip Webb
+ * @author Josh Long
+ * @since 1.2.0
+ */
+public class PoolingDataSourceBean extends PoolingDataSource implements BeanNameAware,
+ InitializingBean {
+
+ private static ThreadLocal source = new ThreadLocal();
+
+ private XADataSource dataSource;
+
+ private String beanName;
+
+ public PoolingDataSourceBean() {
+ super();
+ setMaxPoolSize(10);
+ setAllowLocalTransactions(true);
+ setEnableJdbc4ConnectionTest(true);
+ }
+
+ @Override
+ public synchronized void init() {
+ source.set(this);
+ try {
+ super.init();
+ }
+ finally {
+ source.remove();
+ }
+ }
+
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (!StringUtils.hasLength(getUniqueName())) {
+ setUniqueName(this.beanName);
+ }
+ }
+
+ /**
+ * Set the {@link XADataSource} directly, instead of calling
+ * {@link #setClassName(String)}.
+ * @param dataSource the data source to use
+ */
+ public void setDataSource(XADataSource dataSource) {
+ this.dataSource = dataSource;
+ setClassName(DirectXADataSource.class.getName());
+ setDriverProperties(new Properties());
+ }
+
+ protected final XADataSource getDataSource() {
+ return this.dataSource;
+ }
+
+ @Override
+ public XAStatefulHolder createPooledConnection(Object xaFactory, ResourceBean bean)
+ throws Exception {
+ if (xaFactory instanceof DirectXADataSource) {
+ xaFactory = ((DirectXADataSource) xaFactory).getDataSource();
+ }
+ return super.createPooledConnection(xaFactory, bean);
+ }
+
+ /**
+ * A {@link XADataSource} implementation that delegates to the {@link ThreadLocal}
+ * {@link PoolingDataSourceBean}.
+ * @see PoolingDataSourceBean#setDataSource(XADataSource)
+ */
+ public static class DirectXADataSource implements XADataSource {
+
+ private final XADataSource dataSource;
+
+ public DirectXADataSource() {
+ this.dataSource = source.get().dataSource;
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return this.dataSource.getLogWriter();
+ }
+
+ @Override
+ public XAConnection getXAConnection() throws SQLException {
+ return this.dataSource.getXAConnection();
+ }
+
+ @Override
+ public XAConnection getXAConnection(String user, String password)
+ throws SQLException {
+ return this.dataSource.getXAConnection(user, password);
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ this.dataSource.setLogWriter(out);
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ this.dataSource.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return this.dataSource.getLoginTimeout();
+ }
+
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ return this.dataSource.getParentLogger();
+ }
+
+ public XADataSource getDataSource() {
+ return this.dataSource;
+ }
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessorTests.java
new file mode 100644
index 0000000000..8090459f90
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixDependentBeanFactoryPostProcessorTests.java
@@ -0,0 +1,80 @@
+/*
+ * 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.bitronix;
+
+import javax.jms.ConnectionFactory;
+import javax.sql.DataSource;
+
+import org.junit.Test;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import bitronix.tm.BitronixTransactionManager;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link BitronixDependentBeanFactoryPostProcessor}.
+ *
+ * @author Phillip Webb
+ */
+public class BitronixDependentBeanFactoryPostProcessorTests {
+
+ private AnnotationConfigApplicationContext context;
+
+ @Test
+ public void setsDependsOn() {
+ DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory());
+ this.context = new AnnotationConfigApplicationContext(beanFactory);
+ this.context.register(Config.class);
+ this.context.refresh();
+ String name = "bitronixTransactionManager";
+ verify(beanFactory).registerDependentBean(name, "dataSource");
+ verify(beanFactory).registerDependentBean(name, "connectionFactory");
+ this.context.close();
+ }
+
+ @Configuration
+ static class Config {
+
+ @Bean
+ public DataSource dataSource() {
+ return mock(DataSource.class);
+ }
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ return mock(ConnectionFactory.class);
+ }
+
+ @Bean
+ public BitronixTransactionManager bitronixTransactionManager() {
+ return mock(BitronixTransactionManager.class);
+ }
+
+ @Bean
+ public static BitronixDependentBeanFactoryPostProcessor bitronixPostProcessor() {
+ return new BitronixDependentBeanFactoryPostProcessor();
+ }
+
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBeanTests.java
new file mode 100644
index 0000000000..fa0225ac79
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingConnectionFactoryBeanTests.java
@@ -0,0 +1,76 @@
+/*
+ * 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.bitronix;
+
+import javax.jms.XAConnectionFactory;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link PoolingConnectionFactoryBean}.
+ *
+ * @author Phillip Webb
+ */
+public class PoolingConnectionFactoryBeanTests {
+
+ private PoolingConnectionFactoryBean bean = new PoolingConnectionFactoryBean() {
+ @Override
+ public synchronized void init() {
+ // Stub out for the tests
+ };
+ };
+
+ @Test
+ public void sensbileDefaults() throws Exception {
+ assertThat(this.bean.getMaxPoolSize(), equalTo(10));
+ assertThat(this.bean.getTestConnections(), equalTo(true));
+ assertThat(this.bean.getAutomaticEnlistingEnabled(), equalTo(true));
+ assertThat(this.bean.getAllowLocalTransactions(), equalTo(true));
+ }
+
+ @Test
+ public void setsUniqueNameIfNull() throws Exception {
+ this.bean.setBeanName("beanName");
+ this.bean.afterPropertiesSet();
+ assertThat(this.bean.getUniqueName(), equalTo("beanName"));
+ }
+
+ @Test
+ public void doesNotSetUniqueNameIfNotNull() throws Exception {
+ this.bean.setBeanName("beanName");
+ this.bean.setUniqueName("un");
+ this.bean.afterPropertiesSet();
+ assertThat(this.bean.getUniqueName(), equalTo("un"));
+ }
+
+ @Test
+ public void setConnectionFactory() throws Exception {
+ XAConnectionFactory factory = mock(XAConnectionFactory.class);
+ this.bean.setConnectionFactory(factory);
+ this.bean.setBeanName("beanName");
+ this.bean.afterPropertiesSet();
+ this.bean.init();
+ this.bean.createPooledConnection(factory, this.bean);
+ verify(factory).createXAConnection();
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBeanTests.java
new file mode 100644
index 0000000000..16cfa9f734
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/PoolingDataSourceBeanTests.java
@@ -0,0 +1,78 @@
+/*
+ * 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.bitronix;
+
+import java.sql.Connection;
+
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link PoolingDataSourceBean}.
+ *
+ * @author Phillip Webb
+ */
+public class PoolingDataSourceBeanTests {
+
+ private PoolingDataSourceBean bean = new PoolingDataSourceBean();
+
+ @Test
+ public void sensbileDefaults() throws Exception {
+ assertThat(this.bean.getMaxPoolSize(), equalTo(10));
+ assertThat(this.bean.getAutomaticEnlistingEnabled(), equalTo(true));
+ assertThat(this.bean.isEnableJdbc4ConnectionTest(), equalTo(true));
+ }
+
+ @Test
+ public void setsUniqueNameIfNull() throws Exception {
+ this.bean.setBeanName("beanName");
+ this.bean.afterPropertiesSet();
+ assertThat(this.bean.getUniqueName(), equalTo("beanName"));
+ }
+
+ @Test
+ public void doesNotSetUniqueNameIfNotNull() throws Exception {
+ this.bean.setBeanName("beanName");
+ this.bean.setUniqueName("un");
+ this.bean.afterPropertiesSet();
+ assertThat(this.bean.getUniqueName(), equalTo("un"));
+ }
+
+ @Test
+ public void setDataSource() throws Exception {
+ XADataSource dataSource = mock(XADataSource.class);
+ XAConnection xaConnection = mock(XAConnection.class);
+ Connection connection = mock(Connection.class);
+ given(dataSource.getXAConnection()).willReturn(xaConnection);
+ given(xaConnection.getConnection()).willReturn(connection);
+ this.bean.setDataSource(dataSource);
+ this.bean.setBeanName("beanName");
+ this.bean.afterPropertiesSet();
+ this.bean.init();
+ this.bean.createPooledConnection(dataSource, this.bean);
+ verify(dataSource).getXAConnection();
+ }
+
+}