Commit c24667f6 authored by Phillip Webb's avatar Phillip Webb

Merge branch 'gh-947'

Fixes gh-947
parents bacdd5a4 40d8dde2
...@@ -25,6 +25,16 @@ ...@@ -25,6 +25,16 @@
<artifactId>spring-boot</artifactId> <artifactId>spring-boot</artifactId>
</dependency> </dependency>
<!-- Optional --> <!-- Optional -->
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
...@@ -80,6 +90,11 @@ ...@@ -80,6 +90,11 @@
<artifactId>velocity</artifactId> <artifactId>velocity</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.groovy</groupId> <groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-templates</artifactId> <artifactId>groovy-templates</artifactId>
......
/*
* 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 {};
}
/*
* 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;
}
}
}
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.sql.XADataSource;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
...@@ -74,7 +75,7 @@ public class DataSourceAutoConfiguration { ...@@ -74,7 +75,7 @@ public class DataSourceAutoConfiguration {
} }
@Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class) @Conditional(DataSourceAutoConfiguration.EmbeddedDataSourceCondition.class)
@ConditionalOnMissingBean(DataSource.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class) @Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedConfiguration { protected static class EmbeddedConfiguration {
...@@ -92,7 +93,7 @@ public class DataSourceAutoConfiguration { ...@@ -92,7 +93,7 @@ public class DataSourceAutoConfiguration {
} }
@Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class) @Conditional(DataSourceAutoConfiguration.NonEmbeddedDataSourceCondition.class)
@ConditionalOnMissingBean(DataSource.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
protected static class NonEmbeddedConfiguration { protected static class NonEmbeddedConfiguration {
@Autowired @Autowired
...@@ -196,7 +197,8 @@ public class DataSourceAutoConfiguration { ...@@ -196,7 +197,8 @@ public class DataSourceAutoConfiguration {
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
if (hasBean(context, DataSource.class)) { if (hasBean(context, DataSource.class)
|| hasBean(context, XADataSource.class)) {
return ConditionOutcome return ConditionOutcome
.match("existing bean configured database detected"); .match("existing bean configured database detected");
} }
...@@ -210,6 +212,7 @@ public class DataSourceAutoConfiguration { ...@@ -210,6 +212,7 @@ public class DataSourceAutoConfiguration {
return BeanFactoryUtils.beanNamesForTypeIncludingAncestors( return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getBeanFactory(), type, true, false).length > 0; context.getBeanFactory(), type, true, false).length > 0;
} }
} }
} }
...@@ -49,8 +49,6 @@ public class DataSourceBuilder { ...@@ -49,8 +49,6 @@ public class DataSourceBuilder {
private ClassLoader classLoader; private ClassLoader classLoader;
private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider();
private Map<String, String> properties = new HashMap<String, String>(); private Map<String, String> properties = new HashMap<String, String>();
public static DataSourceBuilder create() { public static DataSourceBuilder create() {
...@@ -76,9 +74,9 @@ public class DataSourceBuilder { ...@@ -76,9 +74,9 @@ public class DataSourceBuilder {
private void maybeGetDriverClassName() { private void maybeGetDriverClassName() {
if (!this.properties.containsKey("driverClassName") if (!this.properties.containsKey("driverClassName")
&& this.properties.containsKey("url")) { && this.properties.containsKey("url")) {
String cls = this.driverClassNameProvider.getDriverClassName(this.properties String url = this.properties.get("url");
.get("url")); String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
this.properties.put("driverClassName", cls); this.properties.put("driverClassName", driverClass);
} }
} }
......
...@@ -16,6 +16,9 @@ ...@@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.jdbc; 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.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
...@@ -64,7 +67,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB ...@@ -64,7 +67,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE; private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;
private DriverClassNameProvider driverClassNameProvider = new DriverClassNameProvider(); private Xa xa = new Xa();
@Override @Override
public void setBeanClassLoader(ClassLoader classLoader) { public void setBeanClassLoader(ClassLoader classLoader) {
...@@ -86,7 +89,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB ...@@ -86,7 +89,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
String driverClassName = null; String driverClassName = null;
if (StringUtils.hasText(this.url)) { if (StringUtils.hasText(this.url)) {
driverClassName = this.driverClassNameProvider.getDriverClassName(this.url); driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();
} }
if (!StringUtils.hasText(driverClassName)) { if (!StringUtils.hasText(driverClassName)) {
...@@ -113,7 +116,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB ...@@ -113,7 +116,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
"Cannot determine embedded database url for database type " "Cannot determine embedded database url for database type "
+ this.embeddedDatabaseConnection + this.embeddedDatabaseConnection
+ ". If you want an embedded " + ". 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; return url;
} }
...@@ -228,4 +231,39 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB ...@@ -228,4 +231,39 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
return this.classLoader; 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<String, String> properties = new LinkedHashMap<String, String>();
public String getDataSourceClassName() {
return this.dataSourceClassName;
}
public void setDataSourceClassName(String dataSourceClassName) {
this.dataSourceClassName = dataSourceClassName;
}
public Map<String, String> getProperties() {
return this.properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
}
} }
/*
* 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;
}
}
...@@ -36,7 +36,8 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; ...@@ -36,7 +36,8 @@ import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
* @since 1.2.0 * @since 1.2.0
*/ */
@Configuration @Configuration
@AutoConfigureBefore(DataSourceAutoConfiguration.class) @AutoConfigureBefore({ XADataSourceAutoConfiguration.class,
DataSourceAutoConfiguration.class })
@ConditionalOnClass(DataSource.class) @ConditionalOnClass(DataSource.class)
@ConditionalOnProperty(prefix = DataSourceProperties.PREFIX, name = "jndi-name") @ConditionalOnProperty(prefix = DataSourceProperties.PREFIX, name = "jndi-name")
@EnableConfigurationProperties(DataSourceProperties.class) @EnableConfigurationProperties(DataSourceProperties.class)
......
/*
* 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);
}
}
...@@ -20,6 +20,7 @@ import javax.jms.ConnectionFactory; ...@@ -20,6 +20,7 @@ import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJndi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -29,6 +30,7 @@ import org.springframework.jms.annotation.JmsBootstrapConfiguration; ...@@ -29,6 +30,7 @@ import org.springframework.jms.annotation.JmsBootstrapConfiguration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory; import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerConfigUtils; import org.springframework.jms.config.JmsListenerConfigUtils;
import org.springframework.jms.support.destination.DestinationResolver; import org.springframework.jms.support.destination.DestinationResolver;
import org.springframework.jms.support.destination.JndiDestinationResolver;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
/** /**
...@@ -67,6 +69,18 @@ class JmsAnnotationDrivenConfiguration { ...@@ -67,6 +69,18 @@ class JmsAnnotationDrivenConfiguration {
@EnableJms @EnableJms
@ConditionalOnMissingBean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME) @ConditionalOnMissingBean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
protected static class EnableJmsConfiguration { protected static class EnableJmsConfiguration {
}
@ConditionalOnJndi
protected static class JndiConfiguration {
@Bean
@ConditionalOnMissingBean
public DestinationResolver destinationResolver() {
JndiDestinationResolver resolver = new JndiDestinationResolver();
resolver.setFallbackToDynamicDestination(true);
return resolver;
}
} }
......
/*
* 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);
}
}
...@@ -19,19 +19,16 @@ package org.springframework.boot.autoconfigure.jms.activemq; ...@@ -19,19 +19,16 @@ package org.springframework.boot.autoconfigure.jms.activemq;
import javax.jms.ConnectionFactory; import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory; 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.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.context.annotation.ConditionContext; import org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration;
import org.springframework.context.annotation.Conditional; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.type.AnnotatedTypeMetadata;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} to integrate with an ActiveMQ * {@link EnableAutoConfiguration Auto-configuration} to integrate with an ActiveMQ
...@@ -39,67 +36,16 @@ import org.springframework.core.type.AnnotatedTypeMetadata; ...@@ -39,67 +36,16 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
* embedded broker. * embedded broker.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 1.1.0 * @since 1.1.0
*/ */
@Configuration @Configuration
@AutoConfigureBefore(JmsAutoConfiguration.class) @AutoConfigureBefore(JmsAutoConfiguration.class)
@AutoConfigureAfter(JtaAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class }) @ConditionalOnClass({ ConnectionFactory.class, ActiveMQConnectionFactory.class })
@ConditionalOnMissingBean(ConnectionFactory.class) @ConditionalOnMissingBean(ConnectionFactory.class)
@EnableConfigurationProperties(ActiveMQProperties.class)
@Import({ ActiveMQXAConnectionFactoryConfiguration.class, ActiveMQConnectionFactoryConfiguration.class })
public class ActiveMQAutoConfiguration { 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);
}
}
} }
...@@ -18,27 +18,34 @@ package org.springframework.boot.autoconfigure.jms.activemq; ...@@ -18,27 +18,34 @@ package org.springframework.boot.autoconfigure.jms.activemq;
import javax.jms.ConnectionFactory; import javax.jms.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.apache.activemq.pool.PooledConnectionFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* Creates a {@link ConnectionFactory} based on {@link ActiveMQProperties}. * Configuration for ActiveMQ {@link ConnectionFactory}.
* *
* @author Greg Turnquist * @author Greg Turnquist
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 1.1.0 * @since 1.1.0
*/ */
@Configuration @Configuration
@EnableConfigurationProperties(ActiveMQProperties.class) @ConditionalOnMissingBean(ConnectionFactory.class)
class ActiveMQConnectionFactoryConfiguration { class ActiveMQConnectionFactoryConfiguration {
@Autowired
private ActiveMQProperties properties;
@Bean @Bean
public ConnectionFactory jmsConnectionFactory() { public ConnectionFactory jmsConnectionFactory(ActiveMQProperties properties) {
return this.properties.createConnectionFactory(); ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactoryFactory(
properties).createConnectionFactory(ActiveMQConnectionFactory.class);
if (properties.isPooled()) {
PooledConnectionFactory pool = new PooledConnectionFactory();
pool.setConnectionFactory(connectionFactory);
return pool;
}
return connectionFactory;
} }
} }
/*
* 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 extends ActiveMQConnectionFactory> T createConnectionFactory(
Class<T> factoryClass) {
try {
return doCreateConnectionFactory(factoryClass);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create "
+ "ActiveMQConnectionFactory", ex);
}
}
private <T extends ActiveMQConnectionFactory> T doCreateConnectionFactory(
Class<T> 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;
}
}
...@@ -16,15 +16,7 @@ ...@@ -16,15 +16,7 @@
package org.springframework.boot.autoconfigure.jms.activemq; 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.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 * Configuration properties for ActiveMQ
...@@ -35,10 +27,6 @@ import org.springframework.util.StringUtils; ...@@ -35,10 +27,6 @@ import org.springframework.util.StringUtils;
@ConfigurationProperties(prefix = "spring.activemq") @ConfigurationProperties(prefix = "spring.activemq")
public class ActiveMQProperties { 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 String brokerUrl;
private boolean inMemory = true; private boolean inMemory = true;
...@@ -93,52 +81,4 @@ public class ActiveMQProperties { ...@@ -93,52 +81,4 @@ public class ActiveMQProperties {
this.password = password; 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);
}
} }
/*
* 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);
}
}
...@@ -16,48 +16,25 @@ ...@@ -16,48 +16,25 @@
package org.springframework.boot.autoconfigure.jms.hornetq; 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 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.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.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 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.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.context.annotation.Import;
import org.springframework.util.ClassUtils;
/** /**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration * {@link EnableAutoConfiguration Auto-configuration} to integrate with an HornetQ broker.
* Auto-configuration} to integrate with an HornetQ broker. If the necessary classes are * If the necessary classes are present, embed the broker in the application by default.
* present, embed the broker in the application by default. Otherwise, connect to a broker * Otherwise, connect to a broker available on the local machine with the default
* available on the local machine with the default settings. * settings.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 1.1.0 * @since 1.1.0
* @see HornetQProperties * @see HornetQProperties
*/ */
...@@ -65,151 +42,9 @@ import org.springframework.util.ClassUtils; ...@@ -65,151 +42,9 @@ import org.springframework.util.ClassUtils;
@AutoConfigureBefore(JmsAutoConfiguration.class) @AutoConfigureBefore(JmsAutoConfiguration.class)
@ConditionalOnClass({ ConnectionFactory.class, HornetQJMSClient.class }) @ConditionalOnClass({ ConnectionFactory.class, HornetQJMSClient.class })
@EnableConfigurationProperties(HornetQProperties.class) @EnableConfigurationProperties(HornetQProperties.class)
@Import({ HornetQEmbeddedServerConfiguration.class,
HornetQXAConnectionFactoryConfiguration.class,
HornetQConnectionFactoryConfiguration.class })
public class HornetQAutoConfiguration { 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<String, Object> params = new HashMap<String, Object>();
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<HornetQConfigurationCustomizer> configurationCustomizers;
@Autowired(required = false)
private List<JMSQueueConfiguration> queuesConfiguration;
@Autowired(required = false)
private List<TopicConfiguration> 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 <T> void addAll(List<T> 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));
}
}
}
} }
/*
* 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);
}
}
/*
* 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 extends HornetQConnectionFactory> T createConnectionFactory(
Class<T> factoryClass) {
try {
return doCreateConnectionFactory(factoryClass);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to create "
+ "HornetQConnectionFactory", ex);
}
}
private <T extends HornetQConnectionFactory> T doCreateConnectionFactory(
Class<T> 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 extends HornetQConnectionFactory> T createEmbeddedConnectionFactory(
Class<T> 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 extends HornetQConnectionFactory> T createNativeConnectionFactory(
Class<T> factoryClass) throws Exception {
Map<String, Object> params = new HashMap<String, Object>();
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<T> constructor = factoryClass.getConstructor(boolean.class,
TransportConfiguration[].class);
return constructor.newInstance(false,
new TransportConfiguration[] { transportConfiguration });
}
}
/*
* 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<HornetQConfigurationCustomizer> configurationCustomizers;
@Autowired(required = false)
private List<JMSQueueConfiguration> queuesConfiguration;
@Autowired(required = false)
private List<TopicConfiguration> 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 <T> void addAll(List<T> 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));
}
}
}
/*
* 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));
}
}
/*
* 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 <A href="http://www.atomikos.com/">Atomikos</a>.
*
* @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);
}
}
/*
* 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 <A href="http://docs.codehaus.org/display/BTM/Home">Bitronix</A>.
*
* @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);
}
}
/*
* 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();
}
}
/*
* 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 {
}
/*
* 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;
}
}
...@@ -89,6 +89,8 @@ public class EntityManagerFactoryBuilder { ...@@ -89,6 +89,8 @@ public class EntityManagerFactoryBuilder {
private Map<String, Object> properties = new HashMap<String, Object>(); private Map<String, Object> properties = new HashMap<String, Object>();
private boolean jta;
private Builder(DataSource dataSource) { private Builder(DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
...@@ -142,6 +144,21 @@ public class EntityManagerFactoryBuilder { ...@@ -142,6 +144,21 @@ public class EntityManagerFactoryBuilder {
return this; 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() { public LocalContainerEntityManagerFactoryBean build() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) { if (EntityManagerFactoryBuilder.this.persistenceUnitManager != null) {
...@@ -153,7 +170,14 @@ public class EntityManagerFactoryBuilder { ...@@ -153,7 +170,14 @@ public class EntityManagerFactoryBuilder {
} }
entityManagerFactoryBean entityManagerFactoryBean
.setJpaVendorAdapter(EntityManagerFactoryBuilder.this.jpaVendorAdapter); .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.setPackagesToScan(this.packagesToScan);
entityManagerFactoryBean.getJpaPropertyMap().putAll( entityManagerFactoryBean.getJpaPropertyMap().putAll(
EntityManagerFactoryBuilder.this.properties.getProperties()); EntityManagerFactoryBuilder.this.properties.getProperties());
......
...@@ -22,6 +22,7 @@ import java.util.Map; ...@@ -22,6 +22,7 @@ import java.util.Map;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
...@@ -29,8 +30,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome; ...@@ -29,8 +30,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 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.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.ConditionContext;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -39,29 +41,30 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; ...@@ -39,29 +41,30 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA. * {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Josh Long
*/ */
@Configuration @Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
EnableTransactionManagement.class, EntityManager.class }) EnableTransactionManagement.class, EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class) @Conditional(HibernateEntityManagerCondition.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, JtaAutoConfiguration.class })
public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
private static final String JTA_PLATFORM = "hibernate.transaction.jta.platform";
@Autowired @Autowired
private JpaProperties properties; private JpaProperties properties;
@Autowired @Autowired
private DataSource dataSource; private DataSource dataSource;
@Autowired
private ConfigurableApplicationContext applicationContext;
@Override @Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() { protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter(); return new HibernateJpaVendorAdapter();
...@@ -74,6 +77,21 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration { ...@@ -74,6 +77,21 @@ public class HibernateJpaAutoConfiguration extends JpaBaseConfiguration {
return vendorProperties; return vendorProperties;
} }
@Override
protected void customizeVendorProperties(Map<String, Object> 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 { static class HibernateEntityManagerCondition extends SpringBootCondition {
private static String[] CLASS_NAMES = { private static String[] CLASS_NAMES = {
......
...@@ -44,6 +44,7 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter; ...@@ -44,6 +44,7 @@ import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager; 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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
...@@ -71,6 +72,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { ...@@ -71,6 +72,9 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@Autowired @Autowired
private JpaProperties jpaProperties; private JpaProperties jpaProperties;
@Autowired(required = false)
private JtaTransactionManager jtaTransactionManager;
@Bean @Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class) @ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager() { public PlatformTransactionManager transactionManager() {
...@@ -103,14 +107,24 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { ...@@ -103,14 +107,24 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@ConditionalOnMissingBean @ConditionalOnMissingBean
public LocalContainerEntityManagerFactoryBean entityManagerFactory( public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder factoryBuilder) { EntityManagerFactoryBuilder factoryBuilder) {
Map<String, Object> vendorProperties = getVendorProperties();
customizeVendorProperties(vendorProperties);
return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()) return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan())
.properties(getVendorProperties()).build(); .properties(vendorProperties).jta(isJta()).build();
} }
protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter(); protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
protected abstract Map<String, Object> getVendorProperties(); protected abstract Map<String, Object> 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<String, Object> vendorProperties) {
}
protected EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback() { protected EntityManagerFactoryBuilder.EntityManagerFactoryBeanCallback getVendorCallback() {
return null; return null;
} }
...@@ -127,6 +141,20 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware { ...@@ -127,6 +141,20 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) { 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 @Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException { public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
......
...@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log; ...@@ -25,7 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.autoconfigure.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.context.properties.ConfigurationProperties; 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.orm.jpa.vendor.Database;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
......
...@@ -20,11 +20,14 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ ...@@ -20,11 +20,14 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\ org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
org.springframework.boot.autoconfigure.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration,\ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
......
/*
* 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<String, Object> attributes = new HashMap<String, Object>();
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;
}
}
}
...@@ -20,47 +20,46 @@ import org.junit.Rule; ...@@ -20,47 +20,46 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; 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 * @author Maciej Walkowiak
*/ */
public class DriverClassNameProviderTests { public class DatabaseDriverTests {
private DriverClassNameProvider provider = new DriverClassNameProvider();
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
@Test @Test
public void classNameForKnownDatabase() { public void classNameForKnownDatabase() {
String driverClassName = this.provider String driverClassName = DatabaseDriver.fromJdbcUrl(
.getDriverClassName("jdbc:postgresql://hostname/dbname"); "jdbc:postgresql://hostname/dbname").getDriverClassName();
assertEquals("org.postgresql.Driver", driverClassName); assertEquals("org.postgresql.Driver", driverClassName);
} }
@Test @Test
public void nullForUnknownDatabase() { public void nullClassNameForUnknownDatabase() {
String driverClassName = this.provider String driverClassName = DatabaseDriver.fromJdbcUrl(
.getDriverClassName("jdbc:unknowndb://hostname/dbname"); "jdbc:unknowndb://hostname/dbname").getDriverClassName();
assertNull(driverClassName); assertNull(driverClassName);
} }
@Test @Test
public void failureOnNullJdbcUrl() { public void unknownOnNullJdbcUrl() {
this.thrown.expect(IllegalArgumentException.class); assertThat(DatabaseDriver.fromJdbcUrl(null), equalTo(DatabaseDriver.UNKNOWN));
this.thrown.expectMessage("JdbcUrl must not be null");
this.provider.getDriverClassName(null);
} }
@Test @Test
public void failureOnMalformedJdbcUrl() { public void failureOnMalformedJdbcUrl() {
this.thrown.expect(IllegalArgumentException.class); this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("JdbcUrl must start with"); this.thrown.expectMessage("URL must start with");
this.provider.getDriverClassName("malformed:url"); DatabaseDriver.fromJdbcUrl("malformed:url");
} }
} }
/*
* 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;
}
}
}
...@@ -24,7 +24,6 @@ import org.junit.Test; ...@@ -24,7 +24,6 @@ import org.junit.Test;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration; 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.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -49,6 +48,10 @@ import static org.junit.Assert.assertTrue; ...@@ -49,6 +48,10 @@ import static org.junit.Assert.assertTrue;
*/ */
public class JmsAutoConfigurationTests { 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; private AnnotationConfigApplicationContext context;
@Test @Test
...@@ -61,7 +64,7 @@ public class JmsAutoConfigurationTests { ...@@ -61,7 +64,7 @@ public class JmsAutoConfigurationTests {
.getBean(JmsMessagingTemplate.class); .getBean(JmsMessagingTemplate.class);
assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory); assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
assertEquals(jmsTemplate, messagingTemplate.getJmsTemplate()); assertEquals(jmsTemplate, messagingTemplate.getJmsTemplate());
assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL, assertEquals(ACTIVEMQ_EMBEDDED_URL,
((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory()) ((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory())
.getBrokerURL()); .getBrokerURL());
assertTrue("listener container factory should be created by default", assertTrue("listener container factory should be created by default",
...@@ -158,7 +161,7 @@ public class JmsAutoConfigurationTests { ...@@ -158,7 +161,7 @@ public class JmsAutoConfigurationTests {
assertNotNull(jmsTemplate); assertNotNull(jmsTemplate);
assertNotNull(connectionFactory); assertNotNull(connectionFactory);
assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory); assertEquals(jmsTemplate.getConnectionFactory(), connectionFactory);
assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL, assertEquals(ACTIVEMQ_NETWORK_URL,
((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory()) ((ActiveMQConnectionFactory) jmsTemplate.getConnectionFactory())
.getBrokerURL()); .getBrokerURL());
} }
...@@ -188,8 +191,7 @@ public class JmsAutoConfigurationTests { ...@@ -188,8 +191,7 @@ public class JmsAutoConfigurationTests {
assertEquals(jmsTemplate.getConnectionFactory(), pool); assertEquals(jmsTemplate.getConnectionFactory(), pool);
ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool
.getConnectionFactory(); .getConnectionFactory();
assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL, assertEquals(ACTIVEMQ_EMBEDDED_URL, factory.getBrokerURL());
factory.getBrokerURL());
} }
@Test @Test
...@@ -204,8 +206,7 @@ public class JmsAutoConfigurationTests { ...@@ -204,8 +206,7 @@ public class JmsAutoConfigurationTests {
assertEquals(jmsTemplate.getConnectionFactory(), pool); assertEquals(jmsTemplate.getConnectionFactory(), pool);
ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool ActiveMQConnectionFactory factory = (ActiveMQConnectionFactory) pool
.getConnectionFactory(); .getConnectionFactory();
assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL, assertEquals(ACTIVEMQ_NETWORK_URL, factory.getBrokerURL());
factory.getBrokerURL());
} }
@Test @Test
...@@ -257,6 +258,7 @@ public class JmsAutoConfigurationTests { ...@@ -257,6 +258,7 @@ public class JmsAutoConfigurationTests {
@Configuration @Configuration
protected static class TestConfiguration2 { protected static class TestConfiguration2 {
@Bean @Bean
ConnectionFactory connectionFactory() { ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory() { return new ActiveMQConnectionFactory() {
...@@ -265,10 +267,12 @@ public class JmsAutoConfigurationTests { ...@@ -265,10 +267,12 @@ public class JmsAutoConfigurationTests {
} }
}; };
} }
} }
@Configuration @Configuration
protected static class TestConfiguration3 { protected static class TestConfiguration3 {
@Bean @Bean
JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) { JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory); JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
...@@ -280,6 +284,7 @@ public class JmsAutoConfigurationTests { ...@@ -280,6 +284,7 @@ public class JmsAutoConfigurationTests {
@Configuration @Configuration
protected static class TestConfiguration4 implements BeanPostProcessor { protected static class TestConfiguration4 implements BeanPostProcessor {
@Override @Override
public Object postProcessAfterInitialization(Object bean, String beanName) public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException { throws BeansException {
...@@ -295,6 +300,7 @@ public class JmsAutoConfigurationTests { ...@@ -295,6 +300,7 @@ public class JmsAutoConfigurationTests {
throws BeansException { throws BeansException {
return bean; return bean;
} }
} }
@Configuration @Configuration
......
...@@ -17,67 +17,50 @@ ...@@ -17,67 +17,50 @@
package org.springframework.boot.autoconfigure.jms.activemq; package org.springframework.boot.autoconfigure.jms.activemq;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.core.env.StandardEnvironment;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
* Tests for {@link ActiveMQProperties}. * Tests for {@link ActiveMQProperties} and ActiveMQConnectionFactoryFactory.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
public class ActiveMQPropertiesTests { public class ActiveMQPropertiesTests {
private final ActiveMQProperties properties = new ActiveMQProperties(); private static final String DEFAULT_EMBEDDED_BROKER_URL = "vm://localhost?broker.persistent=false";
private final StandardEnvironment environment = new StandardEnvironment();
@Test private static final String DEFAULT_NETWORK_BROKER_URL = "tcp://localhost:61616";
public void determineBrokerUrlDefault() {
assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL,
ActiveMQProperties.determineBrokerUrl(this.environment));
}
@Test private final ActiveMQProperties properties = new ActiveMQProperties();
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 @Test
public void getBrokerUrlIsInMemoryByDefault() { public void getBrokerUrlIsInMemoryByDefault() {
assertEquals(ActiveMQProperties.DEFAULT_EMBEDDED_BROKER_URL, assertEquals(DEFAULT_EMBEDDED_BROKER_URL, new ActiveMQConnectionFactoryFactory(
this.properties.determineBrokerUrl()); this.properties).determineBrokerUrl());
} }
@Test @Test
public void getBrokerUrlUseExplicitBrokerUrl() { public void getBrokerUrlUseExplicitBrokerUrl() {
this.properties.setBrokerUrl("vm://foo-bar"); this.properties.setBrokerUrl("vm://foo-bar");
assertEquals("vm://foo-bar", this.properties.determineBrokerUrl()); assertEquals("vm://foo-bar",
new ActiveMQConnectionFactoryFactory(this.properties)
.determineBrokerUrl());
} }
@Test @Test
public void getBrokerUrlWithInMemorySetToFalse() { public void getBrokerUrlWithInMemorySetToFalse() {
this.properties.setInMemory(false); this.properties.setInMemory(false);
assertEquals(ActiveMQProperties.DEFAULT_NETWORK_BROKER_URL, assertEquals(DEFAULT_NETWORK_BROKER_URL, new ActiveMQConnectionFactoryFactory(
this.properties.determineBrokerUrl()); this.properties).determineBrokerUrl());
} }
@Test @Test
public void getExplicitBrokerUrlAlwaysWins() { public void getExplicitBrokerUrlAlwaysWins() {
this.properties.setBrokerUrl("vm://foo-bar"); this.properties.setBrokerUrl("vm://foo-bar");
this.properties.setInMemory(false); this.properties.setInMemory(false);
assertEquals("vm://foo-bar", this.properties.determineBrokerUrl()); assertEquals("vm://foo-bar",
new ActiveMQConnectionFactoryFactory(this.properties)
.determineBrokerUrl());
} }
} }
...@@ -43,11 +43,13 @@ import org.junit.Test; ...@@ -43,11 +43,13 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration; import org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jms.core.JmsTemplate; import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator; import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.SessionCallback; import org.springframework.jms.core.SessionCallback;
...@@ -183,10 +185,8 @@ public class HornetQAutoConfigurationTests { ...@@ -183,10 +185,8 @@ public class HornetQAutoConfigurationTests {
@Test @Test
public void embeddedServiceWithCustomJmsConfiguration() { public void embeddedServiceWithCustomJmsConfiguration() {
load(CustomJmsConfiguration.class, "spring.hornetq.embedded.queues=Queue1,Queue2"); // Ignored // Ignored with custom config
// with load(CustomJmsConfiguration.class, "spring.hornetq.embedded.queues=Queue1,Queue2");
// custom
// config
DestinationChecker checker = new DestinationChecker(this.context); DestinationChecker checker = new DestinationChecker(this.context);
checker.checkQueue("custom", true); // See CustomJmsConfiguration checker.checkQueue("custom", true); // See CustomJmsConfiguration
...@@ -317,7 +317,7 @@ public class HornetQAutoConfigurationTests { ...@@ -317,7 +317,7 @@ public class HornetQAutoConfigurationTests {
String... environment) { String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(config); applicationContext.register(config);
applicationContext.register(HornetQAutoConfiguration.class, applicationContext.register(HornetQAutoConfigurationWithoutXA.class,
JmsAutoConfiguration.class); JmsAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(applicationContext, environment); EnvironmentTestUtils.addEnvironment(applicationContext, environment);
applicationContext.refresh(); applicationContext.refresh();
...@@ -417,4 +417,11 @@ public class HornetQAutoConfigurationTests { ...@@ -417,4 +417,11 @@ public class HornetQAutoConfigurationTests {
} }
} }
@Configuration
@EnableConfigurationProperties(HornetQProperties.class)
@Import({ HornetQEmbeddedServerConfiguration.class,
HornetQConnectionFactoryConfiguration.class })
protected static class HornetQAutoConfigurationWithoutXA {
}
} }
/*
* 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);
}
}
}
...@@ -17,11 +17,12 @@ ...@@ -17,11 +17,12 @@
package org.springframework.boot.autoconfigure.orm.jpa; package org.springframework.boot.autoconfigure.orm.jpa;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collections; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
import org.junit.After; import org.junit.After;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
...@@ -228,8 +229,10 @@ public abstract class AbstractJpaAutoConfigurationTests { ...@@ -228,8 +229,10 @@ public abstract class AbstractJpaAutoConfigurationTests {
factoryBean.setJpaVendorAdapter(adapter); factoryBean.setJpaVendorAdapter(adapter);
factoryBean.setDataSource(dataSource); factoryBean.setDataSource(dataSource);
factoryBean.setPersistenceUnitName("manually-configured"); factoryBean.setPersistenceUnitName("manually-configured");
factoryBean.setJpaPropertyMap(Collections.singletonMap("configured", Map<String, Object> properties = new HashMap<String, Object>();
"manually")); properties.put("configured", "manually");
properties.put("hibernate.transaction.jta.platform", NoJtaPlatform.INSTANCE);
factoryBean.setJpaPropertyMap(properties);
return factoryBean; return factoryBean;
} }
} }
......
...@@ -48,6 +48,8 @@ ...@@ -48,6 +48,8 @@
<!-- Third Party --> <!-- Third Party -->
<activemq.version>5.9.1</activemq.version> <activemq.version>5.9.1</activemq.version>
<aspectj.version>1.8.2</aspectj.version> <aspectj.version>1.8.2</aspectj.version>
<atomikos.version>3.9.3</atomikos.version>
<bitronix.version>2.1.4</bitronix.version>
<codahale-metrics.version>3.0.2</codahale-metrics.version> <codahale-metrics.version>3.0.2</codahale-metrics.version>
<commons-beanutils.version>1.9.2</commons-beanutils.version> <commons-beanutils.version>1.9.2</commons-beanutils.version>
<commons-collections.version>3.2.1</commons-collections.version> <commons-collections.version>3.2.1</commons-collections.version>
...@@ -85,6 +87,7 @@ ...@@ -85,6 +87,7 @@
<jolokia.version>1.2.2</jolokia.version> <jolokia.version>1.2.2</jolokia.version>
<json-path.version>0.9.1</json-path.version> <json-path.version>0.9.1</json-path.version>
<jstl.version>1.2</jstl.version> <jstl.version>1.2</jstl.version>
<jta.version>1.1</jta.version>
<junit.version>4.11</junit.version> <junit.version>4.11</junit.version>
<liquibase.version>3.0.8</liquibase.version> <liquibase.version>3.0.8</liquibase.version>
<log4j.version>1.2.17</log4j.version> <log4j.version>1.2.17</log4j.version>
...@@ -257,6 +260,21 @@ ...@@ -257,6 +260,21 @@
<artifactId>spring-boot-starter-jetty</artifactId> <artifactId>spring-boot-starter-jetty</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version> <version>1.2.0.BUILD-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-bitronix</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-arjuna</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId> <artifactId>spring-boot-starter-log4j</artifactId>
...@@ -350,6 +368,21 @@ ...@@ -350,6 +368,21 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>${logback.version}</version> <version>${logback.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jms</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>${atomikos.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.codahale.metrics</groupId> <groupId>com.codahale.metrics</groupId>
<artifactId>metrics-graphite</artifactId> <artifactId>metrics-graphite</artifactId>
...@@ -370,6 +403,11 @@ ...@@ -370,6 +403,11 @@
<artifactId>metrics-servlets</artifactId> <artifactId>metrics-servlets</artifactId>
<version>${codahale-metrics.version}</version> <version>${codahale-metrics.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<version>${bitronix.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.janino</groupId> <groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId> <artifactId>janino</artifactId>
...@@ -482,6 +520,11 @@ ...@@ -482,6 +520,11 @@
<artifactId>jstl</artifactId> <artifactId>jstl</artifactId>
<version>${jstl.version}</version> <version>${jstl.version}</version>
</dependency> </dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>${jta.version}</version>
</dependency>
<dependency> <dependency>
<groupId>jaxen</groupId> <groupId>jaxen</groupId>
<artifactId>jaxen</artifactId> <artifactId>jaxen</artifactId>
......
...@@ -205,6 +205,10 @@ content into your application; rather pick only the properties that you need. ...@@ -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.jpa.hibernate.ddl-auto= # defaults to create-drop for embedded dbs
spring.data.jpa.repositories.enabled=true # if spring data repository support is enabled 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}]) # SOLR ({sc-spring-boot-autoconfigure}/solr/SolrProperties.{sc-ext}[SolrProperties}])
spring.data.solr.host=http://127.0.0.1:8983/solr spring.data.solr.host=http://127.0.0.1:8983/solr
spring.data.solr.zkHost= spring.data.solr.zkHost=
......
...@@ -1874,6 +1874,83 @@ own beans: ...@@ -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 <<boot-features-connecting-to-a-jndi-datasource, `spring.datasource.jndi-name` property>>
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]] [[boot-features-integration]]
== Spring Integration == Spring Integration
Spring Integration provides abstractions over messaging and also other transports such as Spring Integration provides abstractions over messaging and also other transports such as
......
...@@ -255,6 +255,12 @@ and Hibernate. ...@@ -255,6 +255,12 @@ and Hibernate.
|`spring-boot-starter-jdbc` |`spring-boot-starter-jdbc`
|JDBC Database support. |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` |`spring-boot-starter-mobile`
|Support for `spring-mobile` |Support for `spring-mobile`
......
...@@ -38,6 +38,9 @@ ...@@ -38,6 +38,9 @@
<module>spring-boot-sample-hornetq</module> <module>spring-boot-sample-hornetq</module>
<module>spring-boot-sample-integration</module> <module>spring-boot-sample-integration</module>
<module>spring-boot-sample-jetty</module> <module>spring-boot-sample-jetty</module>
<module>spring-boot-sample-jta-atomikos</module>
<module>spring-boot-sample-jta-bitronix</module>
<module>spring-boot-sample-jta-jndi</module>
<module>spring-boot-sample-liquibase</module> <module>spring-boot-sample-liquibase</module>
<module>spring-boot-sample-parent-context</module> <module>spring-boot-sample-parent-context</module>
<module>spring-boot-sample-profile</module> <module>spring-boot-sample-profile</module>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-jta-atomikos</artifactId>
<name>Spring Boot Atomikos JTA Sample</name>
<description>Spring Boot Atomikos JTA Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hornetq</artifactId>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
/*
* 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;
}
}
/*
* 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<Account, Long> {
}
/*
* 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");
}
}
}
/*
* 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);
}
}
/*
* 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();
}
}
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=accounts
logging.level.com.atomikos=WARN
/*
* 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;
}
};
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-jta-bitronix</artifactId>
<name>Spring Boot Bitronix JTA Sample</name>
<description>Spring Boot Bitronix JTA Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-bitronix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hornetq</artifactId>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
/*
* 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;
}
}
/*
* 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<Account, Long> {
}
/*
* 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");
}
}
}
/*
* 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);
}
}
/*
* 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();
}
}
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=accounts
/*
* 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;
}
};
}
}
## 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]
----
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.0" name="org.postgresql">
<resources>
<resource-root path="postgresql-9.3-1102.jdbc41.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>
----
## 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"]
----
<datasources>
...
<xa-datasource
jndi-name="java:jboss/datasources/bootdemo"
pool-name="CrmXADS"
enabled="true">
<xa-datasource-property name="url">jdbc:postgresql://localhost:5432/crm</xa-datasource-property>
<driver>postgres</driver>
<xa-pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>20</max-pool-size>
<prefill>true</prefill>
</xa-pool>
<security>
<user-name>springboot</user-name>
<password>springboot</password>
</security>
</xa-datasource>
<drivers>
...
<driver name="postgres" module="org.postgresql">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
----
### 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"]
----
<jms-destinations>
<jms-queue name="accounts">
<entry name="java:/jms/queue/bootdemo"/>
</jms-queue>
...
</jms-destinations>
----
## 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.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-jta-jndi</artifactId>
<name>Spring Boot JNDI JTA Sample</name>
<packaging>war</packaging>
<description>Spring Boot JNDI JTA Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>jms-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>spring-boot-sample-jta-jndi</finalName>
</build>
</project>
/*
* 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;
}
}
/*
* 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<Account, Long> {
}
/*
* 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");
}
}
}
/*
* 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);
}
}
/*
* 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 {
}
/*
* 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);
}
}
/*
* 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;
}
}
spring.jpa.generate-ddl=true
spring.datasource.jndi-name=java:jboss/datasources/bootdemo
# Workaround SPR-12118
spring.jpa.open-in-view=false
...@@ -36,6 +36,8 @@ ...@@ -36,6 +36,8 @@
<module>spring-boot-starter-integration</module> <module>spring-boot-starter-integration</module>
<module>spring-boot-starter-jdbc</module> <module>spring-boot-starter-jdbc</module>
<module>spring-boot-starter-jetty</module> <module>spring-boot-starter-jetty</module>
<module>spring-boot-starter-jta-atomikos</module>
<module>spring-boot-starter-jta-bitronix</module>
<module>spring-boot-starter-logging</module> <module>spring-boot-starter-logging</module>
<module>spring-boot-starter-log4j</module> <module>spring-boot-starter-log4j</module>
<module>spring-boot-starter-mobile</module> <module>spring-boot-starter-mobile</module>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
<name>Spring Boot Atomikos JTA Starter</name>
<description>Spring Boot Atomikos JTA Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jms</artifactId>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-jta-bitronix</artifactId>
<name>Spring Boot Bitronix JTA Starter</name>
<description>Spring Boot Bitronix JTA Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>jms-api</artifactId>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
...@@ -34,11 +34,31 @@ ...@@ -34,11 +34,31 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jms</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>jms-api</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
...@@ -69,6 +89,11 @@ ...@@ -69,6 +89,11 @@
<artifactId>tomcat-embed-jasper</artifactId> <artifactId>tomcat-embed-jasper</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.groovy</groupId> <groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId> <artifactId>groovy</artifactId>
......
/*
* 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;
}
/*
* 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;
}
/*
* 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();
}
}
/*
* 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();
}
}
/*
* 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 <a
* href="http://www.atomikos.com/Documentation/SpringIntegration">correct Atomikos
* ordering</a>.
*
* @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<String> dependsOn = new LinkedHashSet<String>(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<String> dependsOn = new LinkedHashSet<String>(asList(bean.getDependsOn()));
dependsOn.addAll(asList(transactionManagers));
bean.setDependsOn(dependsOn.toArray(new String[dependsOn.size()]));
}
}
private void addDependencies(ConfigurableListableBeanFactory beanFactory,
String type, Set<String> 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<String> asList(String[] array) {
return (array == null ? Collections.<String> emptyList() : Arrays.asList(array));
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
/*
* 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
}
/*
* 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 <a
* href="http://www.atomikos.com/Documentation/JtaProperties">Atomikos configuration
* properties</a>. 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<String, String> values = new TreeMap<String, String>();
/**
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment