Remove support for Atomikos

Closes gh-28589
This commit is contained in:
Andy Wilkinson 2021-11-10 20:17:53 +00:00
parent 900085628a
commit ef02cc9bff
32 changed files with 31 additions and 1813 deletions

View File

@ -12,8 +12,6 @@ description = "Spring Boot AutoConfigure"
dependencies {
api(project(":spring-boot-project:spring-boot"))
optional("com.atomikos:transactions-jdbc")
optional("com.atomikos:transactions-jta")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
@ -94,6 +92,7 @@ dependencies {
optional("org.aspectj:aspectjweaver")
optional("org.eclipse.jetty:jetty-webapp") {
exclude group: "javax.servlet", module: "javax.servlet-api"
exclude(group: "org.eclipse.jetty", module: "jetty-jndi")
}
optional("org.eclipse.jetty:jetty-reactive-httpclient")
optional("org.eclipse.jetty.websocket:javax-websocket-server-impl") {

View File

@ -1,124 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.transaction.jta;
import java.io.File;
import java.util.Properties;
import javax.jms.Message;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import com.atomikos.icatch.config.UserTransactionService;
import com.atomikos.icatch.config.UserTransactionServiceImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.boot.jms.XAConnectionFactoryWrapper;
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.boot.system.ApplicationHome;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.util.StringUtils;
/**
* JTA Configuration for <A href="https://www.atomikos.com/">Atomikos</a>.
*
* @author Josh Long
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Kazuki Shimizu
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ AtomikosProperties.class, JtaProperties.class })
@ConditionalOnClass({ JtaTransactionManager.class, UserTransactionManager.class })
@ConditionalOnMissingBean(org.springframework.transaction.TransactionManager.class)
class AtomikosJtaConfiguration {
@Bean(initMethod = "init", destroyMethod = "shutdownWait")
@ConditionalOnMissingBean(UserTransactionService.class)
UserTransactionServiceImp userTransactionService(AtomikosProperties atomikosProperties,
JtaProperties jtaProperties) {
Properties properties = new Properties();
if (StringUtils.hasText(jtaProperties.getTransactionManagerId())) {
properties.setProperty("com.atomikos.icatch.tm_unique_name", jtaProperties.getTransactionManagerId());
}
properties.setProperty("com.atomikos.icatch.log_base_dir", getLogBaseDir(jtaProperties));
properties.putAll(atomikosProperties.asProperties());
return new UserTransactionServiceImp(properties);
}
private String getLogBaseDir(JtaProperties jtaProperties) {
if (StringUtils.hasLength(jtaProperties.getLogDir())) {
return jtaProperties.getLogDir();
}
File home = new ApplicationHome().getDir();
return new File(home, "transaction-logs").getAbsolutePath();
}
@Bean(initMethod = "init", destroyMethod = "close")
@ConditionalOnMissingBean(TransactionManager.class)
UserTransactionManager atomikosTransactionManager(UserTransactionService userTransactionService) throws Exception {
UserTransactionManager manager = new UserTransactionManager();
manager.setStartupTransactionService(false);
manager.setForceShutdown(true);
return manager;
}
@Bean
@ConditionalOnMissingBean(XADataSourceWrapper.class)
AtomikosXADataSourceWrapper xaDataSourceWrapper() {
return new AtomikosXADataSourceWrapper();
}
@Bean
@ConditionalOnMissingBean
static AtomikosDependsOnBeanFactoryPostProcessor atomikosDependsOnBeanFactoryPostProcessor() {
return new AtomikosDependsOnBeanFactoryPostProcessor();
}
@Bean
JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, transactionManager);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(jtaTransactionManager));
return jtaTransactionManager;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Message.class)
static class AtomikosJtaJmsConfiguration {
@Bean
@ConditionalOnMissingBean(XAConnectionFactoryWrapper.class)
AtomikosXAConnectionFactoryWrapper xaConnectionFactoryWrapper() {
return new AtomikosXAConnectionFactoryWrapper();
}
}
}

View File

@ -40,7 +40,7 @@ import org.springframework.context.annotation.Import;
@ConditionalOnProperty(prefix = "spring.jta", value = "enabled", matchIfMissing = true)
@AutoConfigureBefore({ XADataSourceAutoConfiguration.class, ActiveMQAutoConfiguration.class,
ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@Import({ JndiJtaConfiguration.class, AtomikosJtaConfiguration.class })
@Import(JndiJtaConfiguration.class)
public class JtaAutoConfiguration {
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.transaction.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
* @author Andy Wilkinson
* @since 1.2.0
*/
@ConfigurationProperties(prefix = "spring.jta", ignoreUnknownFields = true)
public class JtaProperties {
/**
* Transaction logs directory.
*/
private String logDir;
/**
* Transaction manager unique identifier.
*/
private String transactionManagerId;
public void setLogDir(String logDir) {
this.logDir = logDir;
}
public String getLogDir() {
return this.logDir;
}
public String getTransactionManagerId() {
return this.transactionManagerId;
}
public void setTransactionManagerId(String transactionManagerId) {
this.transactionManagerId = transactionManagerId;
}
}

View File

@ -75,6 +75,7 @@ import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.jta.JtaTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
@ -214,7 +215,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test
void jtaDefaultPlatform() {
contextRunner().withConfiguration(AutoConfigurations.of(JtaAutoConfiguration.class))
contextRunner().withUserConfiguration(JtaTransactionManagerConfiguration.class)
.run(assertJtaPlatform(SpringJtaPlatform.class));
}
@ -298,7 +299,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test
void providerDisablesAutoCommitIsNotConfiguredWithJta() {
contextRunner().withConfiguration(AutoConfigurations.of(JtaAutoConfiguration.class))
contextRunner().withUserConfiguration(JtaTransactionManagerConfiguration.class)
.withPropertyValues("spring.datasource.type:" + HikariDataSource.class.getName(),
"spring.datasource.hikari.auto-commit:false")
.run((context) -> {
@ -677,4 +678,17 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
}
@Configuration(proxyBeanMethods = false)
static class JtaTransactionManagerConfiguration {
@Bean
JtaTransactionManager jtaTransactionManager() {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransaction(mock(UserTransaction.class));
jtaTransactionManager.setTransactionManager(mock(TransactionManager.class));
return jtaTransactionManager;
}
}
}

View File

@ -16,29 +16,16 @@
package org.springframework.boot.autoconfigure.transaction.jta;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import javax.jms.ConnectionFactory;
import javax.jms.TemporaryQueue;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;
import javax.jms.XASession;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import javax.transaction.xa.XAResource;
import com.atomikos.icatch.config.UserTransactionService;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jms.AtomikosConnectionFactoryBean;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.AfterEachCallback;
@ -49,21 +36,13 @@ import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.osjava.sj.loader.JndiLoader;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.boot.jms.XAConnectionFactoryWrapper;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.jta.atomikos.AtomikosDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.jta.atomikos.AtomikosProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -72,7 +51,6 @@ import org.springframework.transaction.jta.UserTransactionAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
@ -86,9 +64,6 @@ import static org.mockito.Mockito.mock;
*/
class JtaAutoConfigurationTests {
private final File atomikosLogs = new File(new BuildOutput(JtaAutoConfigurationTests.class).getRootLocation(),
"atomikos-logs");
private AnnotationConfigApplicationContext context;
@AfterEach
@ -132,81 +107,14 @@ class JtaAutoConfigurationTests {
}
@Test
void disableJtaSupport() {
@ExtendWith(JndiExtension.class)
void disableJtaSupport(InitialContext initialContext) throws NamingException {
new JndiEntry("java:comp/UserTransaction", UserTransaction.class).register(initialContext);
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.jta.enabled:false").applyTo(this.context);
this.context.register(JtaAutoConfiguration.class);
this.context.refresh();
assertThat(this.context.getBeansOfType(JtaTransactionManager.class)).isEmpty();
assertThat(this.context.getBeansOfType(XADataSourceWrapper.class)).isEmpty();
assertThat(this.context.getBeansOfType(XAConnectionFactoryWrapper.class)).isEmpty();
}
@Test
void atomikosSanityCheck() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.jta.log-dir:" + this.atomikosLogs).applyTo(this.context);
this.context.register(JtaProperties.class, AtomikosJtaConfiguration.class);
this.context.refresh();
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
void defaultAtomikosTransactionManagerName(@TempDir Path dir) {
this.context = new AnnotationConfigApplicationContext();
File logs = new File(dir.toFile(), "jta");
TestPropertyValues.of("spring.jta.logDir:" + logs.getAbsolutePath()).applyTo(this.context);
this.context.register(AtomikosJtaConfiguration.class);
this.context.refresh();
File epochFile = new File(logs, "tmlog0.log");
assertThat(epochFile.isFile()).isTrue();
}
@Test
void atomikosConnectionFactoryPoolConfiguration() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.jta.atomikos.connectionfactory.minPoolSize:5",
"spring.jta.atomikos.connectionfactory.maxPoolSize:10", "spring.jta.log-dir:" + this.atomikosLogs)
.applyTo(this.context);
this.context.register(AtomikosJtaConfiguration.class, PoolConfiguration.class);
this.context.refresh();
AtomikosConnectionFactoryBean connectionFactory = this.context.getBean(AtomikosConnectionFactoryBean.class);
assertThat(connectionFactory.getMinPoolSize()).isEqualTo(5);
assertThat(connectionFactory.getMaxPoolSize()).isEqualTo(10);
}
@Test
void atomikosDataSourcePoolConfiguration() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.jta.atomikos.datasource.minPoolSize:5",
"spring.jta.atomikos.datasource.maxPoolSize:10", "spring.jta.log-dir:" + this.atomikosLogs)
.applyTo(this.context);
this.context.register(AtomikosJtaConfiguration.class, PoolConfiguration.class);
this.context.refresh();
AtomikosDataSourceBean dataSource = this.context.getBean(AtomikosDataSourceBean.class);
assertThat(dataSource.getMinPoolSize()).isEqualTo(5);
assertThat(dataSource.getMaxPoolSize()).isEqualTo(10);
}
@Test
void atomikosCustomizeJtaTransactionManagerUsingProperties() {
this.context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.transaction.default-timeout:30",
"spring.transaction.rollback-on-commit-failure:true", "spring.jta.log-dir:" + this.atomikosLogs)
.applyTo(this.context);
this.context.register(AtomikosJtaConfiguration.class, TransactionAutoConfiguration.class);
this.context.refresh();
JtaTransactionManager transactionManager = this.context.getBean(JtaTransactionManager.class);
assertThat(transactionManager.getDefaultTimeout()).isEqualTo(30);
assertThat(transactionManager.isRollbackOnCommitFailure()).isTrue();
}
@Configuration(proxyBeanMethods = false)
@ -219,31 +127,6 @@ class JtaAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class PoolConfiguration {
@Bean
ConnectionFactory pooledConnectionFactory(XAConnectionFactoryWrapper wrapper) throws Exception {
XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
XAConnection connection = mock(XAConnection.class);
XASession session = mock(XASession.class);
TemporaryQueue queue = mock(TemporaryQueue.class);
XAResource resource = mock(XAResource.class);
given(connectionFactory.createXAConnection()).willReturn(connection);
given(connection.createXASession()).willReturn(session);
given(session.createTemporaryQueue()).willReturn(queue);
given(session.getXAResource()).willReturn(resource);
return wrapper.wrapConnectionFactory(connectionFactory);
}
@Bean
DataSource pooledDataSource(XADataSourceWrapper wrapper) throws Exception {
XADataSource dataSource = mock(XADataSource.class);
return wrapper.wrapDataSource(dataSource);
}
}
private static final class JndiEntry {
private final String name;

View File

@ -793,10 +793,9 @@ features.caching.provider.caffeine=io.caching.provider.caffeine
features.caching.provider.simple=io.caching.provider.simple
features.caching.provider.none=io.caching.provider.none
features.jta=io.jta
features.jta.atomikos=io.jta.atomikos
features.jta.javaee=io.jta.javaee
features.jta.javaee=io.jta.jakartaee
features.jta.mixing-xa-and-non-xa-connections=io.jta.mixing-xa-and-non-xa-connections
features.jta.supporting-alternative-embedded-transaction-manager=io.jta.supporting-alternative-embedded-transaction-manager
features.jta.supporting-alternative-embedded-transaction-manager=io.jta.supporting-embedded-transaction-manager
features.email=io.email
features.quartz=io.quartz
features.resttemplate=io.rest-client.resttemplate

View File

@ -1,7 +1,6 @@
[[io.jta]]
== Distributed Transactions with JTA
Spring Boot supports distributed JTA transactions across multiple XA resources by using an https://www.atomikos.com/[Atomikos] embedded transaction manager.
JTA transactions are also supported when deploying to a suitable Java EE Application Server.
Spring Boot supports distributed JTA transactions across multiple XA resources by using a transaction manager retrieved from JNDI.
When a JTA environment is detected, Spring's `JtaTransactionManager` is used to manage transactions.
Auto-configured JMS, DataSource, and JPA beans are upgraded to support XA transactions.
@ -10,28 +9,11 @@ If you are within a JTA environment and still want to use local transactions, yo
[[io.jta.atomikos]]
=== Using an Atomikos Transaction Manager
https://www.atomikos.com/[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 to pull in the appropriate Atomikos libraries.
Spring Boot auto-configures Atomikos and ensures that appropriate `depends-on` settings are applied to your Spring beans for correct startup and shutdown ordering.
By default, Atomikos transaction logs are written to a `transaction-logs` directory in your application's home directory (the directory in which your application jar file resides).
You can customize the location of this directory by setting a configprop:spring.jta.log-dir[] property in your `application.properties` file.
Properties starting with `spring.jta.atomikos.properties` can also be used to customize the Atomikos `UserTransactionServiceImp`.
See the {spring-boot-module-api}/jta/atomikos/AtomikosProperties.html[`AtomikosProperties` Javadoc] for complete details.
NOTE: To ensure that multiple transaction managers can safely coordinate the same resource managers, each Atomikos instance must be configured with a unique ID.
By default, this ID is the IP address of the machine on which Atomikos is running.
To ensure uniqueness in production, you should configure the configprop:spring.jta.transaction-manager-id[] property with a different value for each instance of your application.
[[io.jta.javaee]]
=== Using a Java EE Managed Transaction Manager
If you package your Spring Boot application as a `war` or `ear` file and deploy it to a Java EE application server, you can use your application server's built-in transaction manager.
[[io.jta.jakartaee]]
=== Using a Jakarta EE Managed Transaction Manager
If you package your Spring Boot application as a `war` or `ear` file and deploy it to a Jakarta EE application server, you can use your application server's built-in transaction manager.
Spring Boot tries to auto-configure a transaction manager by looking at common JNDI locations (`java:comp/UserTransaction`, `java:comp/TransactionManager`, and so on).
If you use a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI.
When using a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI.
Spring Boot tries to auto-configure JMS by looking for a `ConnectionFactory` at the JNDI path (`java:/JmsXA` or `java:/XAConnectionFactory`), and you can use the <<data#data.sql.datasource.jndi, configprop:spring.datasource.jndi-name[] property>> to configure your `DataSource`.
@ -65,10 +47,8 @@ include::{docs-java}/io/jta/mixingxaandnonxaconnections/xa/MyBean.java[tag=*]
[[io.jta.supporting-alternative-embedded-transaction-manager]]
=== Supporting an Alternative Embedded Transaction Manager
The {spring-boot-module-code}/jms/XAConnectionFactoryWrapper.java[`XAConnectionFactoryWrapper`] and {spring-boot-module-code}/jdbc/XADataSourceWrapper.java[`XADataSourceWrapper`] interfaces can be used to support alternative embedded transaction managers.
[[io.jta.supporting-embedded-transaction-manager]]
=== Supporting an Embedded Transaction Manager
The {spring-boot-module-code}/jms/XAConnectionFactoryWrapper.java[`XAConnectionFactoryWrapper`] and {spring-boot-module-code}/jdbc/XADataSourceWrapper.java[`XADataSourceWrapper`] interfaces can be used to support embedded transaction managers.
The interfaces are responsible for wrapping `XAConnectionFactory` and `XADataSource` beans and exposing them as regular `ConnectionFactory` and `DataSource` beans, which transparently enroll in the distributed transaction.
DataSource and JMS auto-configuration use JTA variants, provided you have a `JtaTransactionManager` bean and appropriate XA wrapper beans registered within your `ApplicationContext`.
The {spring-boot-module-code}/jta/atomikos/AtomikosXAConnectionFactoryWrapper.java[AtomikosXAConnectionFactoryWrapper] and {spring-boot-module-code}/jta/atomikos/AtomikosXADataSourceWrapper.java[AtomikosXADataSourceWrapper] provide good examples of how to write XA wrappers.

View File

@ -1,13 +0,0 @@
plugins {
id "org.springframework.boot.starter"
}
description = "Starter for JTA transactions using Atomikos"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("com.atomikos:transactions-jms")
api("com.atomikos:transactions-jta")
api("com.atomikos:transactions-jdbc")
api("jakarta.transaction:jakarta.transaction-api")
}

View File

@ -22,9 +22,6 @@ dependencies {
api("org.springframework:spring-context")
optional("ch.qos.logback:logback-classic")
optional("com.atomikos:transactions-jdbc")
optional("com.atomikos:transactions-jms")
optional("com.atomikos:transactions-jta")
optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.h2database:h2")
optional("com.google.code.gson:gson")

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/**
* Spring friendly version of {@link com.atomikos.jms.AtomikosConnectionFactoryBean}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.2.0
*/
@SuppressWarnings("serial")
@ConfigurationProperties(prefix = "spring.jta.atomikos.connectionfactory")
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();
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
/**
* Spring friendly version of {@link com.atomikos.jdbc.AtomikosDataSourceBean}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.2.0
*/
@SuppressWarnings("serial")
@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource")
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();
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 com.atomikos.icatch.jta.UserTransactionManager;
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 org.springframework.util.StringUtils;
/**
* {@link BeanFactoryPostProcessor} to automatically setup the recommended
* {@link BeanDefinition#setDependsOn(String[]) dependsOn} settings for
* <a href="https://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<>(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(StringUtils.toStringArray(dependsOn));
}
}
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<>(asList(bean.getDependsOn()));
dependsOn.addAll(asList(transactionManagers));
bean.setDependsOn(StringUtils.toStringArray(dependsOn));
}
}
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 | NoClassDefFoundError ex) {
// Ignore
}
return NO_BEANS;
}
private List<String> asList(String[] array) {
return (array != null) ? Arrays.asList(array) : Collections.emptyList();
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@ -1,425 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.time.Duration;
import java.util.Properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Bean friendly variant of
* <a href="https://www.atomikos.com/Documentation/JtaProperties">Atomikos configuration
* properties</a>. Allows for setter based configuration and is amiable to relaxed data
* binding.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.2.0
* @see #asProperties()
*/
@ConfigurationProperties(prefix = "spring.jta.atomikos.properties")
public class AtomikosProperties {
/**
* Transaction manager implementation that should be started.
*/
private String service;
/**
* Maximum timeout that can be allowed for transactions.
*/
private Duration maxTimeout = Duration.ofMillis(300000);
/**
* Default timeout for JTA transactions.
*/
private Duration defaultJtaTimeout = Duration.ofMillis(10000);
/**
* Maximum number of active transactions.
*/
private int maxActives = 50;
/**
* Whether to enable disk logging.
*/
private boolean enableLogging = true;
/**
* 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.
*/
private String transactionManagerUniqueName;
/**
* Whether sub-transactions should be joined when possible.
*/
private boolean serialJtaTransactions = true;
/**
* Specify whether sub-transactions are allowed.
*/
private boolean allowSubTransactions = true;
/**
* Whether a VM shutdown should trigger forced shutdown of the transaction core.
*/
private boolean forceShutdownOnVmExit;
/**
* How long should normal shutdown (no-force) wait for transactions to complete.
*/
private long defaultMaxWaitTimeOnShutdown = Long.MAX_VALUE;
/**
* Transactions log file base name.
*/
private String logBaseName = "tmlog";
/**
* Directory in which the log files should be stored. Defaults to the current working
* directory.
*/
private String logBaseDir;
/**
* Interval between checkpoints, expressed as the number of log writes between two
* checkpoints. A checkpoint reduces the log file size at the expense of adding some
* overhead in the runtime.
*/
private long checkpointInterval = 500;
/**
* Whether to use different (and concurrent) threads for two-phase commit on the
* participating resources.
*/
private boolean threadedTwoPhaseCommit;
private final Recovery recovery = new Recovery();
/**
* 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) {
this.service = service;
}
public String getService() {
return this.service;
}
/**
* Specifies the maximum timeout 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(Duration maxTimeout) {
this.maxTimeout = maxTimeout;
}
public Duration getMaxTimeout() {
return this.maxTimeout;
}
/**
* The default timeout for JTA transactions (optional, defaults to {@literal 10000}
* ms).
* @param defaultJtaTimeout the default JTA timeout
*/
public void setDefaultJtaTimeout(Duration defaultJtaTimeout) {
this.defaultJtaTimeout = defaultJtaTimeout;
}
public Duration getDefaultJtaTimeout() {
return this.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 maxActives the max activities
*/
public void setMaxActives(int maxActives) {
this.maxActives = maxActives;
}
public int getMaxActives() {
return this.maxActives;
}
/**
* 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) {
this.enableLogging = enableLogging;
}
public boolean isEnableLogging() {
return this.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 https://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) {
this.transactionManagerUniqueName = uniqueName;
}
public String getTransactionManagerUniqueName() {
return this.transactionManagerUniqueName;
}
/**
* 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 subtransactions. 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 transactions are supported
*/
public void setSerialJtaTransactions(boolean serialJtaTransactions) {
this.serialJtaTransactions = serialJtaTransactions;
}
public boolean isSerialJtaTransactions() {
return this.serialJtaTransactions;
}
public void setAllowSubTransactions(boolean allowSubTransactions) {
this.allowSubTransactions = allowSubTransactions;
}
public boolean isAllowSubTransactions() {
return this.allowSubTransactions;
}
/**
* Specifies whether VM shutdown should trigger forced shutdown of the transaction
* core. Defaults to false.
* @param forceShutdownOnVmExit if VM shutdown should be forced
*/
public void setForceShutdownOnVmExit(boolean forceShutdownOnVmExit) {
this.forceShutdownOnVmExit = forceShutdownOnVmExit;
}
public boolean isForceShutdownOnVmExit() {
return this.forceShutdownOnVmExit;
}
/**
* Specifies how long should a normal shutdown (no-force) wait for transactions to
* complete. Defaults to {@literal Long.MAX_VALUE}.
* @param defaultMaxWaitTimeOnShutdown the default max wait time on shutdown
*/
public void setDefaultMaxWaitTimeOnShutdown(long defaultMaxWaitTimeOnShutdown) {
this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown;
}
public long getDefaultMaxWaitTimeOnShutdown() {
return this.defaultMaxWaitTimeOnShutdown;
}
/**
* 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) {
this.logBaseName = logBaseName;
}
public String getLogBaseName() {
return this.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) {
this.logBaseDir = logBaseDir;
}
public String getLogBaseDir() {
return this.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) {
this.checkpointInterval = checkpointInterval;
}
public long getCheckpointInterval() {
return this.checkpointInterval;
}
/**
* 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) {
this.threadedTwoPhaseCommit = threadedTwoPhaseCommit;
}
public boolean isThreadedTwoPhaseCommit() {
return this.threadedTwoPhaseCommit;
}
public Recovery getRecovery() {
return this.recovery;
}
/**
* Returns the properties as a {@link Properties} object that can be used with
* Atomikos.
* @return the properties
*/
public Properties asProperties() {
Properties properties = new Properties();
set(properties, "service", getService());
set(properties, "max_timeout", getMaxTimeout());
set(properties, "default_jta_timeout", getDefaultJtaTimeout());
set(properties, "max_actives", getMaxActives());
set(properties, "enable_logging", isEnableLogging());
set(properties, "tm_unique_name", getTransactionManagerUniqueName());
set(properties, "serial_jta_transactions", isSerialJtaTransactions());
set(properties, "allow_subtransactions", isAllowSubTransactions());
set(properties, "force_shutdown_on_vm_exit", isForceShutdownOnVmExit());
set(properties, "default_max_wait_time_on_shutdown", getDefaultMaxWaitTimeOnShutdown());
set(properties, "log_base_name", getLogBaseName());
set(properties, "log_base_dir", getLogBaseDir());
set(properties, "checkpoint_interval", getCheckpointInterval());
set(properties, "threaded_2pc", isThreadedTwoPhaseCommit());
Recovery recovery = getRecovery();
set(properties, "forget_orphaned_log_entries_delay", recovery.getForgetOrphanedLogEntriesDelay());
set(properties, "recovery_delay", recovery.getDelay());
set(properties, "oltp_max_retries", recovery.getMaxRetries());
set(properties, "oltp_retry_interval", recovery.getRetryInterval());
return properties;
}
private void set(Properties properties, String key, Object value) {
String id = "com.atomikos.icatch." + key;
if (value != null && !properties.containsKey(id)) {
properties.setProperty(id, asString(value));
}
}
private String asString(Object value) {
if (value instanceof Duration) {
return String.valueOf(((Duration) value).toMillis());
}
return value.toString();
}
/**
* Recovery specific settings.
*/
public static class Recovery {
/**
* Delay after which recovery can cleanup pending ('orphaned') log entries.
*/
private Duration forgetOrphanedLogEntriesDelay = Duration.ofMillis(86400000);
/**
* Delay between two recovery scans.
*/
private Duration delay = Duration.ofMillis(10000);
/**
* Number of retry attempts to commit the transaction before throwing an
* exception.
*/
private int maxRetries = 5;
/**
* Delay between retry attempts.
*/
private Duration retryInterval = Duration.ofMillis(10000);
public Duration getForgetOrphanedLogEntriesDelay() {
return this.forgetOrphanedLogEntriesDelay;
}
public void setForgetOrphanedLogEntriesDelay(Duration forgetOrphanedLogEntriesDelay) {
this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay;
}
public Duration getDelay() {
return this.delay;
}
public void setDelay(Duration delay) {
this.delay = delay;
}
public int getMaxRetries() {
return this.maxRetries;
}
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public Duration getRetryInterval() {
return this.retryInterval;
}
public void setRetryInterval(Duration retryInterval) {
this.retryInterval = retryInterval;
}
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.jms.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;
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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.jdbc.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;
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 classes for Atomikos JTA.
*/
package org.springframework.boot.jta.atomikos;

View File

@ -1,20 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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;

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jta.atomikos;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AtomikosConnectionFactoryBean}.
*
* @author Phillip Webb
*/
class AtomikosConnectionFactoryBeanTests {
@Test
void beanMethods() throws Exception {
MockAtomikosConnectionFactoryBean bean = spy(new MockAtomikosConnectionFactoryBean());
bean.setBeanName("bean");
bean.afterPropertiesSet();
assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
verify(bean).init();
verify(bean, never()).close();
bean.destroy();
verify(bean).close();
}
static class MockAtomikosConnectionFactoryBean extends AtomikosConnectionFactoryBean {
@Override
public synchronized void init() {
}
@Override
public synchronized void close() {
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jta.atomikos;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link AtomikosDataSourceBean}.
*
* @author Phillip Webb
*/
class AtomikosDataSourceBeanTests {
@Test
void beanMethods() throws Exception {
MockAtomikosDataSourceBean bean = spy(new MockAtomikosDataSourceBean());
bean.setBeanName("bean");
bean.afterPropertiesSet();
assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
verify(bean).init();
verify(bean, never()).close();
bean.destroy();
verify(bean).close();
}
static class MockAtomikosDataSourceBean extends AtomikosDataSourceBean {
@Override
public synchronized void init() {
}
@Override
public void close() {
}
}
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jta.atomikos;
import java.util.Arrays;
import java.util.HashSet;
import javax.jms.ConnectionFactory;
import javax.sql.DataSource;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jms.extra.MessageDrivenContainer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AtomikosDependsOnBeanFactoryPostProcessor}.
*
* @author Phillip Webb
*/
class AtomikosDependsOnBeanFactoryPostProcessorTests {
private AnnotationConfigApplicationContext context;
@Test
void setsDependsOn() {
this.context = new AnnotationConfigApplicationContext(Config.class);
assertDependsOn("dataSource");
assertDependsOn("connectionFactory");
assertDependsOn("userTransactionManager", "dataSource", "connectionFactory");
assertDependsOn("messageDrivenContainer", "userTransactionManager");
this.context.close();
}
private void assertDependsOn(String bean, String... expected) {
BeanDefinition definition = this.context.getBeanDefinition(bean);
if (definition.getDependsOn() == null) {
assertThat(expected).as("No dependsOn expected for " + bean).isEmpty();
return;
}
HashSet<String> dependsOn = new HashSet<>(Arrays.asList(definition.getDependsOn()));
assertThat(dependsOn).isEqualTo(new HashSet<>(Arrays.asList(expected)));
}
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean
DataSource dataSource() {
return mock(DataSource.class);
}
@Bean
ConnectionFactory connectionFactory() {
return mock(ConnectionFactory.class);
}
@Bean
UserTransactionManager userTransactionManager() {
return mock(UserTransactionManager.class);
}
@Bean
MessageDrivenContainer messageDrivenContainer() {
return mock(MessageDrivenContainer.class);
}
@Bean
static AtomikosDependsOnBeanFactoryPostProcessor atomikosPostProcessor() {
return new AtomikosDependsOnBeanFactoryPostProcessor();
}
}
}

View File

@ -1,122 +0,0 @@
/*
* Copyright 2012-2020 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
*
* https://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.time.Duration;
import java.util.Properties;
import org.assertj.core.data.MapEntry;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link AtomikosProperties}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class AtomikosPropertiesTests {
private AtomikosProperties properties = new AtomikosProperties();
@Test
void testProperties() {
this.properties.setService("service");
this.properties.setMaxTimeout(Duration.ofMillis(1));
this.properties.setDefaultJtaTimeout(Duration.ofMillis(2));
this.properties.setMaxActives(3);
this.properties.setEnableLogging(true);
this.properties.setTransactionManagerUniqueName("uniqueName");
this.properties.setSerialJtaTransactions(true);
this.properties.setAllowSubTransactions(false);
this.properties.setForceShutdownOnVmExit(true);
this.properties.setDefaultMaxWaitTimeOnShutdown(20);
this.properties.setLogBaseName("logBaseName");
this.properties.setLogBaseDir("logBaseDir");
this.properties.setCheckpointInterval(4);
this.properties.setThreadedTwoPhaseCommit(true);
this.properties.getRecovery().setForgetOrphanedLogEntriesDelay(Duration.ofMillis(2000));
this.properties.getRecovery().setDelay(Duration.ofMillis(3000));
this.properties.getRecovery().setMaxRetries(10);
this.properties.getRecovery().setRetryInterval(Duration.ofMillis(4000));
assertThat(this.properties.asProperties().size()).isEqualTo(18);
assertProperty("com.atomikos.icatch.service", "service");
assertProperty("com.atomikos.icatch.max_timeout", "1");
assertProperty("com.atomikos.icatch.default_jta_timeout", "2");
assertProperty("com.atomikos.icatch.max_actives", "3");
assertProperty("com.atomikos.icatch.enable_logging", "true");
assertProperty("com.atomikos.icatch.tm_unique_name", "uniqueName");
assertProperty("com.atomikos.icatch.serial_jta_transactions", "true");
assertProperty("com.atomikos.icatch.allow_subtransactions", "false");
assertProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "true");
assertProperty("com.atomikos.icatch.default_max_wait_time_on_shutdown", "20");
assertProperty("com.atomikos.icatch.log_base_name", "logBaseName");
assertProperty("com.atomikos.icatch.log_base_dir", "logBaseDir");
assertProperty("com.atomikos.icatch.checkpoint_interval", "4");
assertProperty("com.atomikos.icatch.threaded_2pc", "true");
assertProperty("com.atomikos.icatch.forget_orphaned_log_entries_delay", "2000");
assertProperty("com.atomikos.icatch.recovery_delay", "3000");
assertProperty("com.atomikos.icatch.oltp_max_retries", "10");
assertProperty("com.atomikos.icatch.oltp_retry_interval", "4000");
}
@Test
void testDefaultProperties() {
Properties defaultSettings = loadDefaultSettings();
Properties properties = this.properties.asProperties();
assertThat(properties).contains(defaultOf(defaultSettings, "com.atomikos.icatch.max_timeout",
"com.atomikos.icatch.default_jta_timeout", "com.atomikos.icatch.max_actives",
"com.atomikos.icatch.enable_logging", "com.atomikos.icatch.serial_jta_transactions",
"com.atomikos.icatch.allow_subtransactions", "com.atomikos.icatch.force_shutdown_on_vm_exit",
"com.atomikos.icatch.default_max_wait_time_on_shutdown", "com.atomikos.icatch.log_base_name",
"com.atomikos.icatch.checkpoint_interval", "com.atomikos.icatch.threaded_2pc",
"com.atomikos.icatch.forget_orphaned_log_entries_delay", "com.atomikos.icatch.oltp_max_retries",
"com.atomikos.icatch.oltp_retry_interval"));
assertThat(properties).contains(entry("com.atomikos.icatch.recovery_delay",
defaultSettings.get("com.atomikos.icatch.default_jta_timeout")));
assertThat(properties).hasSize(15);
}
private MapEntry<?, ?>[] defaultOf(Properties defaultSettings, String... keys) {
MapEntry<?, ?>[] entries = new MapEntry<?, ?>[keys.length];
for (int i = 0; i < keys.length; i++) {
String key = keys[i];
entries[i] = entry(key, defaultSettings.get(key));
}
return entries;
}
private Properties loadDefaultSettings() {
try {
return PropertiesLoaderUtils.loadProperties(new ClassPathResource("transactions-defaults.properties"));
}
catch (Exception ex) {
throw new IllegalStateException("Failed to get default from Atomikos", ex);
}
}
private void assertProperty(String key, String value) {
assertThat(this.properties.asProperties().getProperty(key)).isEqualTo(value);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jta.atomikos;
import javax.jms.ConnectionFactory;
import javax.jms.XAConnectionFactory;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AtomikosXAConnectionFactoryWrapper}.
*
* @author Phillip Webb
*/
class AtomikosXAConnectionFactoryWrapperTests {
@Test
void wrap() {
XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
AtomikosXAConnectionFactoryWrapper wrapper = new AtomikosXAConnectionFactoryWrapper();
ConnectionFactory wrapped = wrapper.wrapConnectionFactory(connectionFactory);
assertThat(wrapped).isInstanceOf(AtomikosConnectionFactoryBean.class);
assertThat(((AtomikosConnectionFactoryBean) wrapped).getXaConnectionFactory()).isSameAs(connectionFactory);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jta.atomikos;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AtomikosXADataSourceWrapper}.
*
* @author Phillip Webb
*/
class AtomikosXADataSourceWrapperTests {
@Test
void wrap() throws Exception {
XADataSource dataSource = mock(XADataSource.class);
AtomikosXADataSourceWrapper wrapper = new AtomikosXADataSourceWrapper();
DataSource wrapped = wrapper.wrapDataSource(dataSource);
assertThat(wrapped).isInstanceOf(AtomikosDataSourceBean.class);
assertThat(((AtomikosDataSourceBean) wrapped).getXaDataSource()).isSameAs(dataSource);
}
}

View File

@ -1,27 +0,0 @@
plugins {
id "java"
id "org.springframework.boot.conventions"
}
description = "Spring Boot Atomikos JTA smoke test"
dependencies {
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-artemis"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-data-jpa"))
implementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jta-atomikos"))
implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
if (JavaVersion.current().java9Compatible) {
implementation("jakarta.xml.bind:jakarta.xml.bind-api")
}
implementation("org.springframework:spring-jms")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.apache.activemq:artemis-jms-server") {
exclude group: "commons-logging", module: "commons-logging"
exclude group: "org.apache.geronimo.specs", module: "geronimo-jms_2.0_spec"
exclude group: "org.apache.geronimo.specs", module: "geronimo-json_1.0_spec"
exclude group: "org.apache.geronimo.specs", module: "geronimo-jta_1.1_spec"
}
testImplementation(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-test"))
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 smoketest.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;
}
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 smoketest.atomikos;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AccountRepository extends JpaRepository<Account, Long> {
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 smoketest.atomikos;
import javax.transaction.Transactional;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
@Service
@Transactional
public class AccountService {
private final JmsTemplate jmsTemplate;
private final AccountRepository accountRepository;
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");
}
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://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 smoketest.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);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://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 smoketest.atomikos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SampleAtomikosApplication {
public static void main(String[] args) throws Exception {
try (ConfigurableApplicationContext 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);
}
}
}

View File

@ -1,3 +0,0 @@
logging.level.com.atomikos=WARN
spring.artemis.embedded.queues=accounts
spring.jpa.open-in-view=true

View File

@ -1,57 +0,0 @@
/*
* Copyright 2012-2021 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
*
* https://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 smoketest.atomikos;
import java.io.File;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration tests for demo application.
*
* @author Phillip Webb
*/
@ExtendWith(OutputCaptureExtension.class)
class SampleAtomikosApplicationTests {
@Test
void testTransactionRollback(CapturedOutput output) throws Exception {
File logDir = new File(new BuildOutput(getClass()).getRootLocation(), "atomikos-logs");
SampleAtomikosApplication.main(new String[] { "--spring.jta.log-dir=" + logDir });
assertThat(output).satisfies(numberOfOccurrences("---->", 1));
assertThat(output).satisfies(numberOfOccurrences("----> josh", 1));
assertThat(output).satisfies(numberOfOccurrences("Count is 1", 2));
assertThat(output).satisfies(numberOfOccurrences("Simulated error", 1));
}
private <T extends CharSequence> Consumer<T> numberOfOccurrences(String substring, int expectedCount) {
return (charSequence) -> {
int count = StringUtils.countOccurrencesOf(charSequence.toString(), substring);
assertThat(count).isEqualTo(expectedCount);
};
}
}