diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml
index 40e3303c5c..b36152d257 100644
--- a/spring-boot-autoconfigure/pom.xml
+++ b/spring-boot-autoconfigure/pom.xml
@@ -25,6 +25,16 @@
spring-boot
+
+ com.atomikos
+ transactions-jdbc
+ true
+
+
+ com.atomikos
+ transactions-jta
+ true
+
com.fasterxml.jackson.core
jackson-databind
@@ -80,6 +90,11 @@
velocity
true
+
+ org.codehaus.btm
+ btm
+ true
+
org.codehaus.groovy
groovy-templates
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndi.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndi.java
new file mode 100644
index 0000000000..8015c01749
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnJndi.java
@@ -0,0 +1,39 @@
+/*
+ * 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.autoconfigure.condition;
+
+import javax.naming.InitialContext;
+
+import org.springframework.context.annotation.Conditional;
+
+/**
+ * {@link Conditional} that matches based on the availability of a JNDI
+ * {@link InitialContext} and the ability to lookup specific locations.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Conditional(OnJndiCondition.class)
+public @interface ConditionalOnJndi {
+
+ /**
+ * JNDI Locations, one of which must exist. If no locations are specific the condition
+ * matches solely based on the presence of an {@link InitialContext}.
+ */
+ String[] value() default {};
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java
new file mode 100644
index 0000000000..3b85fb0dd1
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnJndiCondition.java
@@ -0,0 +1,101 @@
+/*
+ * 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.autoconfigure.condition;
+
+import javax.naming.NamingException;
+
+import org.springframework.context.annotation.Condition;
+import org.springframework.context.annotation.ConditionContext;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+import org.springframework.jndi.JndiLocatorDelegate;
+import org.springframework.jndi.JndiLocatorSupport;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Condition} that checks for JNDI locations.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ * @see ConditionalOnJndi
+ */
+class OnJndiCondition extends SpringBootCondition {
+
+ @Override
+ public ConditionOutcome getMatchOutcome(ConditionContext context,
+ AnnotatedTypeMetadata metadata) {
+ AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(metadata
+ .getAnnotationAttributes(ConditionalOnJndi.class.getName()));
+ String[] locations = annotationAttributes.getStringArray("value");
+ try {
+ return getMatchOutcome(locations);
+ }
+ catch (NoClassDefFoundError ex) {
+ return ConditionOutcome.noMatch("JNDI class not found");
+ }
+ }
+
+ private ConditionOutcome getMatchOutcome(String[] locations) {
+ if (!isJndiAvailable()) {
+ return ConditionOutcome.noMatch("JNDI environment is not available");
+ }
+ if (locations.length == 0) {
+ return ConditionOutcome.match("JNDI environment is available");
+ }
+ JndiLocator locator = getJndiLocator(locations);
+ String location = locator.lookupFirstLocation();
+ if (location != null) {
+ return ConditionOutcome.match("JNDI location '" + location
+ + "' found from candidates "
+ + StringUtils.arrayToCommaDelimitedString(locations));
+ }
+ return ConditionOutcome.noMatch("No JNDI location found from candidates "
+ + StringUtils.arrayToCommaDelimitedString(locations));
+ }
+
+ protected boolean isJndiAvailable() {
+ return JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable();
+ }
+
+ protected JndiLocator getJndiLocator(String[] locations) {
+ return new JndiLocator(locations);
+ }
+
+ protected static class JndiLocator extends JndiLocatorSupport {
+
+ private String[] locations;
+
+ public JndiLocator(String[] locations) {
+ this.locations = locations;
+ }
+
+ public String lookupFirstLocation() {
+ for (String location : this.locations) {
+ try {
+ lookup(location);
+ return location;
+ }
+ catch (NamingException ex) {
+ // Swallow and continue
+ }
+ }
+ return null;
+ }
+
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
index e07915bb24..5a68991a21 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java
@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.jdbc;
import javax.sql.DataSource;
+import javax.sql.XADataSource;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -74,7 +75,7 @@ public class DataSourceAutoConfiguration {
}
@Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class)
- @ConditionalOnMissingBean(DataSource.class)
+ @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedConfiguration {
@@ -92,7 +93,7 @@ public class DataSourceAutoConfiguration {
}
@Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)
- @ConditionalOnMissingBean(DataSource.class)
+ @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
protected static class NonEmbeddedConfiguration {
@Autowired
@@ -196,7 +197,8 @@ public class DataSourceAutoConfiguration {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
- if (hasBean(context, DataSource.class)) {
+ if (hasBean(context, DataSource.class)
+ || hasBean(context, XADataSource.class)) {
return ConditionOutcome
.match("existing bean configured database detected");
}
@@ -210,6 +212,7 @@ public class DataSourceAutoConfiguration {
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getBeanFactory(), type, true, false).length > 0;
}
+
}
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java
index cecc04a3c0..18d13e3f28 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBuilder.java
@@ -49,8 +49,6 @@ public class DataSourceBuilder {
private ClassLoader classLoader;
- private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider();
-
private Map properties = new HashMap();
public static DataSourceBuilder create() {
@@ -76,9 +74,9 @@ public class DataSourceBuilder {
private void maybeGetDriverClassName() {
if (!this.properties.containsKey("driverClassName")
&& this.properties.containsKey("url")) {
- String cls = this.driverClassNameProvider.getDriverClassName(this.properties
- .get("url"));
- this.properties.put("driverClassName", cls);
+ String url = this.properties.get("url");
+ String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
+ this.properties.put("driverClassName", driverClass);
}
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
index e497399285..fb8ee46c7e 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java
@@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.jdbc;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
@@ -64,7 +67,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;
- private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider();
+ private Xa xa = new Xa();
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
@@ -86,7 +89,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
String driverClassName = null;
if (StringUtils.hasText(this.url)) {
- driverClassName = this.driverClassNameProvider.getDriverClassName(this.url);
+ driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
}
if (!StringUtils.hasText(driverClassName)) {
@@ -113,7 +116,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
"Cannot determine embedded database url for database type "
+ this.embeddedDatabaseConnection
+ ". If you want an embedded "
- + "database please put a supported on on the classpath.");
+ + "database please put a supported one on the classpath.");
}
return url;
}
@@ -228,4 +231,39 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
return this.classLoader;
}
+ public Xa getXa() {
+ return this.xa;
+ }
+
+ public void setXa(Xa xa) {
+ this.xa = xa;
+ }
+
+ /**
+ * XA Specific datasource settings.
+ */
+ public static class Xa {
+
+ private String dataSourceClassName;
+
+ private Map properties = new LinkedHashMap();
+
+ public String getDataSourceClassName() {
+ return this.dataSourceClassName;
+ }
+
+ public void setDataSourceClassName(String dataSourceClassName) {
+ this.dataSourceClassName = dataSourceClassName;
+ }
+
+ public Map getProperties() {
+ return this.properties;
+ }
+
+ public void setProperties(Map properties) {
+ this.properties = properties;
+ }
+
+ }
+
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java
new file mode 100644
index 0000000000..026607762f
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriver.java
@@ -0,0 +1,137 @@
+/*
+ * 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.autoconfigure.jdbc;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Enumeration of common database drivers.
+ *
+ * @author Phillip Webb
+ * @author Maciej Walkowiak
+ * @since 1.2.0
+ */
+enum DatabaseDriver {
+
+ /**
+ * Unknown type.
+ */
+ UNKNOWN(null),
+
+ /**
+ * Apache Derby.
+ */
+ DERBY("org.apache.derby.jdbc.EmbeddedDriver"),
+
+ /**
+ * H2.
+ */
+ H2("org.h2.Driver", "org.h2.jdbcx.JdbcDataSource"),
+
+ /**
+ * HyperSQL DataBase.
+ */
+ HSQLDB("org.hsqldb.jdbc.JDBCDriver", "org.hsqldb.jdbc.pool.JDBCXADataSource"),
+
+ /**
+ * SQL Lite.
+ */
+ SQLITE("org.sqlite.JDBC"),
+
+ /**
+ * MySQL.
+ */
+ MYSQL("com.mysql.jdbc.Driver", "org.mysql.jdbc.MySQLDataSource"),
+
+ /**
+ * Maria DB.
+ */
+ MARIADB("org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MySQLDataSource"),
+
+ /**
+ * Google App Engine.
+ */
+ GOOGLE("com.google.appengine.api.rdbms.AppEngineDriver"),
+
+ /**
+ * Oracle
+ */
+ ORACLE("oracle.jdbc.OracleDriver", "oracle.jdbc.xa.OracleXADataSource"),
+
+ /**
+ * Postres
+ */
+ POSTGRESQL("org.postgresql.Driver", "org.postgresql.xa.PGXADataSource"),
+
+ /**
+ * JTDS
+ */
+ JTDS("net.sourceforge.jtds.jdbc.Driver"),
+
+ /**
+ * SQL Server
+ */
+ SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver");
+
+ private final String driverClassName;
+
+ private final String xaDataSourceClassName;
+
+ private DatabaseDriver(String driverClassName) {
+ this(driverClassName, null);
+ }
+
+ private DatabaseDriver(String driverClassName, String xaDataSourceClassName) {
+ this.driverClassName = driverClassName;
+ this.xaDataSourceClassName = xaDataSourceClassName;
+ }
+
+ /**
+ * @return the driverClassName or {@code null}
+ */
+ public String getDriverClassName() {
+ return this.driverClassName;
+ }
+
+ /**
+ * @return the xaDataSourceClassName or {@code null}
+ */
+ public String getXaDataSourceClassName() {
+ return this.xaDataSourceClassName;
+ }
+
+ /**
+ * Find a {@link DatabaseDriver} for the given URL.
+ * @param url JDBC URL
+ * @return driver class name or {@link #UNKNOWN} if not found
+ */
+ public static DatabaseDriver fromJdbcUrl(String url) {
+ if (StringUtils.hasLength(url)) {
+ Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'");
+ String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase();
+ for (DatabaseDriver driver : values()) {
+ String prefix = ":" + driver.name().toLowerCase() + ":";
+ if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
+ return driver;
+ }
+ }
+ }
+ return UNKNOWN;
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java
index 735ab89357..2308938aea 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.java
@@ -36,7 +36,8 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
* @since 1.2.0
*/
@Configuration
-@AutoConfigureBefore(DataSourceAutoConfiguration.class)
+@AutoConfigureBefore({ XADataSourceAutoConfiguration.class,
+ DataSourceAutoConfiguration.class })
@ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = DataSourceProperties.PREFIX, name = "jndi-name")
@EnableConfigurationProperties(DataSourceProperties.class)
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java
new file mode 100644
index 0000000000..1f5c00b844
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfiguration.java
@@ -0,0 +1,119 @@
+/*
+ * 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.autoconfigure.jdbc;
+
+import javax.sql.DataSource;
+import javax.sql.XADataSource;
+import javax.transaction.TransactionManager;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration;
+import org.springframework.boot.bind.RelaxedDataBinder;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jta.XADataSourceWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link DataSource} with XA.
+ *
+ * @author Phillip Webb
+ * @author Josh Long
+ * @since 1.2.0
+ */
+@AutoConfigureBefore(DataSourceAutoConfiguration.class)
+@AutoConfigureAfter(JtaAutoConfiguration.class)
+@EnableConfigurationProperties(DataSourceProperties.class)
+@ConditionalOnClass({ DataSource.class, TransactionManager.class })
+@ConditionalOnBean(XADataSourceWrapper.class)
+@ConditionalOnMissingBean(DataSource.class)
+public class XADataSourceAutoConfiguration implements BeanClassLoaderAware {
+
+ @Autowired
+ private XADataSourceWrapper wrapper;
+
+ @Autowired
+ private DataSourceProperties properties;
+
+ @Autowired(required = false)
+ private XADataSource xaDataSource;
+
+ private ClassLoader classLoader;
+
+ @Bean
+ @ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
+ public DataSource dataSource() throws Exception {
+ XADataSource xaDataSource = this.xaDataSource;
+ if (xaDataSource == null) {
+ xaDataSource = createXaDataSource();
+ }
+ return this.wrapper.wrapDataSource(xaDataSource);
+ }
+
+ @Override
+ public void setBeanClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ private XADataSource createXaDataSource() {
+ String className = this.properties.getXa().getDataSourceClassName();
+ if (!StringUtils.hasLength(className)) {
+ className = DatabaseDriver.fromJdbcUrl(this.properties.getUrl())
+ .getXaDataSourceClassName();
+ }
+ Assert.state(StringUtils.hasLength(className),
+ "No XA DataSource class name specified");
+ XADataSource dataSource = createXaDataSourceInstance(className);
+ bindXaProperties(dataSource, this.properties);
+ return dataSource;
+ }
+
+ private XADataSource createXaDataSourceInstance(String className) {
+ try {
+ Class> dataSourceClass = ClassUtils.forName(className, this.classLoader);
+ Object instance = BeanUtils.instantiate(dataSourceClass);
+ Assert.isInstanceOf(XADataSource.class, instance);
+ return (XADataSource) instance;
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(
+ "Unable to create XADataSource instance from '" + className + "'");
+ }
+ }
+
+ private void bindXaProperties(XADataSource target, DataSourceProperties properties) {
+ MutablePropertyValues values = new MutablePropertyValues();
+ values.add("user", this.properties.getUsername());
+ values.add("password", this.properties.getPassword());
+ values.add("url", this.properties.getUrl());
+ values.addPropertyValues(properties.getXa().getProperties());
+ new RelaxedDataBinder(target).withAlias("user", "username").bind(values);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java
index 92cc4599f2..2feb63e04c 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JmsAnnotationDrivenConfiguration.java
@@ -20,6 +20,7 @@ import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -29,6 +30,7 @@ import org.springframework.jms.annotation.JmsBootstrapConfiguration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerConfigUtils;
import org.springframework.jms.support.destination.DestinationResolver;
+import org.springframework.jms.support.destination.JndiDestinationResolver;
import org.springframework.transaction.PlatformTransactionManager;
/**
@@ -67,6 +69,18 @@ class JmsAnnotationDrivenConfiguration {
@EnableJms
@ConditionalOnMissingBean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
protected static class EnableJmsConfiguration {
+ }
+
+ @ConditionalOnJndi
+ protected static class JndiConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public DestinationResolver destinationResolver() {
+ JndiDestinationResolver resolver = new JndiDestinationResolver();
+ resolver.setFallbackToDynamicDestination(true);
+ return resolver;
+ }
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JndiConnectionFactoryAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JndiConnectionFactoryAutoConfiguration.java
new file mode 100644
index 0000000000..9bb79bc389
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/JndiConnectionFactoryAutoConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * 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.autoconfigure.jms;
+
+import javax.jms.ConnectionFactory;
+import javax.naming.NamingException;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jndi.JndiLocatorDelegate;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for JMS provided from JNDI.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@AutoConfigureBefore(JmsAutoConfiguration.class)
+@ConditionalOnMissingBean(ConnectionFactory.class)
+@ConditionalOnJndi("java:/JmsXA")
+public class JndiConnectionFactoryAutoConfiguration {
+
+ @Bean
+ public ConnectionFactory connectionFactory() throws NamingException {
+ return new JndiLocatorDelegate().lookup("java:/JmsXA", ConnectionFactory.class);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java
index d1f2b633d4..61fa5accac 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQAutoConfiguration.java
@@ -19,19 +19,16 @@ package org.springframework.boot.autoconfigure.jms.activemq;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
-import org.apache.activemq.transport.vm.VMTransportFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
-import org.springframework.context.annotation.ConditionContext;
-import org.springframework.context.annotation.Conditional;
+import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* {@link EnableAutoConfiguration Auto-configuration} to integrate with an ActiveMQ
@@ -39,67 +36,16 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
* embedded broker.
*
* @author Stephane Nicoll
+ * @author Phillip Webb
* @since 1.1.0
*/
@Configuration
@AutoConfigureBefore(JmsAutoConfiguration.class)
+@AutoConfigureAfter(JtaAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class })
@ConditionalOnMissingBean(ConnectionFactory.class)
+@EnableConfigurationProperties(ActiveMQProperties.class)
+@Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class })
public class ActiveMQAutoConfiguration {
- @Configuration
- @ConditionalOnClass(VMTransportFactory.class)
- @Conditional(EmbeddedBrokerCondition.class)
- @Import(ActiveMQConnectionFactoryConfiguration.class)
- protected static class EmbeddedBroker {
- }
-
- @Configuration
- @Conditional(NonEmbeddedBrokerCondition.class)
- @Import(ActiveMQConnectionFactoryConfiguration.class)
- protected static class NetworkBroker {
- }
-
- static abstract class BrokerTypeCondition extends SpringBootCondition {
- private final boolean embedded;
-
- BrokerTypeCondition(boolean embedded) {
- this.embedded = embedded;
- }
-
- @Override
- public ConditionOutcome getMatchOutcome(ConditionContext context,
- AnnotatedTypeMetadata metadata) {
- String brokerUrl = ActiveMQProperties.determineBrokerUrl(context
- .getEnvironment());
- boolean match = brokerUrl.contains("vm://");
- boolean outcome = (match == this.embedded);
- return new ConditionOutcome(outcome, buildMessage(brokerUrl, outcome));
- }
-
- protected String buildMessage(String brokerUrl, boolean outcome) {
- String brokerType = this.embedded ? "Embedded" : "Network";
- String detected = outcome ? "detected" : "not detected";
- return brokerType + " ActiveMQ broker " + detected + " - brokerUrl '"
- + brokerUrl + "'";
- }
-
- }
-
- static class EmbeddedBrokerCondition extends BrokerTypeCondition {
-
- EmbeddedBrokerCondition() {
- super(true);
- }
-
- }
-
- static class NonEmbeddedBrokerCondition extends BrokerTypeCondition {
-
- NonEmbeddedBrokerCondition() {
- super(false);
- }
-
- }
-
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java
index 65006e61c3..8e2c4ff9b4 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryConfiguration.java
@@ -18,27 +18,34 @@ package org.springframework.boot.autoconfigure.jms.activemq;
import javax.jms.ConnectionFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.activemq.pool.PooledConnectionFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
- * Creates a {@link ConnectionFactory} based on {@link ActiveMQProperties}.
+ * Configuration for ActiveMQ {@link ConnectionFactory}.
*
* @author Greg Turnquist
* @author Stephane Nicoll
+ * @author Phillip Webb
* @since 1.1.0
*/
@Configuration
-@EnableConfigurationProperties(ActiveMQProperties.class)
+@ConditionalOnMissingBean(ConnectionFactory.class)
class ActiveMQConnectionFactoryConfiguration {
- @Autowired
- private ActiveMQProperties properties;
-
@Bean
- public ConnectionFactory jmsConnectionFactory() {
- return this.properties.createConnectionFactory();
+ public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties) {
+ ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
+ properties).createConnectionFactory(ActiveMQConnectionFactory.class);
+ if (properties.isPooled()) {
+ PooledConnectionFactory pool = new PooledConnectionFactory();
+ pool.setConnectionFactory(connectionFactory);
+ return pool;
+ }
+ return connectionFactory;
}
+
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.java
new file mode 100644
index 0000000000..6d3f82074b
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQConnectionFactoryFactory.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.autoconfigure.jms.activemq;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Factory to create a {@link ActiveMQConnectionFactory} instance from properties defined
+ * in {@link ActiveMQProperties}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+class ActiveMQConnectionFactoryFactory {
+
+ private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
+
+ private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
+
+ private final ActiveMQProperties properties;
+
+ public ActiveMQConnectionFactoryFactory(ActiveMQProperties properties) {
+ Assert.notNull(properties, "Properties must not be null");
+ this.properties = properties;
+ }
+
+ public T createConnectionFactory(
+ Class factoryClass) {
+ try {
+ return doCreateConnectionFactory(factoryClass);
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Unable to create "
+ + "ActiveMQConnectionFactory", ex);
+ }
+ }
+
+ private T doCreateConnectionFactory(
+ Class factoryClass) throws Exception {
+ String brokerUrl = determineBrokerUrl();
+ String user = this.properties.getUser();
+ String password = this.properties.getPassword();
+ if (StringUtils.hasLength(user) && StringUtils.hasLength(password)) {
+ return factoryClass.getConstructor(String.class, String.class, String.class)
+ .newInstance(user, password, brokerUrl);
+ }
+ return factoryClass.getConstructor(String.class).newInstance(brokerUrl);
+ }
+
+ String determineBrokerUrl() {
+ if (this.properties.getBrokerUrl() != null) {
+ return this.properties.getBrokerUrl();
+ }
+ if (this.properties.isInMemory()) {
+ return DEFAULT_EMBEDDED_BROKER_URL;
+ }
+ return DEFAULT_NETWORK_BROKER_URL;
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java
index de3db1caf2..0709c08668 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQProperties.java
@@ -16,15 +16,7 @@
package org.springframework.boot.autoconfigure.jms.activemq;
-import javax.jms.ConnectionFactory;
-
-import org.apache.activemq.ActiveMQConnectionFactory;
-import org.apache.activemq.pool.PooledConnectionFactory;
-import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.core.env.Environment;
-import org.springframework.core.env.PropertyResolver;
-import org.springframework.util.StringUtils;
/**
* Configuration properties for ActiveMQ
@@ -35,10 +27,6 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties(prefix = "spring.activemq")
public class ActiveMQProperties {
- public static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
-
- public static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
-
private String brokerUrl;
private boolean inMemory = true;
@@ -93,52 +81,4 @@ public class ActiveMQProperties {
this.password = password;
}
- /**
- * Return a new {@link ConnectionFactory} from these properties.
- */
- public ConnectionFactory createConnectionFactory() {
- ConnectionFactory connectionFactory = createActiveMQConnectionFactory();
- if (isPooled()) {
- PooledConnectionFactory pool = new PooledConnectionFactory();
- pool.setConnectionFactory(connectionFactory);
- return pool;
- }
- return connectionFactory;
- }
-
- private ConnectionFactory createActiveMQConnectionFactory() {
- String brokerUrl = determineBrokerUrl();
- if (StringUtils.hasLength(this.user) && StringUtils.hasLength(this.password)) {
- return new ActiveMQConnectionFactory(this.user, this.password, brokerUrl);
- }
- return new ActiveMQConnectionFactory(brokerUrl);
- }
-
- String determineBrokerUrl() {
- return determineBrokerUrl(this.brokerUrl, this.inMemory);
- }
-
- /**
- * Determine the broker url to use for the specified {@link Environment}. If no broker
- * url is specified through configuration, a default broker is provided, that is
- * {@value #DEFAULT_EMBEDDED_BROKER_URL} if the {@code inMemory} flag is {@code null}
- * or {@code true}, {@value #DEFAULT_NETWORK_BROKER_URL} otherwise.
- * @param environment the environment to extract configuration from
- * @return the broker url to use
- */
- public static String determineBrokerUrl(Environment environment) {
- PropertyResolver resolver = new RelaxedPropertyResolver(environment,
- "spring.activemq.");
- String brokerUrl = resolver.getProperty("brokerUrl");
- Boolean inMemory = resolver.getProperty("inMemory", Boolean.class);
- return determineBrokerUrl(brokerUrl, inMemory);
- }
-
- private static String determineBrokerUrl(String brokerUrl, Boolean inMemory) {
- if (brokerUrl != null) {
- return brokerUrl;
- }
- boolean embedded = inMemory == null || inMemory;
- return (embedded ? DEFAULT_EMBEDDED_BROKER_URL : DEFAULT_NETWORK_BROKER_URL);
- }
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java
new file mode 100644
index 0000000000..a30fd80e61
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQXAConnectionFactoryConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * 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.autoconfigure.jms.activemq;
+
+import javax.jms.ConnectionFactory;
+import javax.transaction.TransactionManager;
+
+import org.apache.activemq.ActiveMQXAConnectionFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configuration for ActiveMQ XA {@link ConnectionFactory}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnClass(TransactionManager.class)
+@ConditionalOnBean(XAConnectionFactoryWrapper.class)
+@ConditionalOnMissingBean(ConnectionFactory.class)
+class ActiveMQXAConnectionFactoryConfiguration {
+
+ @Bean
+ public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties,
+ XAConnectionFactoryWrapper wrapper) throws Exception {
+ ActiveMQXAConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
+ properties).createConnectionFactory(ActiveMQXAConnectionFactory.class);
+ return wrapper.wrapConnectionFactory(connectionFactory);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfiguration.java
index 7db9103fa3..8e86dea97a 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfiguration.java
@@ -16,48 +16,25 @@
package org.springframework.boot.autoconfigure.jms.hornetq;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
import javax.jms.ConnectionFactory;
-import org.hornetq.api.core.TransportConfiguration;
-import org.hornetq.api.core.client.HornetQClient;
-import org.hornetq.api.core.client.ServerLocator;
import org.hornetq.api.jms.HornetQJMSClient;
-import org.hornetq.api.jms.JMSFactoryType;
-import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory;
-import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
-import org.hornetq.core.remoting.impl.netty.TransportConstants;
-import org.hornetq.jms.client.HornetQConnectionFactory;
-import org.hornetq.jms.server.config.JMSConfiguration;
-import org.hornetq.jms.server.config.JMSQueueConfiguration;
-import org.hornetq.jms.server.config.TopicConfiguration;
-import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
-import org.hornetq.jms.server.config.impl.JMSQueueConfigurationImpl;
-import org.hornetq.jms.server.config.impl.TopicConfigurationImpl;
-import org.hornetq.jms.server.embedded.EmbeddedJMS;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.util.ClassUtils;
+import org.springframework.context.annotation.Import;
/**
- * {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
- * Auto-configuration} to integrate with an HornetQ broker. If the necessary classes are
- * present, embed the broker in the application by default. Otherwise, connect to a broker
- * available on the local machine with the default settings.
+ * {@link EnableAutoConfiguration Auto-configuration} to integrate with an HornetQ broker.
+ * If the necessary classes are present, embed the broker in the application by default.
+ * Otherwise, connect to a broker available on the local machine with the default
+ * settings.
*
* @author Stephane Nicoll
+ * @author Phillip Webb
* @since 1.1.0
* @see HornetQProperties
*/
@@ -65,151 +42,9 @@ import org.springframework.util.ClassUtils;
@AutoConfigureBefore(JmsAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, HornetQJMSClient.class })
@EnableConfigurationProperties(HornetQProperties.class)
+@Import({ HornetQEmbeddedServerConfiguration.class,
+ HornetQXAConnectionFactoryConfiguration.class,
+ HornetQConnectionFactoryConfiguration.class })
public class HornetQAutoConfiguration {
- private static final String EMBEDDED_JMS_CLASS = "org.hornetq.jms.server.embedded.EmbeddedJMS";
-
- @Autowired
- private HornetQProperties properties;
-
- /**
- * Create the {@link ConnectionFactory} to use if none is provided. If no
- * {@linkplain HornetQProperties#getMode() mode} has been explicitly set, start an
- * embedded server unless it has been explicitly disabled, connect to a broker
- * available on the local machine with the default settings otherwise.
- */
- @Bean
- @ConditionalOnMissingBean
- public ConnectionFactory jmsConnectionFactory() {
- HornetQMode mode = this.properties.getMode();
- if (mode == null) {
- mode = deduceMode();
- }
- if (mode == HornetQMode.EMBEDDED) {
- return createEmbeddedConnectionFactory();
- }
- return createNativeConnectionFactory();
- }
-
- /**
- * Deduce the {@link HornetQMode} to use if none has been set.
- */
- private HornetQMode deduceMode() {
- if (this.properties.getEmbedded().isEnabled()
- && ClassUtils.isPresent(EMBEDDED_JMS_CLASS, null)) {
- return HornetQMode.EMBEDDED;
- }
- return HornetQMode.NATIVE;
- }
-
- private ConnectionFactory createEmbeddedConnectionFactory() {
- try {
- TransportConfiguration transportConfiguration = new TransportConfiguration(
- InVMConnectorFactory.class.getName(), this.properties.getEmbedded()
- .generateTransportParameters());
- ServerLocator serviceLocator = HornetQClient
- .createServerLocatorWithoutHA(transportConfiguration);
- return new HornetQConnectionFactory(serviceLocator);
- }
- catch (NoClassDefFoundError ex) {
- throw new IllegalStateException("Unable to create InVM "
- + "HornetQ connection, ensure that hornet-jms-server.jar "
- + "is in the classpath", ex);
- }
- }
-
- private ConnectionFactory createNativeConnectionFactory() {
- Map params = new HashMap();
- params.put(TransportConstants.HOST_PROP_NAME, this.properties.getHost());
- params.put(TransportConstants.PORT_PROP_NAME, this.properties.getPort());
- TransportConfiguration transportConfiguration = new TransportConfiguration(
- NettyConnectorFactory.class.getName(), params);
- return HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF,
- transportConfiguration);
- }
-
- /**
- * Configuration used to create the embedded HornetQ server.
- */
- @Configuration
- @ConditionalOnClass(name = EMBEDDED_JMS_CLASS)
- @ConditionalOnExpression("${spring.hornetq.embedded.enabled:true}")
- static class EmbeddedServerConfiguration {
-
- @Autowired
- private HornetQProperties properties;
-
- @Autowired(required = false)
- private List configurationCustomizers;
-
- @Autowired(required = false)
- private List queuesConfiguration;
-
- @Autowired(required = false)
- private List topicsConfiguration;
-
- @Bean
- @ConditionalOnMissingBean
- public org.hornetq.core.config.Configuration hornetQConfiguration() {
- return new HornetQEmbeddedConfigurationFactory(this.properties)
- .createConfiguration();
- }
-
- @Bean(initMethod = "start", destroyMethod = "stop")
- @ConditionalOnMissingBean
- public EmbeddedJMS hornetQServer(
- org.hornetq.core.config.Configuration configuration,
- JMSConfiguration jmsConfiguration) {
- EmbeddedJMS server = new EmbeddedJMS();
- customize(configuration);
- server.setConfiguration(configuration);
- server.setJmsConfiguration(jmsConfiguration);
- server.setRegistry(new HornetQNoOpBindingRegistry());
- return server;
- }
-
- private void customize(org.hornetq.core.config.Configuration configuration) {
- if (this.configurationCustomizers != null) {
- AnnotationAwareOrderComparator.sort(this.configurationCustomizers);
- for (HornetQConfigurationCustomizer customizer : this.configurationCustomizers) {
- customizer.customize(configuration);
- }
- }
- }
-
- @Bean
- @ConditionalOnMissingBean
- public JMSConfiguration hornetQJmsConfiguration() {
- JMSConfiguration configuration = new JMSConfigurationImpl();
- addAll(configuration.getQueueConfigurations(), this.queuesConfiguration);
- addAll(configuration.getTopicConfigurations(), this.topicsConfiguration);
- addQueues(configuration, this.properties.getEmbedded().getQueues());
- addTopics(configuration, this.properties.getEmbedded().getTopics());
- return configuration;
- }
-
- private void addAll(List list, Collection extends T> items) {
- if (items != null) {
- list.addAll(items);
- }
- }
-
- private void addQueues(JMSConfiguration configuration, String[] queues) {
- boolean persistent = this.properties.getEmbedded().isPersistent();
- for (String queue : queues) {
- configuration.getQueueConfigurations().add(
- new JMSQueueConfigurationImpl(queue, null, persistent, "/queue/"
- + queue));
- }
- }
-
- private void addTopics(JMSConfiguration configuration, String[] topics) {
- for (String topic : topics) {
- configuration.getTopicConfigurations().add(
- new TopicConfigurationImpl(topic, "/topic/" + topic));
- }
- }
-
- }
-
}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryConfiguration.java
new file mode 100644
index 0000000000..64e361c30d
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * 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.autoconfigure.jms.hornetq;
+
+import javax.jms.ConnectionFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hornetq.jms.client.HornetQConnectionFactory;
+import org.hornetq.jms.server.embedded.EmbeddedJMS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configuration for HornetQ {@link ConnectionFactory}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnMissingBean(ConnectionFactory.class)
+class HornetQConnectionFactoryConfiguration {
+
+ private static Log logger = LogFactory
+ .getLog(HornetQEmbeddedServerConfiguration.class);
+
+ // Ensure JMS is setup before XA
+ @Autowired(required = false)
+ private EmbeddedJMS embeddedJMS;
+
+ @Bean
+ public ConnectionFactory jmsConnectionFactory(HornetQProperties properties) {
+ if (this.embeddedJMS != null && logger.isDebugEnabled()) {
+ logger.debug("Using embdedded HornetQ broker");
+ }
+ return new HornetQConnectionFactoryFactory(properties)
+ .createConnectionFactory(HornetQConnectionFactory.class);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryFactory.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryFactory.java
new file mode 100644
index 0000000000..496c73536a
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQConnectionFactoryFactory.java
@@ -0,0 +1,117 @@
+/*
+ * 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.autoconfigure.jms.hornetq;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.hornetq.api.core.TransportConfiguration;
+import org.hornetq.api.core.client.HornetQClient;
+import org.hornetq.api.core.client.ServerLocator;
+import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory;
+import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
+import org.hornetq.core.remoting.impl.netty.TransportConstants;
+import org.hornetq.jms.client.HornetQConnectionFactory;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Factory to create a {@link HornetQConnectionFactory} instance from properties defined
+ * in {@link HornetQProperties}.
+ *
+ * @author Phillip Webb
+ * @author Stephane Nicoll
+ * @since 1.2.0
+ */
+class HornetQConnectionFactoryFactory {
+
+ static final String EMBEDDED_JMS_CLASS = "org.hornetq.jms.server.embedded.EmbeddedJMS";
+
+ private final HornetQProperties properties;
+
+ public HornetQConnectionFactoryFactory(HornetQProperties properties) {
+ Assert.notNull(properties, "Properties must not be null");
+ this.properties = properties;
+ }
+
+ public T createConnectionFactory(
+ Class factoryClass) {
+ try {
+ return doCreateConnectionFactory(factoryClass);
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Unable to create "
+ + "HornetQConnectionFactory", ex);
+ }
+ }
+
+ private T doCreateConnectionFactory(
+ Class factoryClass) throws Exception {
+ HornetQMode mode = this.properties.getMode();
+ if (mode == null) {
+ mode = deduceMode();
+ }
+ if (mode == HornetQMode.EMBEDDED) {
+ return createEmbeddedConnectionFactory(factoryClass);
+ }
+ return createNativeConnectionFactory(factoryClass);
+ }
+
+ /**
+ * Deduce the {@link HornetQMode} to use if none has been set.
+ */
+ private HornetQMode deduceMode() {
+ if (this.properties.getEmbedded().isEnabled()
+ && ClassUtils.isPresent(EMBEDDED_JMS_CLASS, null)) {
+ return HornetQMode.EMBEDDED;
+ }
+ return HornetQMode.NATIVE;
+ }
+
+ private T createEmbeddedConnectionFactory(
+ Class factoryClass) throws Exception {
+ try {
+ TransportConfiguration transportConfiguration = new TransportConfiguration(
+ InVMConnectorFactory.class.getName(), this.properties.getEmbedded()
+ .generateTransportParameters());
+ ServerLocator serviceLocator = HornetQClient
+ .createServerLocatorWithoutHA(transportConfiguration);
+ return factoryClass.getConstructor(ServerLocator.class).newInstance(
+ serviceLocator);
+ }
+ catch (NoClassDefFoundError ex) {
+ throw new IllegalStateException("Unable to create InVM "
+ + "HornetQ connection, ensure that hornet-jms-server.jar "
+ + "is in the classpath", ex);
+ }
+ }
+
+ private T createNativeConnectionFactory(
+ Class factoryClass) throws Exception {
+ Map params = new HashMap();
+ params.put(TransportConstants.HOST_PROP_NAME, this.properties.getHost());
+ params.put(TransportConstants.PORT_PROP_NAME, this.properties.getPort());
+ TransportConfiguration transportConfiguration = new TransportConfiguration(
+ NettyConnectorFactory.class.getName(), params);
+ Constructor constructor = factoryClass.getConstructor(boolean.class,
+ TransportConfiguration[].class);
+ return constructor.newInstance(false,
+ new TransportConfiguration[] { transportConfiguration });
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQEmbeddedServerConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQEmbeddedServerConfiguration.java
new file mode 100644
index 0000000000..2a82e9b1df
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQEmbeddedServerConfiguration.java
@@ -0,0 +1,122 @@
+/*
+ * 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.autoconfigure.jms.hornetq;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.hornetq.jms.server.config.JMSConfiguration;
+import org.hornetq.jms.server.config.JMSQueueConfiguration;
+import org.hornetq.jms.server.config.TopicConfiguration;
+import org.hornetq.jms.server.config.impl.JMSConfigurationImpl;
+import org.hornetq.jms.server.config.impl.JMSQueueConfigurationImpl;
+import org.hornetq.jms.server.config.impl.TopicConfigurationImpl;
+import org.hornetq.jms.server.embedded.EmbeddedJMS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+
+/**
+ * Configuration used to create the embedded HornetQ server.
+ *
+ * @author Phillip Webb
+ * @author Stephane Nicoll
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnClass(name = HornetQConnectionFactoryFactory.EMBEDDED_JMS_CLASS)
+@ConditionalOnProperty(prefix = "spring.hornetq.embedded", name = "enabled", havingValue = "true", matchIfMissing = true)
+class HornetQEmbeddedServerConfiguration {
+
+ @Autowired
+ private HornetQProperties properties;
+
+ @Autowired(required = false)
+ private List configurationCustomizers;
+
+ @Autowired(required = false)
+ private List queuesConfiguration;
+
+ @Autowired(required = false)
+ private List topicsConfiguration;
+
+ @Bean
+ @ConditionalOnMissingBean
+ public org.hornetq.core.config.Configuration hornetQConfiguration() {
+ return new HornetQEmbeddedConfigurationFactory(this.properties)
+ .createConfiguration();
+ }
+
+ @Bean(initMethod = "start", destroyMethod = "stop")
+ @ConditionalOnMissingBean
+ public EmbeddedJMS hornetQServer(org.hornetq.core.config.Configuration configuration,
+ JMSConfiguration jmsConfiguration) {
+ EmbeddedJMS server = new EmbeddedJMS();
+ customize(configuration);
+ server.setConfiguration(configuration);
+ server.setJmsConfiguration(jmsConfiguration);
+ server.setRegistry(new HornetQNoOpBindingRegistry());
+ return server;
+ }
+
+ private void customize(org.hornetq.core.config.Configuration configuration) {
+ if (this.configurationCustomizers != null) {
+ AnnotationAwareOrderComparator.sort(this.configurationCustomizers);
+ for (HornetQConfigurationCustomizer customizer : this.configurationCustomizers) {
+ customizer.customize(configuration);
+ }
+ }
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public JMSConfiguration hornetQJmsConfiguration() {
+ JMSConfiguration configuration = new JMSConfigurationImpl();
+ addAll(configuration.getQueueConfigurations(), this.queuesConfiguration);
+ addAll(configuration.getTopicConfigurations(), this.topicsConfiguration);
+ addQueues(configuration, this.properties.getEmbedded().getQueues());
+ addTopics(configuration, this.properties.getEmbedded().getTopics());
+ return configuration;
+ }
+
+ private void addAll(List list, Collection extends T> items) {
+ if (items != null) {
+ list.addAll(items);
+ }
+ }
+
+ private void addQueues(JMSConfiguration configuration, String[] queues) {
+ boolean persistent = this.properties.getEmbedded().isPersistent();
+ for (String queue : queues) {
+ configuration.getQueueConfigurations().add(
+ new JMSQueueConfigurationImpl(queue, null, persistent, "/queue/"
+ + queue));
+ }
+ }
+
+ private void addTopics(JMSConfiguration configuration, String[] topics) {
+ for (String topic : topics) {
+ configuration.getTopicConfigurations().add(
+ new TopicConfigurationImpl(topic, "/topic/" + topic));
+ }
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQXAConnectionFactoryConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQXAConnectionFactoryConfiguration.java
new file mode 100644
index 0000000000..ae7d5b1b6a
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQXAConnectionFactoryConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * 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.autoconfigure.jms.hornetq;
+
+import javax.jms.ConnectionFactory;
+import javax.transaction.TransactionManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hornetq.jms.client.HornetQXAConnectionFactory;
+import org.hornetq.jms.server.embedded.EmbeddedJMS;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Configuration for HornetQ XA {@link ConnectionFactory}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnMissingBean(ConnectionFactory.class)
+@ConditionalOnClass(TransactionManager.class)
+@ConditionalOnBean(XAConnectionFactoryWrapper.class)
+class HornetQXAConnectionFactoryConfiguration {
+
+ private static Log logger = LogFactory
+ .getLog(HornetQEmbeddedServerConfiguration.class);
+
+ // Ensure JMS is setup before XA
+ @Autowired(required = false)
+ private EmbeddedJMS embeddedJMS;
+
+ @Bean
+ public ConnectionFactory jmsConnectionFactory(HornetQProperties properties,
+ XAConnectionFactoryWrapper wrapper) throws Exception {
+ if (this.embeddedJMS != null && logger.isDebugEnabled()) {
+ logger.debug("Using embdedded HornetQ broker with XA");
+ }
+ return wrapper.wrapConnectionFactory(new HornetQConnectionFactoryFactory(
+ properties).createConnectionFactory(HornetQXAConnectionFactory.class));
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java
new file mode 100644
index 0000000000..65ee8083f5
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/AtomikosJtaConfiguration.java
@@ -0,0 +1,120 @@
+/*
+ * 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.autoconfigure.jta;
+
+import java.io.File;
+import java.util.Properties;
+
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationHome;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+import org.springframework.boot.jta.XADataSourceWrapper;
+import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor;
+import org.springframework.boot.jta.atomikos.AtomikosProperties;
+import org.springframework.boot.jta.atomikos.AtomikosXAConnectionFactoryWrapper;
+import org.springframework.boot.jta.atomikos.AtomikosXADataSourceWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.jta.JtaTransactionManager;
+import org.springframework.util.StringUtils;
+
+import com.atomikos.icatch.config.UserTransactionService;
+import com.atomikos.icatch.config.UserTransactionServiceImp;
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+/**
+ * JTA Configuration for Atomikos.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnClass(UserTransactionManager.class)
+@ConditionalOnMissingBean(PlatformTransactionManager.class)
+class AtomikosJtaConfiguration {
+
+ @Autowired
+ private JtaProperties jtaProperties;
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConfigurationProperties(prefix = JtaProperties.PREFIX)
+ public AtomikosProperties atomikosProperties() {
+ return new AtomikosProperties();
+ }
+
+ @Bean(initMethod = "init", destroyMethod = "shutdownForce")
+ @ConditionalOnMissingBean
+ public UserTransactionService userTransactionService(
+ AtomikosProperties atomikosProperties) {
+ Properties properties = new Properties();
+ properties.setProperty("com.atomikos.icatch.log_base_dir", getLogBaseDir());
+ properties.putAll(atomikosProperties.asProperties());
+ return new UserTransactionServiceImp(properties);
+ }
+
+ private String getLogBaseDir() {
+ if (StringUtils.hasLength(this.jtaProperties.getLogDir())) {
+ return this.jtaProperties.getLogDir();
+ }
+ File home = new ApplicationHome().getDir();
+ return new File(home, "transaction-logs").getAbsolutePath();
+ }
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ @ConditionalOnMissingBean
+ public UserTransactionManager atomikosTransactionManager(
+ UserTransactionService userTransactionService) throws Exception {
+ UserTransactionManager manager = new UserTransactionManager();
+ manager.setStartupTransactionService(false);
+ manager.setForceShutdown(true);
+ return manager;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public XADataSourceWrapper xaDataSourceWrapper() {
+ return new AtomikosXADataSourceWrapper();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public XAConnectionFactoryWrapper xaConnectionFactoryWrapper() {
+ return new AtomikosXAConnectionFactoryWrapper();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public static AtomikosDependsOnBeanFactoryPostProcessor atomikosDependsOnBeanFactoryPostProcessor() {
+ return new AtomikosDependsOnBeanFactoryPostProcessor();
+ }
+
+ @Bean
+ public JtaTransactionManager transactionManager(UserTransaction userTransaction,
+ TransactionManager transactionManager) {
+ return new JtaTransactionManager(userTransaction, transactionManager);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java
new file mode 100644
index 0000000000..6a3407a5e4
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/BitronixJtaConfiguration.java
@@ -0,0 +1,109 @@
+/*
+ * 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.autoconfigure.jta;
+
+import java.io.File;
+
+import javax.transaction.TransactionManager;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationHome;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+import org.springframework.boot.jta.XADataSourceWrapper;
+import org.springframework.boot.jta.bitronix.BitronixDependentBeanFactoryPostProcessor;
+import org.springframework.boot.jta.bitronix.BitronixXAConnectionFactoryWrapper;
+import org.springframework.boot.jta.bitronix.BitronixXADataSourceWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.jta.JtaTransactionManager;
+import org.springframework.util.StringUtils;
+
+import bitronix.tm.TransactionManagerServices;
+import bitronix.tm.jndi.BitronixContext;
+
+/**
+ * JTA Configuration for Bitronix.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnClass(BitronixContext.class)
+@ConditionalOnMissingBean(PlatformTransactionManager.class)
+class BitronixJtaConfiguration {
+
+ @Autowired
+ private JtaProperties jtaProperties;
+
+ @Bean
+ @ConditionalOnMissingBean
+ @ConfigurationProperties(prefix = JtaProperties.PREFIX)
+ public bitronix.tm.Configuration bitronixConfiguration(JtaProperties xxx) {
+ bitronix.tm.Configuration config = TransactionManagerServices.getConfiguration();
+ config.setServerId("spring-boot-jta-bitronix");
+ File logBaseDir = getLogBaseDir();
+ config.setLogPart1Filename(new File(logBaseDir, "part1.btm").getAbsolutePath());
+ config.setLogPart2Filename(new File(logBaseDir, "part2.btm").getAbsolutePath());
+ config.setDisableJmx(true);
+ return config;
+ }
+
+ private File getLogBaseDir() {
+ if (StringUtils.hasLength(this.jtaProperties.getLogDir())) {
+ return new File(this.jtaProperties.getLogDir());
+ }
+ File home = new ApplicationHome().getDir();
+ return new File(home, "transaction-logs");
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public TransactionManager bitronixTransactionManager(
+ bitronix.tm.Configuration configuration) {
+ // Inject configuration to force ordering
+ return TransactionManagerServices.getTransactionManager();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public XADataSourceWrapper xaDataSourceWrapper() {
+ return new BitronixXADataSourceWrapper();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public XAConnectionFactoryWrapper xaConnectionFactoryWrapper() {
+ return new BitronixXAConnectionFactoryWrapper();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public static BitronixDependentBeanFactoryPostProcessor atomikosDependsOnBeanFactoryPostProcessor() {
+ return new BitronixDependentBeanFactoryPostProcessor();
+ }
+
+ @Bean
+ public JtaTransactionManager transactionManager(TransactionManager transactionManager) {
+ return new JtaTransactionManager(transactionManager);
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JndiJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JndiJtaConfiguration.java
new file mode 100644
index 0000000000..92f7558942
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JndiJtaConfiguration.java
@@ -0,0 +1,44 @@
+/*
+ * 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.autoconfigure.jta;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+/**
+ * JTA Configuration for a JDNI managed {@link JtaTransactionManager}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@Configuration
+@ConditionalOnJndi({ JtaTransactionManager.DEFAULT_USER_TRANSACTION_NAME,
+ "java:comp/TransactionManager", "java:appserver/TransactionManager",
+ "java:pm/TransactionManager", "java:/TransactionManager" })
+@ConditionalOnMissingBean(PlatformTransactionManager.class)
+class JndiJtaConfiguration {
+
+ @Bean
+ public JtaTransactionManager transactionManager() {
+ return new JtaTransactionManager();
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfiguration.java
new file mode 100644
index 0000000000..4a0742d1bc
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfiguration.java
@@ -0,0 +1,37 @@
+/*
+ * 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.autoconfigure.jta;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Import;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for JTA.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@ConditionalOnClass(javax.transaction.Transaction.class)
+@Import({ JndiJtaConfiguration.class, BitronixJtaConfiguration.class,
+ AtomikosJtaConfiguration.class })
+@EnableConfigurationProperties(JtaProperties.class)
+public class JtaAutoConfiguration {
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java
new file mode 100644
index 0000000000..02fda641e9
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jta/JtaProperties.java
@@ -0,0 +1,46 @@
+/*
+ * 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.autoconfigure.jta;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+/**
+ * External configuration properties for a {@link JtaTransactionManager} created by
+ * Spring. All {@literal spring.jta.} properties are also applied to the appropriate
+ * vendor specific configuration.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+@ConfigurationProperties(prefix = JtaProperties.PREFIX, ignoreUnknownFields = true)
+public class JtaProperties {
+
+ public static final String PREFIX = "spring.jta";
+
+ private String logDir;
+
+ public void setLogDir(String logDir) {
+ this.logDir = logDir;
+ }
+
+ public String getLogDir() {
+ return this.logDir;
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java
index 1432c27d0f..af710e687d 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/EntityManagerFactoryBuilder.java
@@ -89,6 +89,8 @@ public class EntityManagerFactoryBuilder {
private Map properties = new HashMap();
+ private boolean jta;
+
private Builder(DataSource dataSource) {
this.dataSource = dataSource;
}
@@ -142,6 +144,21 @@ public class EntityManagerFactoryBuilder {
return this;
}
+ /**
+ * Configure if using a JTA {@link DataSource}, i.e. if
+ * {@link LocalContainerEntityManagerFactoryBean#setDataSource(DataSource)
+ * setDataSource} or
+ * {@link LocalContainerEntityManagerFactoryBean#setJtaDataSource(DataSource)
+ * setJtaDataSource} should be called on the
+ * {@link LocalContainerEntityManagerFactoryBean}.
+ * @param jta if the data source is JTA
+ * @return the builder for fluent usage
+ */
+ public Builder jta(boolean jta) {
+ this.jta = jta;
+ return this;
+ }
+
public LocalContainerEntityManagerFactoryBean build() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) {
@@ -153,7 +170,14 @@ public class EntityManagerFactoryBuilder {
}
entityManagerFactoryBean
.setJpaVendorAdapter(EntityManagerFactoryBuilder.this.jpaVendorAdapter);
- entityManagerFactoryBean.setDataSource(this.dataSource);
+
+ if (this.jta) {
+ entityManagerFactoryBean.setJtaDataSource(this.dataSource);
+ }
+ else {
+ entityManagerFactoryBean.setDataSource(this.dataSource);
+ }
+
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
entityManagerFactoryBean.getJpaPropertyMap().putAll(
EntityManagerFactoryBuilder.this.properties.getProperties());
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java
index 2cb64bcbd8..979ca1fa0b 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java
@@ -22,6 +22,7 @@ import java.util.Map;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
+import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -29,8 +30,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.HibernateEntityManagerCondition;
-import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@@ -39,29 +41,30 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA.
*
* @author Phillip Webb
+ * @author Josh Long
*/
@Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
EnableTransactionManagement.class, EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class)
-@AutoConfigureAfter(DataSourceAutoConfiguration.class)
+@AutoConfigureAfter({ DataSourceAutoConfiguration.class, JtaAutoConfiguration.class })
public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
+ private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";
+
@Autowired
private JpaProperties properties;
@Autowired
private DataSource dataSource;
- @Autowired
- private ConfigurableApplicationContext applicationContext;
-
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
@@ -74,6 +77,21 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
return vendorProperties;
}
+ @Override
+ protected void customizeVendorProperties(Map vendorProperties) {
+ super.customizeVendorProperties(vendorProperties);
+ if (!vendorProperties.containsKey(JTA_PLATFORM)) {
+ JtaTransactionManager jtaTransactionManager = getJtaTransactionManager();
+ if (jtaTransactionManager != null) {
+ vendorProperties.put(JTA_PLATFORM, new SpringJtaPlatform(
+ jtaTransactionManager));
+ }
+ else {
+ vendorProperties.put(JTA_PLATFORM, NoJtaPlatform.INSTANCE);
+ }
+ }
+ }
+
static class HibernateEntityManagerCondition extends SpringBootCondition {
private static String[] CLASS_NAMES = {
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java
index 0e4c3497a7..c0935824df 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java
@@ -44,6 +44,7 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@@ -71,6 +72,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@Autowired
private JpaProperties jpaProperties;
+ @Autowired(required = false)
+ private JtaTransactionManager jtaTransactionManager;
+
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager() {
@@ -103,14 +107,24 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder factoryBuilder) {
+ Map vendorProperties = getVendorProperties();
+ customizeVendorProperties(vendorProperties);
return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan())
- .properties(getVendorProperties()).build();
+ .properties(vendorProperties).jta(isJta()).build();
}
protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
protected abstract Map getVendorProperties();
+ /**
+ * Customize vendor properties before they are used. Allows for post processing (for
+ * example to configure JTA specific settings).
+ * @param vendorProperties the vendor properties to customize
+ */
+ protected void customizeVendorProperties(Map vendorProperties) {
+ }
+
protected EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback() {
return null;
}
@@ -127,6 +141,20 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
}
+ /**
+ * @return the jtaTransactionManager or {@code null}
+ */
+ protected JtaTransactionManager getJtaTransactionManager() {
+ return this.jtaTransactionManager;
+ }
+
+ /**
+ * Returns if a JTA {@link PlatformTransactionManager} is being used.
+ */
+ protected final boolean isJta() {
+ return (this.jtaTransactionManager != null);
+ }
+
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java
index 84437aba29..fa481f4b5f 100644
--- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java
@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.orm.jpa.SpringNamingStrategy;
+import org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.StringUtils;
diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
index 8f1ba96768..8fe6706801 100644
--- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -20,11 +20,14 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
+org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
+org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
+org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionOnJndiTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionOnJndiTests.java
new file mode 100644
index 0000000000..5d0f9aec29
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionOnJndiTests.java
@@ -0,0 +1,100 @@
+/*
+ * 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.autoconfigure.condition;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link OnJndiCondition}.
+ *
+ * @author Phillip Webb
+ */
+public class ConditionOnJndiTests {
+
+ private MockableOnJndi condition = new MockableOnJndi();
+
+ @Test
+ public void jndiNotAvailable() {
+ this.condition.setJndiAvailable(false);
+ ConditionOutcome outcome = this.condition.getMatchOutcome(null, mockMetaData());
+ assertThat(outcome.isMatch(), equalTo(false));
+ }
+
+ @Test
+ public void jndiLocationNotFound() {
+ ConditionOutcome outcome = this.condition.getMatchOutcome(null,
+ mockMetaData("java:/a"));
+ assertThat(outcome.isMatch(), equalTo(false));
+ }
+
+ @Test
+ public void jndiLocationFound() {
+ this.condition.setFoundLocation("java:/b");
+ ConditionOutcome outcome = this.condition.getMatchOutcome(null,
+ mockMetaData("java:/a", "java:/b"));
+ assertThat(outcome.isMatch(), equalTo(true));
+ }
+
+ private AnnotatedTypeMetadata mockMetaData(String... value) {
+ AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);
+ Map attributes = new HashMap();
+ attributes.put("value", value);
+ given(metadata.getAnnotationAttributes(ConditionalOnJndi.class.getName()))
+ .willReturn(attributes);
+ return metadata;
+ }
+
+ private static class MockableOnJndi extends OnJndiCondition {
+
+ private boolean jndiAvailable = true;
+
+ private String foundLocation;
+
+ @Override
+ protected boolean isJndiAvailable() {
+ return this.jndiAvailable;
+ }
+
+ @Override
+ protected JndiLocator getJndiLocator(String[] locations) {
+ return new JndiLocator(locations) {
+ @Override
+ public String lookupFirstLocation() {
+ return MockableOnJndi.this.foundLocation;
+ }
+ };
+ }
+
+ public void setJndiAvailable(boolean jndiAvailable) {
+ this.jndiAvailable = jndiAvailable;
+ }
+
+ public void setFoundLocation(String foundLocation) {
+ this.foundLocation = foundLocation;
+ }
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java
similarity index 63%
rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java
rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java
index 861941007c..53987b2b8c 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DriverClassNameProviderTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DatabaseDriverTests.java
@@ -20,47 +20,46 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
/**
- * Tests for {@link DriverClassNameProvider}.
+ * Tests for {@link DatabaseDriver}.
*
+ * @author Phillip Webb
* @author Maciej Walkowiak
*/
-public class DriverClassNameProviderTests {
-
- private DriverClassNameProvider provider = new DriverClassNameProvider();
+public class DatabaseDriverTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void classNameForKnownDatabase() {
- String driverClassName = this.provider
- .getDriverClassName("jdbc:postgresql://hostname/dbname");
+ String driverClassName = DatabaseDriver.fromJdbcUrl(
+ "jdbc:postgresql://hostname/dbname").getDriverClassName();
assertEquals("org.postgresql.Driver", driverClassName);
}
@Test
- public void nullForUnknownDatabase() {
- String driverClassName = this.provider
- .getDriverClassName("jdbc:unknowndb://hostname/dbname");
+ public void nullClassNameForUnknownDatabase() {
+ String driverClassName = DatabaseDriver.fromJdbcUrl(
+ "jdbc:unknowndb://hostname/dbname").getDriverClassName();
assertNull(driverClassName);
}
@Test
- public void failureOnNullJdbcUrl() {
- this.thrown.expect(IllegalArgumentException.class);
- this.thrown.expectMessage("JdbcUrl must not be null");
- this.provider.getDriverClassName(null);
+ public void unknownOnNullJdbcUrl() {
+ assertThat(DatabaseDriver.fromJdbcUrl(null), equalTo(DatabaseDriver.UNKNOWN));
}
@Test
public void failureOnMalformedJdbcUrl() {
this.thrown.expect(IllegalArgumentException.class);
- this.thrown.expectMessage("JdbcUrl must start with");
- this.provider.getDriverClassName("malformed:url");
+ this.thrown.expectMessage("URL must start with");
+ DatabaseDriver.fromJdbcUrl("malformed:url");
}
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java
new file mode 100644
index 0000000000..0defecd364
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/XADataSourceAutoConfigurationTests.java
@@ -0,0 +1,128 @@
+/*
+ * 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.autoconfigure.jdbc;
+
+import javax.sql.DataSource;
+import javax.sql.XADataSource;
+
+import org.hsqldb.jdbc.pool.JDBCXADataSource;
+import org.junit.Test;
+import org.springframework.boot.jta.XADataSourceWrapper;
+import org.springframework.boot.test.EnvironmentTestUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link XADataSourceAutoConfiguration}.
+ *
+ * @author Phillip Webb
+ */
+public class XADataSourceAutoConfigurationTests {
+
+ @Test
+ public void wrapExistingXaDataSource() throws Exception {
+ ApplicationContext context = createContext(WrapExisting.class);
+ context.getBean(DataSource.class);
+ XADataSource source = context.getBean(XADataSource.class);
+ MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
+ assertThat(wrapper.getXaDataSource(), equalTo(source));
+ }
+
+ @Test
+ public void createFromUrl() throws Exception {
+ ApplicationContext context = createContext(FromProperties.class,
+ "spring.datasource.url:jdbc:hsqldb:mem:test",
+ "spring.datasource.username:un");
+ context.getBean(DataSource.class);
+ MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
+ JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource();
+ assertNotNull(dataSource);
+ assertThat(dataSource.getUrl(), equalTo("jdbc:hsqldb:mem:test"));
+ assertThat(dataSource.getUser(), equalTo("un"));
+ }
+
+ @Test
+ public void createFromClass() throws Exception {
+ ApplicationContext context = createContext(
+ FromProperties.class,
+ "spring.datasource.xa.data-source-class:org.hsqldb.jdbc.pool.JDBCXADataSource",
+ "spring.datasource.xa.properties.database-name:test");
+ context.getBean(DataSource.class);
+ MockXADataSourceWrapper wrapper = context.getBean(MockXADataSourceWrapper.class);
+ JDBCXADataSource dataSource = (JDBCXADataSource) wrapper.getXaDataSource();
+ assertNotNull(dataSource);
+ assertThat(dataSource.getDatabaseName(), equalTo("test"));
+
+ }
+
+ private ApplicationContext createContext(Class> configuration, String... env) {
+ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
+ EnvironmentTestUtils.addEnvironment(context, env);
+ context.register(configuration, XADataSourceAutoConfiguration.class);
+ context.refresh();
+ return context;
+ }
+
+ @Configuration
+ static class WrapExisting {
+
+ @Bean
+ public MockXADataSourceWrapper wrapper() {
+ return new MockXADataSourceWrapper();
+ }
+
+ @Bean
+ public XADataSource xaDataSource() {
+ return mock(XADataSource.class);
+ }
+
+ }
+
+ @Configuration
+ static class FromProperties {
+
+ @Bean
+ public MockXADataSourceWrapper wrapper() {
+ return new MockXADataSourceWrapper();
+ }
+
+ }
+
+ private static class MockXADataSourceWrapper implements XADataSourceWrapper {
+
+ private XADataSource dataSource;
+
+ @Override
+ public DataSource wrapDataSource(XADataSource dataSource) {
+ this.dataSource = dataSource;
+ return mock(DataSource.class);
+ }
+
+ public XADataSource getXaDataSource() {
+ return this.dataSource;
+ }
+
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java
index c69d1d524f..c1bcba116a 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/JmsAutoConfigurationTests.java
@@ -24,7 +24,6 @@ import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
-import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -49,6 +48,10 @@ import static org.junit.Assert.assertTrue;
*/
public class JmsAutoConfigurationTests {
+ private static final String ACTIVEMQ_EMBEDDED_URL = "vm://localhost?broker.persistent=false";
+
+ private static final String ACTIVEMQ_NETWORK_URL = "tcp://localhost:61616";
+
private AnnotationConfigApplicationContext context;
@Test
@@ -61,7 +64,7 @@ public class JmsAutoConfigurationTests {
.getBean(JmsMessagingTemplate.class);
assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
assertEquals(jmsTemplate, messagingTemplate.getJmsTemplate());
- assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL,
+ assertEquals(ACTIVEMQ_EMBEDDED_URL,
((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory())
.getBrokerURL());
assertTrue("listener container factory should be created by default",
@@ -158,7 +161,7 @@ public class JmsAutoConfigurationTests {
assertNotNull(jmsTemplate);
assertNotNull(connectionFactory);
assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
- assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL,
+ assertEquals(ACTIVEMQ_NETWORK_URL,
((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory())
.getBrokerURL());
}
@@ -188,8 +191,7 @@ public class JmsAutoConfigurationTests {
assertEquals(jmsTemplate.getConnectionFactory(), pool);
ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool
.getConnectionFactory();
- assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL,
- factory.getBrokerURL());
+ assertEquals(ACTIVEMQ_EMBEDDED_URL, factory.getBrokerURL());
}
@Test
@@ -204,8 +206,7 @@ public class JmsAutoConfigurationTests {
assertEquals(jmsTemplate.getConnectionFactory(), pool);
ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool
.getConnectionFactory();
- assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL,
- factory.getBrokerURL());
+ assertEquals(ACTIVEMQ_NETWORK_URL, factory.getBrokerURL());
}
@Test
@@ -257,6 +258,7 @@ public class JmsAutoConfigurationTests {
@Configuration
protected static class TestConfiguration2 {
+
@Bean
ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory() {
@@ -265,10 +267,12 @@ public class JmsAutoConfigurationTests {
}
};
}
+
}
@Configuration
protected static class TestConfiguration3 {
+
@Bean
JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
@@ -280,6 +284,7 @@ public class JmsAutoConfigurationTests {
@Configuration
protected static class TestConfiguration4 implements BeanPostProcessor {
+
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
@@ -295,6 +300,7 @@ public class JmsAutoConfigurationTests {
throws BeansException {
return bean;
}
+
}
@Configuration
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java
index 699bcdc5c1..68ae0ed1ae 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/activemq/ActiveMQPropertiesTests.java
@@ -17,67 +17,50 @@
package org.springframework.boot.autoconfigure.jms.activemq;
import org.junit.Test;
-import org.springframework.boot.test.EnvironmentTestUtils;
-import org.springframework.core.env.StandardEnvironment;
import static org.junit.Assert.assertEquals;
/**
- * Tests for {@link ActiveMQProperties}.
+ * Tests for {@link ActiveMQProperties} and ActiveMQConnectionFactoryFactory.
*
* @author Stephane Nicoll
*/
public class ActiveMQPropertiesTests {
+ private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
+
+ private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
+
private final ActiveMQProperties properties = new ActiveMQProperties();
- private final StandardEnvironment environment = new StandardEnvironment();
-
- @Test
- public void determineBrokerUrlDefault() {
- assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL,
- ActiveMQProperties.determineBrokerUrl(this.environment));
- }
-
- @Test
- public void determineBrokerUrlVmBrokerUrl() {
- EnvironmentTestUtils.addEnvironment(this.environment,
- "spring.activemq.brokerUrl:vm://localhost?persistent=true");
- assertEquals("vm://localhost?persistent=true",
- ActiveMQProperties.determineBrokerUrl(this.environment));
- }
-
- @Test
- public void determineBrokerUrlInMemoryFlag() {
- EnvironmentTestUtils.addEnvironment(this.environment,
- "spring.activemq.inMemory:false");
- assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL,
- ActiveMQProperties.determineBrokerUrl(this.environment));
- }
-
@Test
public void getBrokerUrlIsInMemoryByDefault() {
- assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL,
- this.properties.determineBrokerUrl());
+ assertEquals(DEFAULT_EMBEDDED_BROKER_URL, new ActiveMQConnectionFactoryFactory(
+ this.properties).determineBrokerUrl());
}
@Test
public void getBrokerUrlUseExplicitBrokerUrl() {
this.properties.setBrokerUrl("vm://foo-bar");
- assertEquals("vm://foo-bar", this.properties.determineBrokerUrl());
+ assertEquals("vm://foo-bar",
+ new ActiveMQConnectionFactoryFactory(this.properties)
+ .determineBrokerUrl());
}
@Test
public void getBrokerUrlWithInMemorySetToFalse() {
this.properties.setInMemory(false);
- assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL,
- this.properties.determineBrokerUrl());
+ assertEquals(DEFAULT_NETWORK_BROKER_URL, new ActiveMQConnectionFactoryFactory(
+ this.properties).determineBrokerUrl());
}
@Test
public void getExplicitBrokerUrlAlwaysWins() {
this.properties.setBrokerUrl("vm://foo-bar");
this.properties.setInMemory(false);
- assertEquals("vm://foo-bar", this.properties.determineBrokerUrl());
+ assertEquals("vm://foo-bar",
+ new ActiveMQConnectionFactoryFactory(this.properties)
+ .determineBrokerUrl());
}
+
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfigurationTests.java
index f304de8248..d96d2a3cd6 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jms/hornetq/HornetQAutoConfigurationTests.java
@@ -43,11 +43,13 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.SessionCallback;
@@ -183,10 +185,8 @@ public class HornetQAutoConfigurationTests {
@Test
public void embeddedServiceWithCustomJmsConfiguration() {
- load(CustomJmsConfiguration.class, "spring.hornetq.embedded.queues=Queue1,Queue2"); // Ignored
- // with
- // custom
- // config
+ // Ignored with custom config
+ load(CustomJmsConfiguration.class, "spring.hornetq.embedded.queues=Queue1,Queue2");
DestinationChecker checker = new DestinationChecker(this.context);
checker.checkQueue("custom", true); // See CustomJmsConfiguration
@@ -317,7 +317,7 @@ public class HornetQAutoConfigurationTests {
String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(config);
- applicationContext.register(HornetQAutoConfiguration.class,
+ applicationContext.register(HornetQAutoConfigurationWithoutXA.class,
JmsAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(applicationContext, environment);
applicationContext.refresh();
@@ -417,4 +417,11 @@ public class HornetQAutoConfigurationTests {
}
}
+ @Configuration
+ @EnableConfigurationProperties(HornetQProperties.class)
+ @Import({ HornetQEmbeddedServerConfiguration.class,
+ HornetQConnectionFactoryConfiguration.class })
+ protected static class HornetQAutoConfigurationWithoutXA {
+ }
+
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java
new file mode 100644
index 0000000000..f07097a6b2
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jta/JtaAutoConfigurationTests.java
@@ -0,0 +1,107 @@
+/*
+ * 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.autoconfigure.jta;
+
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+import org.springframework.boot.jta.XADataSourceWrapper;
+import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor;
+import org.springframework.boot.jta.atomikos.AtomikosProperties;
+import org.springframework.boot.jta.bitronix.BitronixDependentBeanFactoryPostProcessor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import com.atomikos.icatch.config.UserTransactionService;
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link JtaAutoConfiguration}.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ */
+public class JtaAutoConfigurationTests {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private AnnotationConfigApplicationContext context;
+
+ @After
+ public void closeContext() {
+ if (this.context != null) {
+ this.context.close();
+ }
+ }
+
+ @Test
+ public void customPatformTransactionManager() throws Exception {
+ this.context = new AnnotationConfigApplicationContext(
+ CustomTransactionManagerConfig.class, JtaAutoConfiguration.class);
+ this.thrown.expect(NoSuchBeanDefinitionException.class);
+ this.context.getBean(JtaTransactionManager.class);
+ }
+
+ @Test
+ public void atomikosSanityCheck() throws Exception {
+ this.context = new AnnotationConfigApplicationContext(JtaProperties.class,
+ AtomikosJtaConfiguration.class);
+ this.context.getBean(AtomikosProperties.class);
+ this.context.getBean(UserTransactionService.class);
+ this.context.getBean(UserTransactionManager.class);
+ this.context.getBean(UserTransaction.class);
+ this.context.getBean(XADataSourceWrapper.class);
+ this.context.getBean(XAConnectionFactoryWrapper.class);
+ this.context.getBean(AtomikosDependsOnBeanFactoryPostProcessor.class);
+ this.context.getBean(JtaTransactionManager.class);
+ }
+
+ @Test
+ public void bitronixSanityCheck() throws Exception {
+ this.context = new AnnotationConfigApplicationContext(JtaProperties.class,
+ BitronixJtaConfiguration.class);
+ this.context.getBean(bitronix.tm.Configuration.class);
+ this.context.getBean(TransactionManager.class);
+ this.context.getBean(XADataSourceWrapper.class);
+ this.context.getBean(XAConnectionFactoryWrapper.class);
+ this.context.getBean(BitronixDependentBeanFactoryPostProcessor.class);
+ this.context.getBean(JtaTransactionManager.class);
+ }
+
+ @Configuration
+ public static class CustomTransactionManagerConfig {
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return mock(PlatformTransactionManager.class);
+ }
+
+ }
+
+}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java
index 1e6734d164..d68309630f 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java
@@ -17,11 +17,12 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.lang.reflect.Field;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
+import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
@@ -228,8 +229,10 @@ public abstract class AbstractJpaAutoConfigurationTests {
factoryBean.setJpaVendorAdapter(adapter);
factoryBean.setDataSource(dataSource);
factoryBean.setPersistenceUnitName("manually-configured");
- factoryBean.setJpaPropertyMap(Collections.singletonMap("configured",
- "manually"));
+ Map properties = new HashMap();
+ properties.put("configured", "manually");
+ properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
+ factoryBean.setJpaPropertyMap(properties);
return factoryBean;
}
}
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index 9a5707e86e..92840a05e5 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -48,6 +48,8 @@
5.9.1
1.8.2
+ 3.9.3
+ 2.1.4
3.0.2
1.9.2
3.2.1
@@ -85,6 +87,7 @@
1.2.2
0.9.1
1.2
+ 1.1
4.11
3.0.8
1.2.17
@@ -257,6 +260,21 @@
spring-boot-starter-jetty
1.2.0.BUILD-SNAPSHOT
+
+ org.springframework.boot
+ spring-boot-starter-jta-atomikos
+ 1.2.0.BUILD-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-jta-bitronix
+ 1.2.0.BUILD-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-jta-arjuna
+ 1.2.0.BUILD-SNAPSHOT
+
org.springframework.boot
spring-boot-starter-log4j
@@ -350,6 +368,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
@@ -370,6 +403,11 @@
metrics-servlets
${codahale-metrics.version}
+
+ org.codehaus.btm
+ btm
+ ${bitronix.version}
+
org.codehaus.janino
janino
@@ -482,6 +520,11 @@
jstl
${jstl.version}
+
+ javax.transaction
+ jta
+ ${jta.version}
+
jaxen
jaxen
diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc
index a9c14a45c4..6cd7748494 100644
--- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc
+++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc
@@ -205,6 +205,10 @@ content into your application; rather pick only the properties that you need.
spring.jpa.hibernate.ddl-auto= # defaults to create-drop for embedded dbs
spring.data.jpa.repositories.enabled=true # if spring data repository support is enabled
+ # JTA ({sc-spring-boot-autoconfigure}/jta/JtaAutoConfiguration.{sc-ext}[JtaAutoConfiguration])
+ spring.jta.log-dir= # transaction log dir
+ spring.jta.*= # technology specific configuration
+
# SOLR ({sc-spring-boot-autoconfigure}/solr/SolrProperties.{sc-ext}[SolrProperties}])
spring.data.solr.host=http://127.0.0.1:8983/solr
spring.data.solr.zkHost=
diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
index 1774bddbf6..e79ad9b97a 100644
--- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
+++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
@@ -1874,6 +1874,83 @@ own beans:
+[[boot-features-jta]]
+== Distributed Transactions with JTA
+Spring Boot supports distributed JTA transactions across multiple XA resources using
+either an http://www.atomikos.com/[Atomkos] or
+http://docs.codehaus.org/display/BTM/Home[Bitronix] embedded transaction manager. JTA
+transactions are also supported when deploying to a suitable Java EE Application Server.
+
+When a JTA environment is detected, Spring's `JtaTransactionManager` will be used to manage
+transactions. Auto-configured JMS, DataSource and JPA beans will be upgraded to support
+XA transactions. You can use standard Spring idioms such as `@Transactional` to
+participate in a distributed transaction.
+
+
+
+=== Using an Atomikos transaction manager
+Atomikos is a popular open source transaction manager which can be embedded into your
+Spring Boot application. You can use the `spring-boot-starter-jta-atomikos` Starter POM to
+pull in the appropriate Atomikos libraries. Spring Boot will auto-configure Atomikos and
+ensure that appropriate `depends-on` settings are applied to your Spring Beans for correct
+startup and shutdown ordering.
+
+By default Atomikos transaction logs will be written to a `transaction-logs` folder in
+your application home directory (the directory in which your application jar file
+resides). You can customize this directory by setting a `spring.jta.log-dir` property in
+your `application.properties` file. Properties starting `spring.jta.` can also be used to
+customize the Atomikos `UserTransactionServiceIml`. See the
+{dc-spring-boot}/jta/atomikos/AtomikosProperties.{dc-ext}[`AtomikosProperties` javadoc]
+for complete details.
+
+
+
+=== Using a Bitronix transaction manager
+Bitronix is another popular open source JTA transaction manager implementation. You can
+use the `spring-boot-starter-jta-bitronix` starter POM to add the appropriate Birtronix
+dependencies to your project. As with Atomikos, Spring Boot will automatically configure
+Bitronix and post-process your beans to ensure that startup and shutdown ordering is
+correct.
+
+By default Bitronix transaction log files (`part1.btm` and `part2.btm`) will be written to
+a `transaction-logs` folder in your application home directory. You can customize this
+directory by using the `spring.jta.log-dir` property. Properties starting `spring.jta.`
+are also bound to the `bitronix.tm.Configuration` bean, allowing for complete
+customization. See the http://btm.codehaus.org/api/2.0.1/bitronix/tm/Configuration.html[Bitronix
+documentation] for details.
+
+
+
+=== Using a Java EE managed transaction manager
+If you are packaging your Spring Boot application as a `war` or `ear` file and deploying
+it to a Java EE application server, you can use your application servers built-in
+transaction manager. Spring Boot will attempt to auto-configure a transaction manager by
+looking at common JNDI locations (`java:comp/UserTransaction`,
+`java:comp/TransactionManager` etc). If you are using a transaction service provided by
+your application server, you will generally also want to ensure that all resources are
+managed by the server and exposed over JNDI. Spring Boot will attempt to auto-configure
+JMS by looking for a `ConnectionFactory` at the JNDI path `java:/JmsXA` and you can use
+the <>
+to configure your `DataSource`.
+
+
+
+=== Supporting an alternative embedded transaction manager
+The {sc-spring-boot}/jta/XAConnectionFactoryWrapper.{sc-ext}[`XAConnectionFactoryWrapper`]
+and {sc-spring-boot}/jta/XADataSourceWrapper.{sc-ext}[`XADataSourceWrapper`] interfaces
+can be used to support alternative embedded transaction managers. The interfaces are
+responsible for wrapping `XAConnectionFactory` and `XADataSource` beans and exposing them
+as regular `ConnectionFactory` and `DataSource` beans which will transparently enroll in
+the distributed transaction. DataSource and JMS auto-configuration will use JTA variants
+as long as you have a `JtaTransactionManager` bean and appropriate XA wrapper beans
+registered within your `ApplicationContext`
+
+The {sc-spring-boot}/jta/BitronixXAConnectionFactoryWrapper.{sc-ext}[BitronixXAConnectionFactoryWrapper]
+and {sc-spring-boot}/jta/BitronixXADataSourceWrapper.{sc-ext}[BitronixXADataSourceWrapper]
+provide good examples of how to write XA wrappers.
+
+
+
[[boot-features-integration]]
== Spring Integration
Spring Integration provides abstractions over messaging and also other transports such as
diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc
index 57a5c0480b..9965370a0c 100644
--- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc
+++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc
@@ -255,6 +255,12 @@ and Hibernate.
|`spring-boot-starter-jdbc`
|JDBC Database support.
+|`spring-boot-starter-jta-atomikos`
+|Support for JTA distributed transactions via Atomikos.
+
+|`spring-boot-starter-jta-bitronix`
+|Support for JTA distributed transactions via Bitronix.
+
|`spring-boot-starter-mobile`
|Support for `spring-mobile`
diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml
index e4cddbedf6..5404d60bae 100644
--- a/spring-boot-samples/pom.xml
+++ b/spring-boot-samples/pom.xml
@@ -38,6 +38,9 @@
spring-boot-sample-hornetq
spring-boot-sample-integration
spring-boot-sample-jetty
+ spring-boot-sample-jta-atomikos
+ spring-boot-sample-jta-bitronix
+ spring-boot-sample-jta-jndi
spring-boot-sample-liquibase
spring-boot-sample-parent-context
spring-boot-sample-profile
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/pom.xml b/spring-boot-samples/spring-boot-sample-jta-atomikos/pom.xml
new file mode 100644
index 0000000000..bc7c05ce6d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.2.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-jta-atomikos
+ Spring Boot Atomikos JTA Sample
+ Spring Boot Atomikos JTA Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework
+ spring-jms
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-jta-atomikos
+
+
+ org.springframework.boot
+ spring-boot-starter-hornetq
+
+
+ org.hornetq
+ hornetq-jms-server
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Account.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Account.java
new file mode 100644
index 0000000000..d40c546003
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Account.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 sample.atomikos;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Account {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String username;
+
+ Account() {
+ }
+
+ public Account(String username) {
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountRepository.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountRepository.java
new file mode 100644
index 0000000000..6b88cb4cb4
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountRepository.java
@@ -0,0 +1,23 @@
+/*
+ * 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 sample.atomikos;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AccountRepository extends JpaRepository {
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountService.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountService.java
new file mode 100644
index 0000000000..5f84d308f2
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/AccountService.java
@@ -0,0 +1,47 @@
+/*
+ * 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 sample.atomikos;
+
+import javax.transaction.Transactional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@Transactional
+public class AccountService {
+
+ private final JmsTemplate jmsTemplate;
+
+ private final AccountRepository accountRepository;
+
+ @Autowired
+ public AccountService(JmsTemplate jmsTemplate, AccountRepository accountRepository) {
+ this.jmsTemplate = jmsTemplate;
+ this.accountRepository = accountRepository;
+ }
+
+ public void createAccountAndNotify(String username) {
+ this.jmsTemplate.convertAndSend("accounts", username);
+ this.accountRepository.save(new Account(username));
+ if ("error".equals(username)) {
+ throw new RuntimeException("Simulated error");
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Messages.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Messages.java
new file mode 100644
index 0000000000..d6f185f28b
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/Messages.java
@@ -0,0 +1,30 @@
+/*
+ * 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 sample.atomikos;
+
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Messages {
+
+ @JmsListener(destination = "accounts")
+ public void onMessage(String content) {
+ System.out.println("----> " + content);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/SampleAtomikosApplication.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/SampleAtomikosApplication.java
new file mode 100644
index 0000000000..e0094a3e4a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/java/sample/atomikos/SampleAtomikosApplication.java
@@ -0,0 +1,50 @@
+/*
+ * 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 sample.atomikos;
+
+import java.io.Closeable;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan
+@EnableAutoConfiguration
+public class SampleAtomikosApplication {
+
+ public static void main(String[] args) throws Exception {
+ ApplicationContext context = SpringApplication.run(
+ SampleAtomikosApplication.class, args);
+ AccountService service = context.getBean(AccountService.class);
+ AccountRepository repository = context.getBean(AccountRepository.class);
+ service.createAccountAndNotify("josh");
+ System.out.println("Count is " + repository.count());
+ try {
+ service.createAccountAndNotify("error");
+ }
+ catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ System.out.println("Count is " + repository.count());
+ Thread.sleep(100);
+ ((Closeable) context).close();
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/resources/application.properties
new file mode 100644
index 0000000000..62806e0ea8
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+spring.hornetq.mode=embedded
+spring.hornetq.embedded.enabled=true
+spring.hornetq.embedded.queues=accounts
+
+logging.level.com.atomikos=WARN
diff --git a/spring-boot-samples/spring-boot-sample-jta-atomikos/src/test/java/sample/atomikos/SampleAtomikosApplicationTests.java b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/test/java/sample/atomikos/SampleAtomikosApplicationTests.java
new file mode 100644
index 0000000000..3950c3dfa8
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-atomikos/src/test/java/sample/atomikos/SampleAtomikosApplicationTests.java
@@ -0,0 +1,71 @@
+/*
+ * 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 sample.atomikos;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.core.SubstringMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.OutputCapture;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Basic integration tests for demo application.
+ *
+ * @author Phillip Webb
+ */
+public class SampleAtomikosApplicationTests {
+
+ @Rule
+ public OutputCapture outputCapture = new OutputCapture();
+
+ @Test
+ public void testTransactionRollback() throws Exception {
+ SampleAtomikosApplication.main(new String[] {});
+ String expected = "";
+ expected += "----> josh\n";
+ expected += "Count is 1\n";
+ expected += "Simulated error\n";
+ expected += "Count is 1\n";
+ assertThat(this.outputCapture.toString(), containsString(expected));
+ assertThat(this.outputCapture.toString(), containsStringOnce("---->"));
+ }
+
+ private Matcher super String> containsStringOnce(String s) {
+ return new SubstringMatcher(s) {
+
+ @Override
+ protected String relationship() {
+ return "containing once";
+ }
+
+ @Override
+ protected boolean evalSubstringOf(String s) {
+ int i = 0;
+ while (s.contains(this.substring)) {
+ s = s.substring(s.indexOf(this.substring) + this.substring.length());
+ i++;
+ }
+ return i == 1;
+ }
+
+ };
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/pom.xml b/spring-boot-samples/spring-boot-sample-jta-bitronix/pom.xml
new file mode 100644
index 0000000000..a88a235c53
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.2.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-jta-bitronix
+ Spring Boot Bitronix JTA Sample
+ Spring Boot Bitronix JTA Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework
+ spring-jms
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-jta-bitronix
+
+
+ org.springframework.boot
+ spring-boot-starter-hornetq
+
+
+ org.hornetq
+ hornetq-jms-server
+
+
+ com.h2database
+ h2
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Account.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Account.java
new file mode 100644
index 0000000000..183e4c595a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Account.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 sample.bitronix;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Account {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String username;
+
+ Account() {
+ }
+
+ public Account(String username) {
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountRepository.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountRepository.java
new file mode 100644
index 0000000000..53978fa7d0
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountRepository.java
@@ -0,0 +1,23 @@
+/*
+ * 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 sample.bitronix;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AccountRepository extends JpaRepository {
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountService.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountService.java
new file mode 100644
index 0000000000..752adf7611
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/AccountService.java
@@ -0,0 +1,47 @@
+/*
+ * 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 sample.bitronix;
+
+import javax.transaction.Transactional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@Transactional
+public class AccountService {
+
+ private final JmsTemplate jmsTemplate;
+
+ private final AccountRepository accountRepository;
+
+ @Autowired
+ public AccountService(JmsTemplate jmsTemplate, AccountRepository accountRepository) {
+ this.jmsTemplate = jmsTemplate;
+ this.accountRepository = accountRepository;
+ }
+
+ public void createAccountAndNotify(String username) {
+ this.jmsTemplate.convertAndSend("accounts", username);
+ this.accountRepository.save(new Account(username));
+ if ("error".equals(username)) {
+ throw new RuntimeException("Simulated error");
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Messages.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Messages.java
new file mode 100644
index 0000000000..cdaddd76b6
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/Messages.java
@@ -0,0 +1,30 @@
+/*
+ * 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 sample.bitronix;
+
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Messages {
+
+ @JmsListener(destination = "accounts")
+ public void onMessage(String content) {
+ System.out.println("----> " + content);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/SampleBitronixApplication.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/SampleBitronixApplication.java
new file mode 100644
index 0000000000..62fb930224
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/java/sample/bitronix/SampleBitronixApplication.java
@@ -0,0 +1,50 @@
+/*
+ * 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 sample.bitronix;
+
+import java.io.Closeable;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan
+@EnableAutoConfiguration
+public class SampleBitronixApplication {
+
+ public static void main(String[] args) throws Exception {
+ ApplicationContext context = SpringApplication.run(
+ SampleBitronixApplication.class, args);
+ AccountService service = context.getBean(AccountService.class);
+ AccountRepository repository = context.getBean(AccountRepository.class);
+ service.createAccountAndNotify("josh");
+ System.out.println("Count is " + repository.count());
+ try {
+ service.createAccountAndNotify("error");
+ }
+ catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ System.out.println("Count is " + repository.count());
+ Thread.sleep(100);
+ ((Closeable) context).close();
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/resources/application.properties
new file mode 100644
index 0000000000..ca501b18cc
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+spring.hornetq.mode=embedded
+spring.hornetq.embedded.enabled=true
+spring.hornetq.embedded.queues=accounts
diff --git a/spring-boot-samples/spring-boot-sample-jta-bitronix/src/test/java/sample/bitronix/SampleBitronixApplicationTests.java b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/test/java/sample/bitronix/SampleBitronixApplicationTests.java
new file mode 100644
index 0000000000..096d1719e2
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-bitronix/src/test/java/sample/bitronix/SampleBitronixApplicationTests.java
@@ -0,0 +1,73 @@
+/*
+ * 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 sample.bitronix;
+
+import org.hamcrest.Matcher;
+import org.hamcrest.core.SubstringMatcher;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.boot.test.OutputCapture;
+
+import sample.bitronix.SampleBitronixApplication;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Basic integration tests for demo application.
+ *
+ * @author Phillip Webb
+ */
+public class SampleBitronixApplicationTests {
+
+ @Rule
+ public OutputCapture outputCapture = new OutputCapture();
+
+ @Test
+ public void testTransactionRollback() throws Exception {
+ SampleBitronixApplication.main(new String[] {});
+ String expected = "";
+ expected += "----> josh\n";
+ expected += "Count is 1\n";
+ expected += "Simulated error\n";
+ expected += "Count is 1\n";
+ assertThat(this.outputCapture.toString(), containsString(expected));
+ assertThat(this.outputCapture.toString(), containsStringOnce("---->"));
+ }
+
+ private Matcher super String> containsStringOnce(String s) {
+ return new SubstringMatcher(s) {
+
+ @Override
+ protected String relationship() {
+ return "containing once";
+ }
+
+ @Override
+ protected boolean evalSubstringOf(String s) {
+ int i = 0;
+ while (s.contains(this.substring)) {
+ s = s.substring(s.indexOf(this.substring) + this.substring.length());
+ i++;
+ }
+ return i == 1;
+ }
+
+ };
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/README.adoc b/spring-boot-samples/spring-boot-sample-jta-jndi/README.adoc
new file mode 100644
index 0000000000..3e4addb82e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/README.adoc
@@ -0,0 +1,148 @@
+## Introduction
+
+This application is intended to run inside of a Java EE application server such as
+JBoss Wildfly. It demonstrates Spring Boot's auto-configuration defaulting for a
+container-managed `TransactionManager` and `DataSource`. This example unfortunately
+requires a fully configured Wildfly installation. You'll need to configure a PostgreSQL
+XA `DataSource` and an XA `ConnectionFactory` in the Java EE application server's
+JNDI machinery.
+
+## Setup
+
+### Postgres
+We will use postgres as the underlying database, v9.3.5 or above is recommend. Follow
+the installation instructions from http://www.postgresql.org/[postgresql.org] or use
+a package manager to install the appropriate binaries.
+
+Once installed you will need to initialize and start the server.
+
+[source,indent=0]
+----
+ $ initdb /usr/local/var/postgres -E utf8
+ $ pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start
+----
+
+With the server running you can create a user and a database:
+
+[source,indent=0]
+----
+ $ createuser springboot
+ $ createdb bootdemo
+----
+
+Finally you can type `psql bootdemo` to configure a password:
+
+[source,indent=0]
+----
+ ALTER USER springboot WITH PASSWORD 'springboot';
+ \q
+----
+
+
+### WildFly 8.1
+Download an install WildFly 8.1 from http://wildfly.org/downloads/[wildfly.org]. Once
+installed you will need to add a management user by running `$JBOSS_HOME/bin/add-user.sh`
+(see the WildFly documentation for details).
+
+You will also need to add a postgresql module. The following commands setup the basic
+structure:
+
+[source,indent=0]
+----
+ $ cd $JBOSS_HOME
+ mkdir -p modules/org/postgresql/main
+ wget http://jdbc.postgresql.org/download/postgresql-9.3-1102.jdbc41.jar
+ mv postgresql-9.3-1102.jdbc41.jar modules/org/postgresql/main
+----
+
+You can then add the following to `$JBOSS_HOME/modules/org/postgresql/main/module.xml`:
+
+[source,indent=0]
+----
+
+
+
+
+
+
+
+
+
+
+----
+
+## Configuration
+A custom WildFly configuration is required for the XA `DataSource` and `ConnectionFactory`
+elements. The `$JBOSS_HOME/standalone/configuration/standalone-full.xml` is a good
+starting point, copy this file to
+`$JBOSS_HOME/standalone/configuration/standalone-boot-demo.xml` then make the following
+changes.
+
+### DataSource
+You need to register a PostgreSQL XA `Driver` and then configure an `xa-datasource`.
+
+Here's a complete listing of the `xa-datasource` contribution to the `datasources`
+element, and the `driver` contribution to the `drivers` element to configure a PostgreSQL
+DB connection to localhost.
+https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6/html-single/Administration_and_Configuration_Guide/index.html#Install_a_JDBC_Driver_with_the_Management_Console[You can learn more from the documentation].
+
+[source,xml,indent=0,subs="verbatim,attributes"]
+----
+
+ ...
+
+ jdbc:postgresql://localhost:5432/crm
+ postgres
+
+ 10
+ 20
+ true
+
+
+ springboot
+ springboot
+
+
+
+ ...
+
+ org.postgresql.xa.PGXADataSource
+
+
+
+----
+
+### JMS Destination
+You will also need to configure a `javax.jms.Destination` by contributing the following to
+the `hornetq-server` element:
+
+[source,xml,indent=0,subs="verbatim,attributes"]
+----
+
+
+
+
+ ...
+
+----
+
+
+## Running and deploying the sample
+Run Wildfly with the following command:
+
+[source,indent=0]
+----
+ $JBOSS_HOME/bin/standalone.sh -c standalone-boot-demo.xml
+----
+
+Once running you can deploy the application by copying
+`target/spring-boot-sample-jta-jndi.war` to `$JBOSS_HOME/standalone/deployments`.
+
+Open a browser to http://localhost:8080/spring-boot-sample-jta-jndi to trigger the
+sample. You should see the current count (it will increment by one on each refresh). If
+you check the logs you should see a `----> Josh` message and some counts. Notice how the
+`error` message triggers an exception with causes both the database insert and the JMS
+message to be rolled back.
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/pom.xml b/spring-boot-samples/spring-boot-sample-jta-jndi/pom.xml
new file mode 100644
index 0000000000..73a54e864e
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-samples
+ 1.2.0.BUILD-SNAPSHOT
+
+ spring-boot-sample-jta-jndi
+ Spring Boot JNDI JTA Sample
+ war
+ Spring Boot JNDI JTA Sample
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework
+ spring-jms
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ javax.jms
+ jms-api
+ provided
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ spring-boot-sample-jta-jndi
+
+
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Account.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Account.java
new file mode 100644
index 0000000000..c55d0e30cf
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Account.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 sample.jndi;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Account {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String username;
+
+ Account() {
+ }
+
+ public Account(String username) {
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountRepository.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountRepository.java
new file mode 100644
index 0000000000..350e87af25
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountRepository.java
@@ -0,0 +1,23 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.data.repository.CrudRepository;
+
+public interface AccountRepository extends CrudRepository {
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountService.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountService.java
new file mode 100644
index 0000000000..f297d1572a
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/AccountService.java
@@ -0,0 +1,47 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@Transactional
+public class AccountService {
+
+ private final JmsTemplate jmsTemplate;
+
+ private final AccountRepository accountRepository;
+
+ @Autowired
+ public AccountService(JmsTemplate jmsTemplate, AccountRepository accountRepository) {
+ this.jmsTemplate = jmsTemplate;
+ this.accountRepository = accountRepository;
+ }
+
+ public void createAccountAndNotify(String username) {
+ this.jmsTemplate.convertAndSend("java:/jms/queue/bootdemo", username);
+ Account entity = new Account(username);
+ this.accountRepository.save(entity);
+ if ("error".equals(username)) {
+ throw new RuntimeException("Simulated error");
+ }
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Messages.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Messages.java
new file mode 100644
index 0000000000..59bb4e639d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/Messages.java
@@ -0,0 +1,30 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Messages {
+
+ @JmsListener(destination = "java:/jms/queue/bootdemo")
+ public void onMessage(String content) {
+ System.out.println("----> " + content);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiApplication.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiApplication.java
new file mode 100644
index 0000000000..2f42628517
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiApplication.java
@@ -0,0 +1,28 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableAutoConfiguration
+@ComponentScan
+public class SampleJndiApplication {
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiInitializer.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiInitializer.java
new file mode 100644
index 0000000000..3314d0e126
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/SampleJndiInitializer.java
@@ -0,0 +1,29 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.context.web.SpringBootServletInitializer;
+
+public class SampleJndiInitializer extends SpringBootServletInitializer {
+
+ @Override
+ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+ return application.sources(SampleJndiApplication.class);
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/WebController.java b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/WebController.java
new file mode 100644
index 0000000000..6b444578f6
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/java/sample/jndi/WebController.java
@@ -0,0 +1,51 @@
+/*
+ * 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 sample.jndi;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class WebController {
+
+ private final AccountService service;
+
+ private final AccountRepository repository;
+
+ @Autowired
+ public WebController(AccountService service, AccountRepository repository) {
+ this.service = service;
+ this.repository = repository;
+ }
+
+ @RequestMapping("/")
+ public String hello() {
+ System.out.println("Count is " + this.repository.count());
+ this.service.createAccountAndNotify("josh");
+ try {
+ this.service.createAccountAndNotify("error");
+ }
+ catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ long count = this.repository.count();
+ System.out.println("Count is " + count);
+ return "Count is " + count;
+ }
+
+}
diff --git a/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/resources/application.properties
new file mode 100644
index 0000000000..a4720b1e4d
--- /dev/null
+++ b/spring-boot-samples/spring-boot-sample-jta-jndi/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+spring.jpa.generate-ddl=true
+spring.datasource.jndi-name=java:jboss/datasources/bootdemo
+
+# Workaround SPR-12118
+spring.jpa.open-in-view=false
diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml
index 6f45c9b2e0..efe5a5b2b4 100644
--- a/spring-boot-starters/pom.xml
+++ b/spring-boot-starters/pom.xml
@@ -36,6 +36,8 @@
spring-boot-starter-integration
spring-boot-starter-jdbc
spring-boot-starter-jetty
+ spring-boot-starter-jta-atomikos
+ spring-boot-starter-jta-bitronix
spring-boot-starter-logging
spring-boot-starter-log4j
spring-boot-starter-mobile
diff --git a/spring-boot-starters/spring-boot-starter-jta-atomikos/pom.xml b/spring-boot-starters/spring-boot-starter-jta-atomikos/pom.xml
new file mode 100644
index 0000000000..fe36be77a5
--- /dev/null
+++ b/spring-boot-starters/spring-boot-starter-jta-atomikos/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starters
+ 1.2.0.BUILD-SNAPSHOT
+
+ spring-boot-starter-jta-atomikos
+ Spring Boot Atomikos JTA Starter
+ Spring Boot Atomikos JTA Starter
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ com.atomikos
+ transactions-jms
+
+
+ com.atomikos
+ transactions-jta
+
+
+ com.atomikos
+ transactions-jdbc
+
+
+
diff --git a/spring-boot-starters/spring-boot-starter-jta-bitronix/pom.xml b/spring-boot-starters/spring-boot-starter-jta-bitronix/pom.xml
new file mode 100644
index 0000000000..ced6f76870
--- /dev/null
+++ b/spring-boot-starters/spring-boot-starter-jta-bitronix/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starters
+ 1.2.0.BUILD-SNAPSHOT
+
+ spring-boot-starter-jta-bitronix
+ Spring Boot Bitronix JTA Starter
+ Spring Boot Bitronix JTA Starter
+ http://projects.spring.io/spring-boot/
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ javax.jms
+ jms-api
+
+
+ javax.transaction
+ jta
+
+
+ org.codehaus.btm
+ btm
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml
index 86d4e5f2df..7036d0cb55 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
@@ -69,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/XAConnectionFactoryWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/XAConnectionFactoryWrapper.java
new file mode 100644
index 0000000000..4680fb5c9a
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/XAConnectionFactoryWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import javax.jms.ConnectionFactory;
+import javax.jms.XAConnectionFactory;
+import javax.transaction.TransactionManager;
+
+/**
+ * Strategy interface used to wrap a JMS {@link XAConnectionFactory} enrolling it with a
+ * JTA {@link TransactionManager}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public interface XAConnectionFactoryWrapper {
+
+ /**
+ * Wrap the specific {@link XAConnectionFactory} and enroll it with a JTA
+ * {@link TransactionManager}.
+ * @param connectionFactory the connection factory to wrap
+ * @return the wrapped connection factory
+ */
+ ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory)
+ throws Exception;
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/XADataSourceWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/XADataSourceWrapper.java
new file mode 100644
index 0000000000..1ee8d818b7
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/XADataSourceWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import javax.sql.DataSource;
+import javax.sql.XADataSource;
+import javax.transaction.TransactionManager;
+
+/**
+ * Strategy interface used to wrap a JMS {@link XADataSource} enrolling it with a JTA
+ * {@link TransactionManager}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public interface XADataSourceWrapper {
+
+ /**
+ * Wrap the specific {@link XADataSource} and enroll it with a JTA
+ * {@link TransactionManager}.
+ * @param dataSource the data source to wrap
+ * @return the wrapped data source
+ */
+ DataSource wrapDataSource(XADataSource dataSource) throws Exception;
+
+}
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 0000000000..9559ed1b5f
--- /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 0000000000..d601aaf37a
--- /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 0000000000..4d0fdc22dd
--- /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 0000000000..39e5fca759
--- /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 0000000000..5222846035
--- /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/atomikos/AtomikosXAConnectionFactoryWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapper.java
new file mode 100644
index 0000000000..c370bb945b
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ConnectionFactory;
+import javax.jms.XAConnectionFactory;
+
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+
+/**
+ * {@link XAConnectionFactoryWrapper} that uses an {@link AtomikosConnectionFactoryBean}
+ * to wrap a {@link XAConnectionFactory}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class AtomikosXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper {
+
+ @Override
+ public ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory) {
+ AtomikosConnectionFactoryBean bean = new AtomikosConnectionFactoryBean();
+ bean.setXaConnectionFactory(connectionFactory);
+ return bean;
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapper.java
new file mode 100644
index 0000000000..28fc7f778b
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sql.XADataSource;
+
+import org.springframework.boot.jta.XADataSourceWrapper;
+
+/**
+ * {@link XADataSourceWrapper} that uses an {@link AtomikosDataSourceBean} to wrap a
+ * {@link XADataSource}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class AtomikosXADataSourceWrapper implements XADataSourceWrapper {
+
+ @Override
+ public AtomikosDataSourceBean wrapDataSource(XADataSource dataSource)
+ throws Exception {
+ AtomikosDataSourceBean bean = new AtomikosDataSourceBean();
+ bean.setXaDataSource(dataSource);
+ return bean;
+ }
+
+}
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/BitronixXAConnectionFactoryWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixXAConnectionFactoryWrapper.java
new file mode 100644
index 0000000000..6a539c9a0c
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixXAConnectionFactoryWrapper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.jms.XAConnectionFactory;
+
+import org.springframework.boot.jta.XAConnectionFactoryWrapper;
+
+/**
+ * {@link XAConnectionFactoryWrapper} that uses a Bitronix
+ * {@link PoolingConnectionFactoryBean} to wrap a {@link XAConnectionFactory}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class BitronixXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper {
+
+ @Override
+ public ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory) {
+ PoolingConnectionFactoryBean pool = new PoolingConnectionFactoryBean();
+ pool.setConnectionFactory(connectionFactory);
+ return pool;
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapper.java
new file mode 100644
index 0000000000..2edd1a07bf
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.sql.XADataSource;
+
+import org.springframework.boot.jta.XADataSourceWrapper;
+
+/**
+ * {@link XADataSourceWrapper} that uses a Bitronix {@link PoolingDataSourceBean} to wrap
+ * a {@link XADataSource}.
+ *
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class BitronixXADataSourceWrapper implements XADataSourceWrapper {
+
+ @Override
+ public PoolingDataSourceBean wrapDataSource(XADataSource dataSource) throws Exception {
+ PoolingDataSourceBean pool = new PoolingDataSourceBean();
+ pool.setDataSource(dataSource);
+ return pool;
+ }
+
+}
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/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 0000000000..3ca0cc266b
--- /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/main/java/org/springframework/boot/orm/jpa/SpringNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/SpringNamingStrategy.java
index c0f133e720..a6917f94ce 100644
--- a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/SpringNamingStrategy.java
+++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/SpringNamingStrategy.java
@@ -16,32 +16,17 @@
package org.springframework.boot.orm.jpa;
-import org.hibernate.cfg.ImprovedNamingStrategy;
import org.hibernate.cfg.NamingStrategy;
-import org.hibernate.internal.util.StringHelper;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
/**
* Hibernate {@link NamingStrategy} that follows Spring recommended naming conventions.
- * Naming conventions implemented here are identical to {@link ImprovedNamingStrategy}
- * with the exception that foreign key columns include the referenced column name.
*
* @author Phillip Webb
- * @see "http://stackoverflow.com/questions/7689206/ejb3namingstrategy-vs-improvednamingstrategy-foreign-key-naming"
+ * @deprecated Since 1.2.0 in favor of
+ * {@link org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy}
*/
-public class SpringNamingStrategy extends ImprovedNamingStrategy {
-
- @Override
- public String foreignKeyColumnName(String propertyName, String propertyEntityName,
- String propertyTableName, String referencedColumnName) {
- String name = propertyTableName;
- if (propertyName != null) {
- name = StringHelper.unqualify(propertyName);
- }
- Assert.state(StringUtils.hasLength(name),
- "Unable to generate foreignKeyColumnName");
- return columnName(name) + "_" + referencedColumnName;
- }
+@Deprecated
+public class SpringNamingStrategy extends
+ org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy {
}
diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringJtaPlatform.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringJtaPlatform.java
new file mode 100644
index 0000000000..66fd6000ad
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringJtaPlatform.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.orm.jpa.hibernate;
+
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
+import org.springframework.transaction.jta.JtaTransactionManager;
+import org.springframework.util.Assert;
+
+/**
+ * Generic Hibernate {@link AbstractJtaPlatform} implementation that simply resolves the
+ * JTA {@link UserTransaction} and {@link TransactionManager} from the Spring-configured
+ * {@link JtaTransactionManager} implementation.
+ *
+ * @author Josh Long
+ * @author Phillip Webb
+ * @since 1.2.0
+ */
+public class SpringJtaPlatform extends AbstractJtaPlatform {
+
+ private static final long serialVersionUID = 1L;
+
+ private final JtaTransactionManager transactionManager;
+
+ public SpringJtaPlatform(JtaTransactionManager transactionManager) {
+ Assert.notNull(transactionManager, "TransactionManager must not be null");
+ this.transactionManager = transactionManager;
+ }
+
+ protected boolean hasTransactionManager() {
+ return true;
+ }
+
+ @Override
+ protected TransactionManager locateTransactionManager() {
+ return this.transactionManager.getTransactionManager();
+ }
+
+ @Override
+ protected UserTransaction locateUserTransaction() {
+ return this.transactionManager.getUserTransaction();
+ }
+
+}
diff --git a/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java
new file mode 100644
index 0000000000..92239e4800
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/orm/jpa/hibernate/SpringNamingStrategy.java
@@ -0,0 +1,48 @@
+/*
+ * 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.orm.jpa.hibernate;
+
+import org.hibernate.cfg.ImprovedNamingStrategy;
+import org.hibernate.cfg.NamingStrategy;
+import org.hibernate.internal.util.StringHelper;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Hibernate {@link NamingStrategy} that follows Spring recommended naming conventions.
+ * Naming conventions implemented here are identical to {@link ImprovedNamingStrategy}
+ * with the exception that foreign key columns include the referenced column name.
+ *
+ * @author Phillip Webb
+ * @see "http://stackoverflow.com/questions/7689206/ejb3namingstrategy-vs-improvednamingstrategy-foreign-key-naming"
+ * @since 1.2.0
+ */
+public class SpringNamingStrategy extends ImprovedNamingStrategy {
+
+ @Override
+ public String foreignKeyColumnName(String propertyName, String propertyEntityName,
+ String propertyTableName, String referencedColumnName) {
+ String name = propertyTableName;
+ if (propertyName != null) {
+ name = StringHelper.unqualify(propertyName);
+ }
+ Assert.state(StringUtils.hasLength(name),
+ "Unable to generate foreignKeyColumnName");
+ return columnName(name) + "_" + referencedColumnName;
+ }
+
+}
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 0000000000..55b7f0bcf7
--- /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 0000000000..10348aa5bf
--- /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 0000000000..485f46d953
--- /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 0000000000..3060a60cb3
--- /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));
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapperTests.java
new file mode 100644
index 0000000000..3745522fd6
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXAConnectionFactoryWrapperTests.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ConnectionFactory;
+import javax.jms.XAConnectionFactory;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AtomikosXAConnectionFactoryWrapper}.
+ *
+ * @author Phillip Webb
+ */
+public class AtomikosXAConnectionFactoryWrapperTests {
+
+ @Test
+ public void wrap() {
+ XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
+ AtomikosXAConnectionFactoryWrapper wrapper = new AtomikosXAConnectionFactoryWrapper();
+ ConnectionFactory wrapped = wrapper.wrapConnectionFactory(connectionFactory);
+ assertThat(wrapped, instanceOf(AtomikosConnectionFactoryBean.class));
+ assertThat(((AtomikosConnectionFactoryBean) wrapped).getXaConnectionFactory(),
+ sameInstance(connectionFactory));
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapperTests.java
new file mode 100644
index 0000000000..c2d6e56a5f
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosXADataSourceWrapperTests.java
@@ -0,0 +1,46 @@
+/*
+ * 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.sql.DataSource;
+import javax.sql.XADataSource;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AtomikosXADataSourceWrapper}.
+ *
+ * @author Phillip Webb
+ */
+public class AtomikosXADataSourceWrapperTests {
+
+ @Test
+ public void wrap() throws Exception {
+ XADataSource dataSource = mock(XADataSource.class);
+ AtomikosXADataSourceWrapper wrapper = new AtomikosXADataSourceWrapper();
+ DataSource wrapped = wrapper.wrapDataSource(dataSource);
+ assertThat(wrapped, instanceOf(AtomikosDataSourceBean.class));
+ assertThat(((AtomikosDataSourceBean) wrapped).getXaDataSource(),
+ sameInstance(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/BitronixXAConnectionFactoryWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixXAConnectionFactoryWrapperTests.java
new file mode 100644
index 0000000000..fca543971b
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixXAConnectionFactoryWrapperTests.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jms.XAConnectionFactory;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BitronixXAConnectionFactoryWrapper}.
+ *
+ * @author Phillip Webb
+ */
+public class BitronixXAConnectionFactoryWrapperTests {
+
+ @Test
+ public void wrap() {
+ XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
+ BitronixXAConnectionFactoryWrapper wrapper = new BitronixXAConnectionFactoryWrapper();
+ ConnectionFactory wrapped = wrapper.wrapConnectionFactory(connectionFactory);
+ assertThat(wrapped, instanceOf(PoolingConnectionFactoryBean.class));
+ assertThat(((PoolingConnectionFactoryBean) wrapped).getConnectionFactory(),
+ sameInstance(connectionFactory));
+ }
+
+}
diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapperTests.java
new file mode 100644
index 0000000000..abea432440
--- /dev/null
+++ b/spring-boot/src/test/java/org/springframework/boot/jta/bitronix/BitronixXADataSourceWrapperTests.java
@@ -0,0 +1,46 @@
+/*
+ * 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.sql.DataSource;
+import javax.sql.XADataSource;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BitronixXADataSourceWrapper}.
+ *
+ * @author Phillip Webb
+ */
+public class BitronixXADataSourceWrapperTests {
+
+ @Test
+ public void wrap() throws Exception {
+ XADataSource dataSource = mock(XADataSource.class);
+ BitronixXADataSourceWrapper wrapper = new BitronixXADataSourceWrapper();
+ DataSource wrapped = wrapper.wrapDataSource(dataSource);
+ assertThat(wrapped, instanceOf(PoolingDataSourceBean.class));
+ assertThat(((PoolingDataSourceBean) wrapped).getDataSource(),
+ sameInstance(dataSource));
+ }
+
+}
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();
+ }
+
+}