From a2adc5a1304f2eb24127bf60e6ad7dd50643fc7c Mon Sep 17 00:00:00 2001 From: Gytis Trikleris Date: Mon, 18 Jan 2016 14:56:27 +0100 Subject: [PATCH 1/2] Add Narayana JTA support Add support for JBoss Narayana. Fixes gh-5552 --- spring-boot-autoconfigure/pom.xml | 10 + .../transaction/jta/JtaAutoConfiguration.java | 2 +- .../jta/NarayanaJtaConfiguration.java | 131 +++++++++++ spring-boot-dependencies/pom.xml | 32 +++ .../appendix-application-properties.adoc | 13 ++ spring-boot-samples/pom.xml | 1 + .../spring-boot-sample-jta-narayana/pom.xml | 57 +++++ .../main/java/sample/narayana/Account.java | 43 ++++ .../sample/narayana/AccountRepository.java | 23 ++ .../java/sample/narayana/AccountService.java | 47 ++++ .../main/java/sample/narayana/Messages.java | 30 +++ .../narayana/SampleNarayanaApplication.java | 45 ++++ .../narayana/SampleRuntimeException.java | 12 + .../src/main/resources/application.properties | 5 + .../SampleNarayanaApplicationTests.java | 66 ++++++ spring-boot-starters/pom.xml | 1 + .../spring-boot-starter-jta-narayana/pom.xml | 48 ++++ .../main/resources/META-INF/spring.provides | 1 + spring-boot/pom.xml | 25 ++ .../DataSourceXAResourceRecoveryHelper.java | 169 ++++++++++++++ .../NarayanaBeanFactoryPostProcessor.java | 77 ++++++ .../narayana/NarayanaConfigurationBean.java | 106 +++++++++ .../jta/narayana/NarayanaDataSourceBean.java | 110 +++++++++ .../boot/jta/narayana/NarayanaProperties.java | 221 ++++++++++++++++++ .../narayana/NarayanaRecoveryManagerBean.java | 67 ++++++ .../NarayanaXAConnectionFactoryWrapper.java | 66 ++++++ .../narayana/NarayanaXADataSourceWrapper.java | 59 +++++ .../boot/jta/narayana/package-info.java | 20 ++ ...taSourceXAResourceRecoveryHelperTests.java | 182 +++++++++++++++ ...NarayanaBeanFactoryPostProcessorTests.java | 87 +++++++ .../NarayanaConfigurationBeanTests.java | 111 +++++++++ .../narayana/NarayanaDataSourceBeanTests.java | 119 ++++++++++ .../NarayanaRecoveryManagerBeanTests.java | 59 +++++ ...rayanaXAConnectionFactoryWrapperTests.java | 73 ++++++ .../NarayanaXADataSourceWrapperTests.java | 69 ++++++ 35 files changed, 2186 insertions(+), 1 deletion(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/main/resources/application.properties create mode 100644 spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java create mode 100644 spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml create mode 100644 spring-boot-starters/spring-boot-starter-jta-narayana/src/main/resources/META-INF/spring.provides create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/jta/narayana/package-info.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index b6de2d4b7bb..cb05053708a 100755 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -577,6 +577,16 @@ jooq true + + org.jboss.narayana.jta + jta + true + + + org.jboss.narayana.jts + narayana-jts-integration + true + org.springframework.boot diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java index c52f82a6d85..5a75fb70c6b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfiguration.java @@ -40,7 +40,7 @@ import org.springframework.context.annotation.Import; ActiveMQAutoConfiguration.class, HornetQAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) @Import({ JndiJtaConfiguration.class, BitronixJtaConfiguration.class, - AtomikosJtaConfiguration.class }) + AtomikosJtaConfiguration.class, NarayanaJtaConfiguration.class }) @EnableConfigurationProperties(JtaProperties.class) public class JtaAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java new file mode 100644 index 00000000000..0fa78ffac45 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2015 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.transaction.jta; + +import javax.jms.Message; +import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; + +import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; + +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.jta.XAConnectionFactoryWrapper; +import org.springframework.boot.jta.XADataSourceWrapper; +import org.springframework.boot.jta.narayana.NarayanaBeanFactoryPostProcessor; +import org.springframework.boot.jta.narayana.NarayanaConfigurationBean; +import org.springframework.boot.jta.narayana.NarayanaProperties; +import org.springframework.boot.jta.narayana.NarayanaRecoveryManagerBean; +import org.springframework.boot.jta.narayana.NarayanaXAConnectionFactoryWrapper; +import org.springframework.boot.jta.narayana.NarayanaXADataSourceWrapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.jta.JtaTransactionManager; + +/** + * JTA Configuration for Narayana. + * + * @author Gytis Trikleris + */ +@Configuration +@ConditionalOnClass({ JtaTransactionManager.class, com.arjuna.ats.jta.UserTransaction.class }) +@ConditionalOnMissingBean(PlatformTransactionManager.class) +public class NarayanaJtaConfiguration { + + @Autowired + private JtaProperties jtaProperties; + + @Bean + @ConditionalOnMissingBean + public NarayanaProperties narayanaProperties() { + return new NarayanaProperties(); + } + + @Bean + @ConditionalOnMissingBean + public NarayanaConfigurationBean narayanaConfigurationBean(NarayanaProperties narayanaProperties) { + if (this.jtaProperties.getLogDir() != null) { + narayanaProperties.setLogDir(this.jtaProperties.getLogDir()); + } + + if (this.jtaProperties.getTransactionManagerId() != null) { + narayanaProperties.setTransactionManagerId(this.jtaProperties.getTransactionManagerId()); + } + + return new NarayanaConfigurationBean(narayanaProperties); + } + + @Bean + @DependsOn("narayanaConfigurationBean") + @ConditionalOnMissingBean + public UserTransaction narayanaUserTransaction() { + return com.arjuna.ats.jta.UserTransaction.userTransaction(); + } + + @Bean + @DependsOn("narayanaConfigurationBean") + @ConditionalOnMissingBean + public TransactionManager narayanaTransactionManager() { + return com.arjuna.ats.jta.TransactionManager.transactionManager(); + } + + @Bean + @DependsOn("narayanaConfigurationBean") + public RecoveryManagerService narayanaRecoveryManagerService() { + return new RecoveryManagerService(); + } + + @Bean + public NarayanaRecoveryManagerBean narayanaRecoveryManagerBean(RecoveryManagerService recoveryManagerService) { + return new NarayanaRecoveryManagerBean(recoveryManagerService); + } + + @Bean + public JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { + return new JtaTransactionManager(userTransaction, transactionManager); + } + + @Bean + @ConditionalOnMissingBean(XADataSourceWrapper.class) + public XADataSourceWrapper xaDataSourceWrapper(NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, + NarayanaProperties narayanaProperties) { + return new NarayanaXADataSourceWrapper(narayanaRecoveryManagerBean, narayanaProperties); + } + + @Bean + @ConditionalOnMissingBean + public static NarayanaBeanFactoryPostProcessor narayanaBeanFactoryPostProcessor() { + return new NarayanaBeanFactoryPostProcessor(); + } + + @Configuration + @ConditionalOnClass(Message.class) + static class NarayanaJtaJmsConfiguration { + + @Bean + @ConditionalOnMissingBean(XAConnectionFactoryWrapper.class) + public NarayanaXAConnectionFactoryWrapper xaConnectionFactoryWrapper(TransactionManager transactionManager, + NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, NarayanaProperties narayanaProperties) { + return new NarayanaXAConnectionFactoryWrapper(transactionManager, narayanaRecoveryManagerBean, narayanaProperties); + } + + } + +} diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 361d2a730e0..597ac18d134 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -174,6 +174,8 @@ 9f96c74 0.30 1.6.3 + 5.3.2.Final + 7.3.0.Final 3.2.1 @@ -399,6 +401,11 @@ spring-boot-starter-jta-bitronix 1.4.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-jta-narayana + 1.4.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-undertow @@ -2212,6 +2219,31 @@ wsdl4j ${wsdl4j.version} + + org.jboss.narayana.jta + jta + ${narayana.version} + + + org.jboss.narayana.jta + jdbc + ${narayana.version} + + + org.jboss.narayana.jta + jms + ${narayana.version} + + + org.jboss.narayana.jts + narayana-jts-integration + ${narayana.version} + + + org.jboss + jboss-transaction-spi + ${jboss-transaction-spi.version} + diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 91564175faa..e8e42ffd589 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -715,6 +715,19 @@ content into your application; rather pick only the properties that you need. spring.jta.bitronix.properties.skip-corrupted-logs=false # Skip corrupted transactions log entries. spring.jta.bitronix.properties.warn-about-zero-resource-transaction=true # Log a warning for transactions executed without a single enlisted resource. + # NARAYANA + spring.jta.narayana.one-phase-commit=true # Enable or disable one phase commit optimisation + spring.jta.narayana.default-timeout=60 # Set default transaction timeout in seconds + spring.jta.narayana.periodic-recovery-period=120 # Set interval in which periodic recovery scans are performed in seconds + spring.jta.narayana.recovery-backoff-period=10 # Set back off period between first and second phases of the recovery scan in seconds + spring.jta.narayana.xa-resource-orphan-filters=com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter,com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter # List of XAResourceOrphanFilter implementations + spring.jta.narayana.recovery-modules=com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule,com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule # List of RecoveryModule implementations + spring.jta.narayana.expiry-scanners=com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner # List of ExpiryScanner implementations + spring.jta.narayana.recovery-db-user= # Database username to be used by recovery manager + spring.jta.narayana.recovery-db-pass= # Database password to be used by recovery manager + spring.jta.narayana.recovery-jms-user= # JMS username to be used by recovery manager + spring.jta.narayana.recovery-jms-pass= # JMS password to be used by recovery manager + # EMBEDDED MONGODB ({sc-spring-boot-autoconfigure}/mongo/embedded/EmbeddedMongoProperties.{sc-ext}[EmbeddedMongoProperties]) spring.mongodb.embedded.features=SYNC_DELAY # Comma-separated list of features to enable. spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use. diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 427eabc9d9b..6681adbeef2 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -63,6 +63,7 @@ spring-boot-sample-jpa spring-boot-sample-jta-atomikos spring-boot-sample-jta-bitronix + spring-boot-sample-jta-narayana spring-boot-sample-jta-jndi spring-boot-sample-liquibase spring-boot-sample-logback diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml b/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml new file mode 100644 index 00000000000..4d7d9c32c62 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + spring-boot-samples + org.springframework.boot + 1.4.0.BUILD-SNAPSHOT + + spring-boot-sample-jta-narayana + Spring Boot Narayana JTA Sample + Spring Boot Narayana JTA Sample + http://projects.spring.io/spring-boot/ + + ${basedir}/../.. + + + + org.springframework + spring-jms + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-jta-narayana + + + org.springframework.boot + spring-boot-starter-hornetq + + + org.hornetq + hornetq-jms-server + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java new file mode 100644 index 00000000000..f588e09f722 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.narayana; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Account { + + @Id + @GeneratedValue + private Long id; + + private String username; + + Account() { + } + + public Account(String username) { + this.username = username; + } + + public String getUsername() { + return this.username; + } + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java new file mode 100644 index 00000000000..eb8ec8e9801 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.narayana; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AccountRepository extends JpaRepository { + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java new file mode 100644 index 00000000000..72f0d756d46 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.narayana; + +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 SampleRuntimeException("Simulated error"); + } + } + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java new file mode 100644 index 00000000000..90ff54fb1bd --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.narayana; + +import org.springframework.jms.annotation.JmsListener; +import org.springframework.stereotype.Component; + +@Component +public class Messages { + + @JmsListener(destination = "accounts") + public void onMessage(String content) { + System.out.println("----> " + content); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java new file mode 100644 index 00000000000..9585d14489e --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java @@ -0,0 +1,45 @@ +/* + * 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.narayana; + +import java.io.Closeable; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class SampleNarayanaApplication { + + public static void main(String[] args) throws Exception { + ApplicationContext context = SpringApplication.run(SampleNarayanaApplication.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 { + // Using username "error" will cause service to throw SampleRuntimeException + service.createAccountAndNotify("error"); + } catch (SampleRuntimeException ex) { + // Log message to let test case know that exception was thrown + System.out.println(ex.getMessage()); + } + System.out.println("Count is " + repository.count()); + ((Closeable) context).close(); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java new file mode 100644 index 00000000000..f65d4848971 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java @@ -0,0 +1,12 @@ +package sample.narayana; + +/** + * @author Gytis Trikleris + */ +public class SampleRuntimeException extends RuntimeException { + + public SampleRuntimeException(String message) { + super(message); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/resources/application.properties b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/resources/application.properties new file mode 100644 index 00000000000..ffbc046eb30 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/resources/application.properties @@ -0,0 +1,5 @@ +spring.hornetq.mode=embedded +spring.hornetq.embedded.enabled=true +spring.hornetq.embedded.queues=accounts + +logging.level.com.arjuna=INFO \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java new file mode 100644 index 00000000000..4ec0ab2b85f --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java @@ -0,0 +1,66 @@ +/* + * 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.narayana; + +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.junit.Assert.assertThat; + +/** + * @author Gytis Trikleris + */ +public class SampleNarayanaApplicationTests { + + @Rule + public OutputCapture outputCapture = new OutputCapture(); + + @Test + public void testTransactionRollback() throws Exception { + SampleNarayanaApplication.main(new String[] {}); + String output = this.outputCapture.toString(); + assertThat(output, containsString(1, "---->")); + assertThat(output, containsString(1, "----> josh")); + assertThat(output, containsString(2, "Count is 1")); + assertThat(output, containsString(1, "Simulated error")); + } + + private Matcher containsString(final int times, String s) { + return new SubstringMatcher(s) { + + @Override + protected String relationship() { + return "containing " + times + " times"; + } + + @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 == times; + } + + }; + } + +} diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 4b8579cb5cf..40aa71d0b61 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -48,6 +48,7 @@ spring-boot-starter-jooq spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix + spring-boot-starter-jta-narayana spring-boot-starter-logging spring-boot-starter-log4j2 spring-boot-starter-mail diff --git a/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml b/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml new file mode 100644 index 00000000000..9b689932e08 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + spring-boot-starters + org.springframework.boot + 1.4.0.BUILD-SNAPSHOT + + spring-boot-starter-jta-narayana + Spring Boot Narayana JTA Starter + Spring Boot Narayana JTA Starter + http://projects.spring.io/spring-boot/ + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.jboss.narayana.jta + jta + + + org.jboss.narayana.jta + jdbc + + + org.jboss.narayana.jta + jms + + + org.jboss.narayana.jts + narayana-jts-integration + + + org.jboss + jboss-transaction-spi + + + javax.transaction + javax.transaction-api + + + \ No newline at end of file diff --git a/spring-boot-starters/spring-boot-starter-jta-narayana/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-jta-narayana/src/main/resources/META-INF/spring.provides new file mode 100644 index 00000000000..ff483355d02 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-jta-narayana/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: jta, jdbc, jms, jboss-transaction-spi \ No newline at end of file diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index c853b19759f..6dbef42a7ce 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -229,6 +229,31 @@ snakeyaml true + + org.jboss.narayana.jta + jta + true + + + org.jboss.narayana.jta + jdbc + true + + + org.jboss.narayana.jta + jms + true + + + org.jboss.narayana.jts + narayana-jts-integration + true + + + org.jboss + jboss-transaction-spi + true + org.springframework.boot diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java new file mode 100644 index 00000000000..6ff48c18f9c --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java @@ -0,0 +1,169 @@ +/* + * Copyright 2012-2015 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.narayana; + +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; + +/** + * XAResourceRecoveryHelper implementation which gets Xids, which needs to be recovered, from the database. + * + * @author Gytis Trikleris + */ +public class DataSourceXAResourceRecoveryHelper implements XAResourceRecoveryHelper, XAResource { + + private static final Logger LOGGER = Logger.getLogger(DataSourceXAResourceRecoveryHelper.class.getName()); + + private final XADataSource xaDataSource; + + private final String user; + + private final String pass; + + private XAConnection xaConnection; + + private XAResource delegate; + + public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource) { + this(xaDataSource, null, null); + } + + public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user, String pass) { + this.xaDataSource = xaDataSource; + this.user = user; + this.pass = pass; + } + + @Override + public boolean initialise(String properties) { + return true; + } + + @Override + public XAResource[] getXAResources() { + if (connect()) { + return new XAResource[] { this }; + } + + return new XAResource[0]; + } + + @Override + public Xid[] recover(int i) throws XAException { + try { + return this.delegate.recover(i); + } + finally { + if (i == XAResource.TMENDRSCAN) { + disconnect(); + } + } + } + + @Override + public void start(Xid xid, int i) throws XAException { + this.delegate.start(xid, i); + } + + @Override + public void end(Xid xid, int i) throws XAException { + this.delegate.end(xid, i); + } + + @Override + public int prepare(Xid xid) throws XAException { + return this.delegate.prepare(xid); + } + + @Override + public void commit(Xid xid, boolean b) throws XAException { + this.delegate.commit(xid, b); + } + + @Override + public void rollback(Xid xid) throws XAException { + this.delegate.rollback(xid); + } + + @Override + public boolean isSameRM(XAResource xaResource) throws XAException { + return this.delegate.isSameRM(xaResource); + } + + @Override + public void forget(Xid xid) throws XAException { + this.delegate.forget(xid); + } + + @Override + public int getTransactionTimeout() throws XAException { + return this.delegate.getTransactionTimeout(); + } + + @Override + public boolean setTransactionTimeout(int i) throws XAException { + return this.delegate.setTransactionTimeout(i); + } + + private boolean connect() { + if (this.delegate != null) { + return true; + } + + try { + this.xaConnection = getXaConnection(); + this.delegate = this.xaConnection.getXAResource(); + } + catch (SQLException e) { + LOGGER.log(Level.WARNING, "Failed to create connection", e); + return false; + } + + return true; + } + + private void disconnect() throws XAException { + try { + this.xaConnection.close(); + } + catch (SQLException e) { + LOGGER.log(Level.WARNING, "Failed to close connection", e); + } + finally { + this.xaConnection = null; + this.delegate = null; + } + } + + private XAConnection getXaConnection() throws SQLException { + if (this.user == null && this.pass == null) { + return this.xaDataSource.getXAConnection(); + } + + return this.xaDataSource.getXAConnection(this.user, this.pass); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java new file mode 100644 index 00000000000..8b7a4513d98 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2015 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.narayana; + +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 setup correct beans ordering. + * + * @author Gytis Trikleris + */ +public class NarayanaBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { + + private static final String[] NO_BEANS = {}; + + private static final int ORDER = Ordered.LOWEST_PRECEDENCE; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + String[] transactionManagers = beanFactory.getBeanNamesForType(TransactionManager.class, true, false); + String[] recoveryManagers = beanFactory.getBeanNamesForType(NarayanaRecoveryManagerBean.class, true, false); + + addBeanDependencies(beanFactory, transactionManagers, "javax.sql.DataSource"); + addBeanDependencies(beanFactory, recoveryManagers, "javax.sql.DataSource"); + addBeanDependencies(beanFactory, transactionManagers, "javax.jms.ConnectionFactory"); + addBeanDependencies(beanFactory, recoveryManagers, "javax.jms.ConnectionFactory"); + } + + private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, String[] beanNames, String dependencyType) { + for (String beanName : beanNames) { + addBeanDependencies(beanFactory, beanName, dependencyType); + } + } + + private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, String beanName, String dependencyType) { + for (String dependentBeanName : getBeanNamesForType(beanFactory, dependencyType)) { + beanFactory.registerDependentBean(beanName, dependentBeanName); + } + } + + private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, String type) { + try { + return beanFactory.getBeanNamesForType(Class.forName(type), true, false); + } + catch (ClassNotFoundException ex) { + // Ignore + } catch (NoClassDefFoundError ex) { + // Ignore + } + return NO_BEANS; + } + + @Override + public int getOrder() { + return ORDER; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java new file mode 100644 index 00000000000..311a2c503fd --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2015 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.narayana; + +import java.util.List; + +import com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean; +import com.arjuna.ats.arjuna.common.CoreEnvironmentBean; +import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException; +import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; +import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean; +import com.arjuna.ats.jta.common.JTAEnvironmentBean; +import com.arjuna.common.internal.util.propertyservice.BeanPopulator; + +import org.springframework.beans.factory.InitializingBean; + +/** + * Bean that configures Narayana transaction manager. + * + * @author Gytis Trikleris + */ +public class NarayanaConfigurationBean implements InitializingBean { + + private static final String JBOSSTS_PROPERTIES_FILE_NAME = "jbossts-properties.xml"; + + private final NarayanaProperties narayanaProperties; + + public NarayanaConfigurationBean(NarayanaProperties narayanaProperties) { + this.narayanaProperties = narayanaProperties; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (isPropertiesFileAvailable()) { + return; + } + + setNodeIdentifier(this.narayanaProperties.getTransactionManagerId()); + setObjectStoreDir(this.narayanaProperties.getLogDir()); + setCommitOnePhase(this.narayanaProperties.isOnePhaseCommit()); + setDefaultTimeout(this.narayanaProperties.getDefaultTimeout()); + setPeriodicRecoveryPeriod(this.narayanaProperties.getPeriodicRecoveryPeriod()); + setRecoveryBackoffPeriod(this.narayanaProperties.getRecoveryBackoffPeriod()); + setXaResourceOrphanFilters(this.narayanaProperties.getXaResourceOrphanFilters()); + setRecoveryModules(this.narayanaProperties.getRecoveryModules()); + setExpiryScanners(this.narayanaProperties.getExpiryScanners()); + } + + private boolean isPropertiesFileAvailable() { + return Thread.currentThread().getContextClassLoader().getResource(JBOSSTS_PROPERTIES_FILE_NAME) != null; + } + + private void setNodeIdentifier(String nodeIdentifier) throws CoreEnvironmentBeanException { + BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier); + } + + private void setObjectStoreDir(String objectStoreDir) { + BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).setObjectStoreDir(objectStoreDir); + BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") + .setObjectStoreDir(objectStoreDir); + BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").setObjectStoreDir(objectStoreDir); + } + + private void setCommitOnePhase(boolean isCommitOnePhase) { + BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).setCommitOnePhase(isCommitOnePhase); + } + + private void setDefaultTimeout(int defaultTimeout) { + BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).setDefaultTimeout(defaultTimeout); + } + + private void setPeriodicRecoveryPeriod(int periodicRecoveryPeriod) { + BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setPeriodicRecoveryPeriod(periodicRecoveryPeriod); + } + + private void setRecoveryBackoffPeriod(int recoveryBackoffPeriod) { + BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setRecoveryBackoffPeriod(recoveryBackoffPeriod); + } + + private void setXaResourceOrphanFilters(List xaResourceOrphanFilters) { + BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).setXaResourceOrphanFilterClassNames(xaResourceOrphanFilters); + } + + private void setRecoveryModules(List recoveryModules) { + BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setRecoveryModuleClassNames(recoveryModules); + } + + private void setExpiryScanners(List expiryScanners) { + BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setExpiryScannerClassNames(expiryScanners); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java new file mode 100644 index 00000000000..1421ec499f9 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2015 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.narayana; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.util.logging.Logger; + +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import com.arjuna.ats.internal.jdbc.ConnectionManager; +import com.arjuna.ats.jdbc.TransactionalDriver; + +/** + * {@link DataSource} implementation wrapping {@link XADataSource} and using {@link ConnectionManager} to acquire connections. + * + * @author Gytis Trikleris + */ +public class NarayanaDataSourceBean implements DataSource { + + private final XADataSource xaDataSource; + + public NarayanaDataSourceBean(XADataSource xaDataSource) { + this.xaDataSource = xaDataSource; + } + + @Override + public Connection getConnection() throws SQLException { + Properties properties = new Properties(); + properties.put(TransactionalDriver.XADataSource, this.xaDataSource); + + return ConnectionManager.create(null, properties); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + Properties properties = new Properties(); + properties.put(TransactionalDriver.XADataSource, this.xaDataSource); + properties.put(TransactionalDriver.userName, username); + properties.put(TransactionalDriver.password, password); + + return ConnectionManager.create(null, properties); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return this.xaDataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + this.xaDataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + this.xaDataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return this.xaDataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + @SuppressWarnings("unchecked") + @Override + public T unwrap(Class iface) throws SQLException { + if (isWrapperFor(iface)) { + return (T) this; + } + else if (isWrapperFor(iface, this.xaDataSource.getClass())) { + return (T) this.xaDataSource; + } + + throw new SQLException(getClass() + " is not a wrapper for " + iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return isWrapperFor(iface, getClass()); + } + + private boolean isWrapperFor(Class iface, Class wrapperIface) { + return iface.isAssignableFrom(wrapperIface); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java new file mode 100644 index 00000000000..1962fe43fbb --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java @@ -0,0 +1,221 @@ +/* + * Copyright 2012-2015 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.narayana; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Subset of Narayana properties which can be configured via Spring configuration. Use jbossts-properties.xml for complete + * configuration. + * + * @author Gytis Trikleris + */ +@ConfigurationProperties(prefix = NarayanaProperties.PROPERTIES_PREFIX) +public class NarayanaProperties { + + /** + * Prefix for Narayana specific properties. + */ + public static final String PROPERTIES_PREFIX = "spring.jta.narayana"; + + /** + * Transaction object store directory. Default: target/tx-object-store. + */ + private String logDir = "target/tx-object-store"; + + /** + * Unique transaction manager id. Default: 1. + */ + private String transactionManagerId = "1"; + + /** + * Enable one phase commit optimisation. Default: true. + */ + private boolean onePhaseCommit = true; + + /** + * Transaction timeout in seconds. Default: 60. + */ + private int defaultTimeout = 60; + + /** + * Interval in which periodic recovery scans are performed in seconds. Default: 120 + */ + private int periodicRecoveryPeriod = 120; + + /** + * Back off period between first and second phases of the recovery scan in seconds. Default: 10 + */ + private int recoveryBackoffPeriod = 10; + + /** + * Database username to be used by recovery manager. Default: null + */ + private String recoveryDbUser = null; + + /** + * Database password to be used by recovery manager. Default: null + */ + private String recoveryDbPass = null; + + /** + * JMS username to be used by recovery manager. Default: null + */ + private String recoveryJmsUser = null; + + /** + * JMS password to be used by recovery manager. Default: null + */ + private String recoveryJmsPass = null; + + /** + * List of orphan filters. Default: + *
    + *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter
  • + *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter
  • + *
+ */ + private List xaResourceOrphanFilters = Arrays.asList( + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter", + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter"); + + /** + * List of recovery modules. Default: + *
    + *
  • com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule
  • + *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule
  • + *
+ */ + private List recoveryModules = Arrays.asList("com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", + "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule"); + + /** + * List of expiry scanners. Default: + *
    + *
  • com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner
  • + *
+ */ + private List expiryScanners = Arrays + .asList("com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); + + public String getLogDir() { + return this.logDir; + } + + public void setLogDir(String logDir) { + this.logDir = logDir; + } + + public String getTransactionManagerId() { + return this.transactionManagerId; + } + + public void setTransactionManagerId(String transactionManagerId) { + this.transactionManagerId = transactionManagerId; + } + + public boolean isOnePhaseCommit() { + return this.onePhaseCommit; + } + + public void setOnePhaseCommit(boolean onePhaseCommit) { + this.onePhaseCommit = onePhaseCommit; + } + + public int getDefaultTimeout() { + return this.defaultTimeout; + } + + public int getPeriodicRecoveryPeriod() { + return this.periodicRecoveryPeriod; + } + + public void setPeriodicRecoveryPeriod(int periodicRecoveryPeriod) { + this.periodicRecoveryPeriod = periodicRecoveryPeriod; + } + + public int getRecoveryBackoffPeriod() { + return this.recoveryBackoffPeriod; + } + + public void setRecoveryBackoffPeriod(int recoveryBackoffPeriod) { + this.recoveryBackoffPeriod = recoveryBackoffPeriod; + } + + public void setDefaultTimeout(int defaultTimeout) { + this.defaultTimeout = defaultTimeout; + } + + public List getXaResourceOrphanFilters() { + return this.xaResourceOrphanFilters; + } + + public void setXaResourceOrphanFilters(List xaResourceOrphanFilters) { + this.xaResourceOrphanFilters = xaResourceOrphanFilters; + } + + public List getRecoveryModules() { + return this.recoveryModules; + } + + public void setRecoveryModules(List recoveryModules) { + this.recoveryModules = recoveryModules; + } + + public List getExpiryScanners() { + return this.expiryScanners; + } + + public void setExpiryScanners(List expiryScanners) { + this.expiryScanners = expiryScanners; + } + + public String getRecoveryDbUser() { + return this.recoveryDbUser; + } + + public void setRecoveryDbUser(String recoveryDbUser) { + this.recoveryDbUser = recoveryDbUser; + } + + public String getRecoveryDbPass() { + return this.recoveryDbPass; + } + + public void setRecoveryDbPass(String recoveryDbPass) { + this.recoveryDbPass = recoveryDbPass; + } + + public String getRecoveryJmsUser() { + return this.recoveryJmsUser; + } + + public void setRecoveryJmsUser(String recoveryJmsUser) { + this.recoveryJmsUser = recoveryJmsUser; + } + + public String getRecoveryJmsPass() { + return this.recoveryJmsPass; + } + + public void setRecoveryJmsPass(String recoveryJmsPass) { + this.recoveryJmsPass = recoveryJmsPass; + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java new file mode 100644 index 00000000000..62c2f976bd9 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2015 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.narayana; + +import com.arjuna.ats.arjuna.recovery.RecoveryManager; +import com.arjuna.ats.arjuna.recovery.RecoveryModule; +import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule; +import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; +import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +/** + * Bean to set up Narayana recovery manager. + * + * @author Gytis Trikleris + */ +public class NarayanaRecoveryManagerBean implements InitializingBean, DisposableBean { + + private final RecoveryManagerService recoveryManagerService; + + public NarayanaRecoveryManagerBean(RecoveryManagerService recoveryManagerService) { + this.recoveryManagerService = recoveryManagerService; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.recoveryManagerService.create(); + this.recoveryManagerService.start(); + } + + @Override + public void destroy() throws Exception { + this.recoveryManagerService.stop(); + this.recoveryManagerService.destroy(); + } + + void registerXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) { + getXARecoveryModule(RecoveryManager.manager()).addXAResourceRecoveryHelper(xaResourceRecoveryHelper); + } + + private XARecoveryModule getXARecoveryModule(RecoveryManager recoveryManager) { + for (RecoveryModule recoveryModule : recoveryManager.getModules()) { + if (recoveryModule instanceof XARecoveryModule) { + return (XARecoveryModule) recoveryModule; + } + } + + throw new IllegalStateException("XARecoveryModule is not registered with recovery manager"); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java new file mode 100644 index 00000000000..06c4f672659 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java @@ -0,0 +1,66 @@ +/* + * 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.narayana; + +import javax.jms.ConnectionFactory; +import javax.jms.XAConnectionFactory; +import javax.transaction.TransactionManager; + +import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; +import org.jboss.narayana.jta.jms.ConnectionFactoryProxy; +import org.jboss.narayana.jta.jms.JmsXAResourceRecoveryHelper; +import org.jboss.narayana.jta.jms.TransactionHelperImpl; + +import org.springframework.boot.jta.XAConnectionFactoryWrapper; + +/** + * {@link XAConnectionFactoryWrapper} that uses {@link ConnectionFactoryProxy} to wrap an {@link XAConnectionFactory}. + * + * @author Gytis Trikleris + */ +public class NarayanaXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper { + + private final TransactionManager transactionManager; + + private final NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + + private final NarayanaProperties narayanaProperties; + + public NarayanaXAConnectionFactoryWrapper(TransactionManager transactionManager, + NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, NarayanaProperties narayanaProperties) { + this.transactionManager = transactionManager; + this.narayanaRecoveryManagerBean = narayanaRecoveryManagerBean; + this.narayanaProperties = narayanaProperties; + } + + @Override + public ConnectionFactory wrapConnectionFactory(XAConnectionFactory xaConnectionFactory) { + this.narayanaRecoveryManagerBean.registerXAResourceRecoveryHelper(getRecoveryHelper(xaConnectionFactory)); + + return new ConnectionFactoryProxy(xaConnectionFactory, new TransactionHelperImpl(this.transactionManager)); + } + + private XAResourceRecoveryHelper getRecoveryHelper(XAConnectionFactory xaConnectionFactory) { + if (this.narayanaProperties.getRecoveryJmsUser() == null && this.narayanaProperties.getRecoveryJmsPass() == null) { + return new JmsXAResourceRecoveryHelper(xaConnectionFactory); + } + + return new JmsXAResourceRecoveryHelper(xaConnectionFactory, this.narayanaProperties.getRecoveryJmsUser(), + this.narayanaProperties.getRecoveryJmsPass()); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java new file mode 100644 index 00000000000..7bb844164f4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2015 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.narayana; + +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; + +import org.springframework.boot.jta.XADataSourceWrapper; + +/** + * {@link XADataSourceWrapper} that uses {@link NarayanaDataSourceBean} to wrap an {@link XADataSource}. + * + * @author Gytis Trikleris + */ +public class NarayanaXADataSourceWrapper implements XADataSourceWrapper { + + private final NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + + private final NarayanaProperties narayanaProperties; + + public NarayanaXADataSourceWrapper(NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, + NarayanaProperties narayanaProperties) { + this.narayanaRecoveryManagerBean = narayanaRecoveryManagerBean; + this.narayanaProperties = narayanaProperties; + } + + @Override + public DataSource wrapDataSource(XADataSource dataSource) { + this.narayanaRecoveryManagerBean.registerXAResourceRecoveryHelper(getRecoveryHelper(dataSource)); + + return new NarayanaDataSourceBean(dataSource); + } + + private XAResourceRecoveryHelper getRecoveryHelper(XADataSource dataSource) { + if (this.narayanaProperties.getRecoveryDbUser() == null && this.narayanaProperties.getRecoveryDbPass() == null) { + return new DataSourceXAResourceRecoveryHelper(dataSource); + } + + return new DataSourceXAResourceRecoveryHelper(dataSource, this.narayanaProperties.getRecoveryDbUser(), + this.narayanaProperties.getRecoveryDbPass()); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/package-info.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/package-info.java new file mode 100644 index 00000000000..70a99f09ae0 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2015 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 classes for Narayana JTA. + */ +package org.springframework.boot.jta.narayana; diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java new file mode 100644 index 00000000000..ab9b74d23cb --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java @@ -0,0 +1,182 @@ +/* + * 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.narayana; + +import java.sql.SQLException; + +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.times; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; +import static org.mockito.Matchers.anyString; + +/** + * @author Gytis Trikleris + */ +public class DataSourceXAResourceRecoveryHelperTests { + + private XADataSource xaDataSource; + + private XAConnection xaConnection; + + private XAResource xaResource; + + private DataSourceXAResourceRecoveryHelper recoveryHelper; + + @Before + public void before() throws SQLException { + this.xaDataSource = mock(XADataSource.class); + this.xaConnection = mock(XAConnection.class); + this.xaResource = mock(XAResource.class); + this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.xaDataSource); + + when(this.xaDataSource.getXAConnection()).thenReturn(this.xaConnection); + when(this.xaConnection.getXAResource()).thenReturn(this.xaResource); + } + + @Test + public void shouldCreateConnectionAndGetXAResource() throws SQLException { + XAResource[] xaResources = this.recoveryHelper.getXAResources(); + assertThat(xaResources.length).isEqualTo(1); + assertThat(xaResources[0]).isSameAs(this.recoveryHelper); + verify(this.xaDataSource, times(1)).getXAConnection(); + verify(this.xaConnection, times(1)).getXAResource(); + } + + @Test + public void shouldCreateConnectionWithCredentialsAndGetXAResource() throws SQLException { + when(this.xaDataSource.getXAConnection(anyString(), anyString())).thenReturn(this.xaConnection); + this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.xaDataSource, "username", "password"); + + XAResource[] xaResources = this.recoveryHelper.getXAResources(); + assertThat(xaResources.length).isEqualTo(1); + assertThat(xaResources[0]).isSameAs(this.recoveryHelper); + verify(this.xaDataSource, times(1)).getXAConnection("username", "password"); + verify(this.xaConnection, times(1)).getXAResource(); + } + + @Test + public void shouldFailToCreateConnectionAndNotGetXAResource() throws SQLException { + when(this.xaDataSource.getXAConnection()).thenThrow(new SQLException("Test exception")); + + XAResource[] xaResources = this.recoveryHelper.getXAResources(); + + assertThat(xaResources.length).isEqualTo(0); + verify(this.xaDataSource, times(1)).getXAConnection(); + verify(this.xaConnection, times(0)).getXAResource(); + } + + @Test + public void shouldDelegateRecoverCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.recover(XAResource.TMSTARTRSCAN); + + verify(this.xaResource, times(1)).recover(XAResource.TMSTARTRSCAN); + } + + @Test + public void shouldDelegateRecoverCallAndCloseConnection() throws XAException, SQLException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.recover(XAResource.TMENDRSCAN); + + verify(this.xaResource, times(1)).recover(XAResource.TMENDRSCAN); + verify(this.xaConnection, times(1)).close(); + } + + @Test + public void shouldDelegateStartCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.start(null, 0); + + verify(this.xaResource, times(1)).start(null, 0); + } + + @Test + public void shouldDelegateEndCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.end(null, 0); + + verify(this.xaResource, times(1)).end(null, 0); + } + + @Test + public void shouldDelegatePrepareCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.prepare(null); + + verify(this.xaResource, times(1)).prepare(null); + } + + @Test + public void shouldDelegateCommitCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.commit(null, true); + + verify(this.xaResource, times(1)).commit(null, true); + } + + @Test + public void shouldDelegateRollbackCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.rollback(null); + + verify(this.xaResource, times(1)).rollback(null); + } + + @Test + public void shouldDelegateIsSameRMCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.isSameRM(null); + + verify(this.xaResource, times(1)).isSameRM(null); + } + + @Test + public void shouldDelegateForgetCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.forget(null); + + verify(this.xaResource, times(1)).forget(null); + } + + @Test + public void shouldDelegateGetTransactionTimeoutCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.getTransactionTimeout(); + + verify(this.xaResource, times(1)).getTransactionTimeout(); + } + + @Test + public void shouldDelegateSetTransactionTimeoutCall() throws XAException { + this.recoveryHelper.getXAResources(); + this.recoveryHelper.setTransactionTimeout(0); + + verify(this.xaResource, times(1)).setTransactionTimeout(0); + } + +} + diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java new file mode 100644 index 00000000000..c0e25db46c6 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2015 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.narayana; + +import javax.jms.ConnectionFactory; +import javax.sql.DataSource; +import javax.transaction.TransactionManager; + +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.spy; +import static org.mockito.BDDMockito.verify; + +/** + * @author Gytis Trikleris + */ +public class NarayanaBeanFactoryPostProcessorTests { + + private AnnotationConfigApplicationContext context; + + @Test + public void setsDependsOn() { + DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); + + this.context = new AnnotationConfigApplicationContext(beanFactory); + this.context.register(Config.class); + this.context.refresh(); + + verify(beanFactory).registerDependentBean("narayanaTransactionManager", "dataSource"); + verify(beanFactory).registerDependentBean("narayanaTransactionManager", "connectionFactory"); + verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", "dataSource"); + verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", "connectionFactory"); + + this.context.close(); + } + + @Configuration + static class Config { + + @Bean + public DataSource dataSource() { + return mock(DataSource.class); + } + + @Bean + public ConnectionFactory connectionFactory() { + return mock(ConnectionFactory.class); + } + + @Bean + public TransactionManager narayanaTransactionManager() { + return mock(TransactionManager.class); + } + + @Bean + public NarayanaRecoveryManagerBean narayanaRecoveryManagerBean() { + return mock(NarayanaRecoveryManagerBean.class); + } + + @Bean + public static NarayanaBeanFactoryPostProcessor narayanaPostProcessor() { + return new NarayanaBeanFactoryPostProcessor(); + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java new file mode 100644 index 00000000000..e60e82781d0 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java @@ -0,0 +1,111 @@ +/* + * 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.narayana; + +import java.util.Arrays; +import java.util.List; + +import com.arjuna.ats.arjuna.common.CoordinatorEnvironmentBean; +import com.arjuna.ats.arjuna.common.CoreEnvironmentBean; +import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; +import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean; +import com.arjuna.ats.jta.common.JTAEnvironmentBean; +import com.arjuna.common.internal.util.propertyservice.BeanPopulator; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Gytis Trikleris + */ +public class NarayanaConfigurationBeanTests { + + @Test + public void shouldSetDefaultProperties() throws Exception { + NarayanaProperties narayanaProperties = new NarayanaProperties(); + NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean(narayanaProperties); + narayanaConfigurationBean.afterPropertiesSet(); + + assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).getNodeIdentifier()).isEqualTo("1"); + assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).getObjectStoreDir()) + .isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore").getObjectStoreDir()) + .isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").getObjectStoreDir()) + .isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).isCommitOnePhase()).isTrue(); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).getDefaultTimeout()).isEqualTo(60); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getPeriodicRecoveryPeriod()).isEqualTo(120); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryBackoffPeriod()).isEqualTo(10); + + + List xaResourceOrphanFilters = Arrays.asList( + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter", + "com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter"); + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceOrphanFilterClassNames()) + .isEqualTo(xaResourceOrphanFilters); + + List recoveryModules = Arrays.asList("com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", + "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule"); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryModuleClassNames()) + .isEqualTo(recoveryModules); + + List expiryScanners = Arrays + .asList("com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getExpiryScannerClassNames()) + .isEqualTo(expiryScanners); + + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceRecoveryClassNames()).isEmpty(); + } + + @Test + public void shouldSetModifiedProperties() throws Exception { + NarayanaProperties narayanaProperties = new NarayanaProperties(); + narayanaProperties.setTransactionManagerId("test-id"); + narayanaProperties.setLogDir("test-dir"); + narayanaProperties.setDefaultTimeout(1); + narayanaProperties.setPeriodicRecoveryPeriod(2); + narayanaProperties.setRecoveryBackoffPeriod(3); + narayanaProperties.setOnePhaseCommit(false); + narayanaProperties.setXaResourceOrphanFilters(Arrays.asList("test-filter-1", "test-filter-2")); + narayanaProperties.setRecoveryModules(Arrays.asList("test-module-1", "test-module-2")); + narayanaProperties.setExpiryScanners(Arrays.asList("test-scanner-1", "test-scanner-2")); + + NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean(narayanaProperties); + narayanaConfigurationBean.afterPropertiesSet(); + + assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).getNodeIdentifier()).isEqualTo("test-id"); + assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).getObjectStoreDir()) + .isEqualTo("test-dir"); + assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore").getObjectStoreDir()) + .isEqualTo("test-dir"); + assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").getObjectStoreDir()) + .isEqualTo("test-dir"); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).isCommitOnePhase()).isFalse(); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).getDefaultTimeout()).isEqualTo(1); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getPeriodicRecoveryPeriod()).isEqualTo(2); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryBackoffPeriod()).isEqualTo(3); + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceOrphanFilterClassNames()) + .isEqualTo(Arrays.asList("test-filter-1", "test-filter-2")); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryModuleClassNames()) + .isEqualTo(Arrays.asList("test-module-1", "test-module-2")); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getExpiryScannerClassNames()) + .isEqualTo(Arrays.asList("test-scanner-1", "test-scanner-2")); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java new file mode 100644 index 00000000000..3521a8f1d75 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jta.narayana; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; + +import com.arjuna.ats.internal.jdbc.ConnectionImple; +import com.arjuna.ats.jdbc.TransactionalDriver; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.times; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; + +/** + * @author Gytis Trikleris + */ +public class NarayanaDataSourceBeanTests { + + private XADataSource mockDataSource; + + private NarayanaDataSourceBean dataSourceBean; + + @Before + public void before() { + this.mockDataSource = mock(XADataSource.class); + this.dataSourceBean = new NarayanaDataSourceBean(this.mockDataSource); + } + + @Test + public void shouldBeAWrapper() throws SQLException { + assertThat(this.dataSourceBean.isWrapperFor(DataSource.class)).isTrue(); + } + + @Test + public void shouldNotBeAWrapper() throws SQLException { + assertThat(this.dataSourceBean.isWrapperFor(XADataSource.class)).isFalse(); + } + + @Test + public void shouldUnwrapDataSource() throws SQLException { + assertThat(this.dataSourceBean.unwrap(DataSource.class)).isInstanceOf(DataSource.class); + assertThat(this.dataSourceBean.unwrap(DataSource.class)).isSameAs(this.dataSourceBean); + } + + @Test + public void shouldUnwrapXaDataSource() throws SQLException { + assertThat(this.dataSourceBean.unwrap(XADataSource.class)).isInstanceOf(XADataSource.class); + assertThat(this.dataSourceBean.unwrap(XADataSource.class)).isSameAs(this.mockDataSource); + } + + @Test + public void shouldGetConnectionAndCommit() throws SQLException { + Connection mockConnection = mock(Connection.class); + XAConnection mockXaConnection = mock(XAConnection.class); + when(mockXaConnection.getConnection()).thenReturn(mockConnection); + when(this.mockDataSource.getXAConnection()).thenReturn(mockXaConnection); + + Properties properties = new Properties(); + properties.put(TransactionalDriver.XADataSource, this.mockDataSource); + + Connection connection = this.dataSourceBean.getConnection(); + assertThat(connection).isInstanceOf(ConnectionImple.class); + + connection.commit(); + + verify(this.mockDataSource, times(1)).getXAConnection(); + verify(mockXaConnection, times(1)).getConnection(); + verify(mockConnection, times(1)).commit(); + } + + @Test + public void shouldGetConnectionAndCommitWithCredentials() throws SQLException { + String username = "testUsername"; + String password = "testPassword"; + Connection mockConnection = mock(Connection.class); + XAConnection mockXaConnection = mock(XAConnection.class); + when(mockXaConnection.getConnection()).thenReturn(mockConnection); + when(this.mockDataSource.getXAConnection(username, password)).thenReturn(mockXaConnection); + + Properties properties = new Properties(); + properties.put(TransactionalDriver.XADataSource, this.mockDataSource); + properties.put(TransactionalDriver.userName, username); + properties.put(TransactionalDriver.password, password); + + Connection connection = this.dataSourceBean.getConnection(username, password); + assertThat(connection).isInstanceOf(ConnectionImple.class); + + connection.commit(); + + verify(this.mockDataSource, times(1)).getXAConnection(username, password); + verify(mockXaConnection, times(1)).getConnection(); + verify(mockConnection, times(1)).commit(); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java new file mode 100644 index 00000000000..5e66bba76d4 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java @@ -0,0 +1,59 @@ +/* + * 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.narayana; + +import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; + +import org.junit.Before; +import org.junit.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Gytis Trikleris + */ +public class NarayanaRecoveryManagerBeanTests { + + private RecoveryManagerService recoveryManagerService; + + private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + + @Before + public void before() { + this.recoveryManagerService = mock(RecoveryManagerService.class); + this.narayanaRecoveryManagerBean = new NarayanaRecoveryManagerBean(this.recoveryManagerService); + } + + @Test + public void shouldCreateAndStartRecoveryManagerService() throws Exception { + this.narayanaRecoveryManagerBean.afterPropertiesSet(); + + verify(this.recoveryManagerService, times(1)).create(); + verify(this.recoveryManagerService, times(1)).start(); + } + + @Test + public void shouldStopAndDestroyRecoveryManagerService() throws Exception { + this.narayanaRecoveryManagerBean.destroy(); + + verify(this.recoveryManagerService, times(1)).stop(); + verify(this.recoveryManagerService, times(1)).destroy(); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java new file mode 100644 index 00000000000..6105d5be2a9 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.jta.narayana; + +import javax.jms.ConnectionFactory; +import javax.jms.XAConnectionFactory; +import javax.transaction.TransactionManager; + +import org.jboss.narayana.jta.jms.ConnectionFactoryProxy; +import org.jboss.narayana.jta.jms.JmsXAResourceRecoveryHelper; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.times; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; +import static org.mockito.Matchers.any; + +/** + * @author Gytis Trikleris + */ +public class NarayanaXAConnectionFactoryWrapperTests { + + private XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class); + + private TransactionManager transactionManager = mock(TransactionManager.class); + + private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean = mock(NarayanaRecoveryManagerBean.class); + + private NarayanaProperties narayanaProperties = mock(NarayanaProperties.class); + + private NarayanaXAConnectionFactoryWrapper wrapper = new NarayanaXAConnectionFactoryWrapper(this.transactionManager, + this.narayanaRecoveryManagerBean, this.narayanaProperties); + + @Test + public void wrap() { + ConnectionFactory wrapped = this.wrapper.wrapConnectionFactory(this.connectionFactory); + assertThat(wrapped).isInstanceOf(ConnectionFactoryProxy.class); + verify(this.narayanaRecoveryManagerBean, times(1)) + .registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class)); + verify(this.narayanaProperties, times(1)).getRecoveryJmsUser(); + verify(this.narayanaProperties, times(1)).getRecoveryJmsPass(); + } + + @Test + public void wrapWithCredentials() { + when(this.narayanaProperties.getRecoveryJmsUser()).thenReturn("userName"); + when(this.narayanaProperties.getRecoveryJmsPass()).thenReturn("password"); + ConnectionFactory wrapped = this.wrapper.wrapConnectionFactory(this.connectionFactory); + + assertThat(wrapped).isInstanceOf(ConnectionFactoryProxy.class); + verify(this.narayanaRecoveryManagerBean, times(1)) + .registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class)); + verify(this.narayanaProperties, times(2)).getRecoveryJmsUser(); + verify(this.narayanaProperties, times(1)).getRecoveryJmsPass(); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java new file mode 100644 index 00000000000..2b031898fde --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java @@ -0,0 +1,69 @@ +/* + * 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.narayana; + +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.times; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; +import static org.mockito.Matchers.any; + +/** + * @author Gytis Trikleris + */ +public class NarayanaXADataSourceWrapperTests { + + private XADataSource dataSource = mock(XADataSource.class); + + private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean = mock(NarayanaRecoveryManagerBean.class); + + private NarayanaProperties narayanaProperties = mock(NarayanaProperties.class); + + private NarayanaXADataSourceWrapper wrapper = new NarayanaXADataSourceWrapper(this.narayanaRecoveryManagerBean, + this.narayanaProperties); + + @Test + public void wrap() { + DataSource wrapped = this.wrapper.wrapDataSource(this.dataSource); + + assertThat(wrapped).isInstanceOf(NarayanaDataSourceBean.class); + verify(this.narayanaRecoveryManagerBean, times(1)) + .registerXAResourceRecoveryHelper(any(DataSourceXAResourceRecoveryHelper.class)); + verify(this.narayanaProperties, times(1)).getRecoveryDbUser(); + verify(this.narayanaProperties, times(1)).getRecoveryDbPass(); + } + + @Test + public void wrapWithCredentials() { + when(this.narayanaProperties.getRecoveryDbUser()).thenReturn("userName"); + when(this.narayanaProperties.getRecoveryDbPass()).thenReturn("password"); + DataSource wrapped = this.wrapper.wrapDataSource(this.dataSource); + + assertThat(wrapped).isInstanceOf(NarayanaDataSourceBean.class); + verify(this.narayanaRecoveryManagerBean, times(1)) + .registerXAResourceRecoveryHelper(any(DataSourceXAResourceRecoveryHelper.class)); + verify(this.narayanaProperties, times(2)).getRecoveryDbUser(); + verify(this.narayanaProperties, times(1)).getRecoveryDbPass(); + } + +} From 48800f18189a082f09f9a474ceeeabee70f15f99 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 6 Apr 2016 15:26:27 -0700 Subject: [PATCH 2/2] Polish contribution --- .../jta/NarayanaJtaConfiguration.java | 55 ++++--- spring-boot-dependencies/pom.xml | 64 ++++---- .../appendix-application-properties.adoc | 8 +- .../spring-boot-sample-jta-narayana/pom.xml | 111 +++++++------ .../main/java/sample/narayana/Account.java | 2 +- .../sample/narayana/AccountRepository.java | 2 +- .../java/sample/narayana/AccountService.java | 2 +- .../main/java/sample/narayana/Messages.java | 2 +- .../narayana/SampleNarayanaApplication.java | 8 +- .../narayana/SampleRuntimeException.java | 19 ++- .../SampleNarayanaApplicationTests.java | 39 +++-- .../spring-boot-starter-jta-narayana/pom.xml | 93 ++++++----- .../DataSourceXAResourceRecoveryHelper.java | 147 ++++++++++-------- .../NarayanaBeanFactoryPostProcessor.java | 36 +++-- .../narayana/NarayanaConfigurationBean.java | 71 +++++---- .../jta/narayana/NarayanaDataSourceBean.java | 30 ++-- .../boot/jta/narayana/NarayanaProperties.java | 33 ++-- .../narayana/NarayanaRecoveryManagerBean.java | 17 +- .../NarayanaXAConnectionFactoryWrapper.java | 50 ++++-- .../narayana/NarayanaXADataSourceWrapper.java | 40 +++-- ...taSourceXAResourceRecoveryHelperTests.java | 48 +++--- ...NarayanaBeanFactoryPostProcessorTests.java | 27 ++-- .../NarayanaConfigurationBeanTests.java | 121 ++++++++------ .../narayana/NarayanaDataSourceBeanTests.java | 49 +++--- .../NarayanaRecoveryManagerBeanTests.java | 29 ++-- ...rayanaXAConnectionFactoryWrapperTests.java | 46 +++--- .../NarayanaXADataSourceWrapperTests.java | 45 +++--- 27 files changed, 670 insertions(+), 524 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java index 0fa78ffac45..ac243d57f10 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/transaction/jta/NarayanaJtaConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -22,7 +22,6 @@ import javax.transaction.UserTransaction; import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; -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.jta.XAConnectionFactoryWrapper; @@ -42,15 +41,20 @@ import org.springframework.transaction.jta.JtaTransactionManager; /** * JTA Configuration for Narayana. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ @Configuration -@ConditionalOnClass({ JtaTransactionManager.class, com.arjuna.ats.jta.UserTransaction.class }) +@ConditionalOnClass({ JtaTransactionManager.class, + com.arjuna.ats.jta.UserTransaction.class }) @ConditionalOnMissingBean(PlatformTransactionManager.class) public class NarayanaJtaConfiguration { - @Autowired - private JtaProperties jtaProperties; + private final JtaProperties jtaProperties; + + public NarayanaJtaConfiguration(JtaProperties jtaProperties) { + this.jtaProperties = jtaProperties; + } @Bean @ConditionalOnMissingBean @@ -60,53 +64,57 @@ public class NarayanaJtaConfiguration { @Bean @ConditionalOnMissingBean - public NarayanaConfigurationBean narayanaConfigurationBean(NarayanaProperties narayanaProperties) { + public NarayanaConfigurationBean narayanaConfiguration( + NarayanaProperties properties) { if (this.jtaProperties.getLogDir() != null) { - narayanaProperties.setLogDir(this.jtaProperties.getLogDir()); + properties.setLogDir(this.jtaProperties.getLogDir()); } - if (this.jtaProperties.getTransactionManagerId() != null) { - narayanaProperties.setTransactionManagerId(this.jtaProperties.getTransactionManagerId()); + properties.setTransactionManagerId( + this.jtaProperties.getTransactionManagerId()); } - - return new NarayanaConfigurationBean(narayanaProperties); + return new NarayanaConfigurationBean(properties); } @Bean - @DependsOn("narayanaConfigurationBean") + @DependsOn("narayanaConfiguration") @ConditionalOnMissingBean public UserTransaction narayanaUserTransaction() { return com.arjuna.ats.jta.UserTransaction.userTransaction(); } @Bean - @DependsOn("narayanaConfigurationBean") + @DependsOn("narayanaConfiguration") @ConditionalOnMissingBean public TransactionManager narayanaTransactionManager() { return com.arjuna.ats.jta.TransactionManager.transactionManager(); } @Bean - @DependsOn("narayanaConfigurationBean") + @DependsOn("narayanaConfiguration") public RecoveryManagerService narayanaRecoveryManagerService() { return new RecoveryManagerService(); } @Bean - public NarayanaRecoveryManagerBean narayanaRecoveryManagerBean(RecoveryManagerService recoveryManagerService) { + public NarayanaRecoveryManagerBean narayanaRecoveryManager( + RecoveryManagerService recoveryManagerService) { return new NarayanaRecoveryManagerBean(recoveryManagerService); } @Bean - public JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager) { + public JtaTransactionManager transactionManager(UserTransaction userTransaction, + TransactionManager transactionManager) { return new JtaTransactionManager(userTransaction, transactionManager); } @Bean @ConditionalOnMissingBean(XADataSourceWrapper.class) - public XADataSourceWrapper xaDataSourceWrapper(NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, + public XADataSourceWrapper xaDataSourceWrapper( + NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, NarayanaProperties narayanaProperties) { - return new NarayanaXADataSourceWrapper(narayanaRecoveryManagerBean, narayanaProperties); + return new NarayanaXADataSourceWrapper(narayanaRecoveryManagerBean, + narayanaProperties); } @Bean @@ -121,9 +129,12 @@ public class NarayanaJtaConfiguration { @Bean @ConditionalOnMissingBean(XAConnectionFactoryWrapper.class) - public NarayanaXAConnectionFactoryWrapper xaConnectionFactoryWrapper(TransactionManager transactionManager, - NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, NarayanaProperties narayanaProperties) { - return new NarayanaXAConnectionFactoryWrapper(transactionManager, narayanaRecoveryManagerBean, narayanaProperties); + public NarayanaXAConnectionFactoryWrapper xaConnectionFactoryWrapper( + TransactionManager transactionManager, + NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, + NarayanaProperties narayanaProperties) { + return new NarayanaXAConnectionFactoryWrapper(transactionManager, + narayanaRecoveryManagerBean, narayanaProperties); } } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 597ac18d134..3de1f716d79 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -100,6 +100,7 @@ 1.1.6 2.2.10 3.3.0.Final + 7.3.0.Final 2.0.6 2.8.1 2.22.2 @@ -124,6 +125,7 @@ 1.10.19 2.14.2 5.1.38 + 5.3.2.Final 1.9.22 2.0.0 9.4.1208.jre7 @@ -174,8 +176,6 @@ 9f96c74 0.30 1.6.3 - 5.3.2.Final - 7.3.0.Final 3.2.1 @@ -401,11 +401,6 @@ spring-boot-starter-jta-bitronix 1.4.0.BUILD-SNAPSHOT
- - org.springframework.boot - spring-boot-starter-jta-narayana - 1.4.0.BUILD-SNAPSHOT - org.springframework.boot spring-boot-starter-undertow @@ -436,6 +431,11 @@ spring-boot-starter-mustache 1.4.0.BUILD-SNAPSHOT + + org.springframework.boot + spring-boot-starter-jta-narayana + 1.4.0.BUILD-SNAPSHOT + org.springframework.boot spring-boot-starter-remote-shell @@ -1740,11 +1740,36 @@ javassist ${javassist.version} + + org.jboss + jboss-transaction-spi + ${jboss-transaction-spi.version} + org.jboss.logging jboss-logging ${jboss-logging.version} + + org.jboss.narayana.jta + jdbc + ${narayana.version} + + + org.jboss.narayana.jta + jms + ${narayana.version} + + + org.jboss.narayana.jta + jta + ${narayana.version} + + + org.jboss.narayana.jts + narayana-jts-integration + ${narayana.version} + org.jdom jdom2 @@ -2219,31 +2244,6 @@ wsdl4j ${wsdl4j.version} - - org.jboss.narayana.jta - jta - ${narayana.version} - - - org.jboss.narayana.jta - jdbc - ${narayana.version} - - - org.jboss.narayana.jta - jms - ${narayana.version} - - - org.jboss.narayana.jts - narayana-jts-integration - ${narayana.version} - - - org.jboss - jboss-transaction-spi - ${jboss-transaction-spi.version} - diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index e8e42ffd589..20f9b7d7a18 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -716,17 +716,17 @@ content into your application; rather pick only the properties that you need. spring.jta.bitronix.properties.warn-about-zero-resource-transaction=true # Log a warning for transactions executed without a single enlisted resource. # NARAYANA - spring.jta.narayana.one-phase-commit=true # Enable or disable one phase commit optimisation spring.jta.narayana.default-timeout=60 # Set default transaction timeout in seconds + spring.jta.narayana.expiry-scanners=com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner # List of ExpiryScanner implementations + spring.jta.narayana.one-phase-commit=true # Enable or disable one phase commit optimisation spring.jta.narayana.periodic-recovery-period=120 # Set interval in which periodic recovery scans are performed in seconds spring.jta.narayana.recovery-backoff-period=10 # Set back off period between first and second phases of the recovery scan in seconds - spring.jta.narayana.xa-resource-orphan-filters=com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter,com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter # List of XAResourceOrphanFilter implementations - spring.jta.narayana.recovery-modules=com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule,com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule # List of RecoveryModule implementations - spring.jta.narayana.expiry-scanners=com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner # List of ExpiryScanner implementations spring.jta.narayana.recovery-db-user= # Database username to be used by recovery manager spring.jta.narayana.recovery-db-pass= # Database password to be used by recovery manager spring.jta.narayana.recovery-jms-user= # JMS username to be used by recovery manager spring.jta.narayana.recovery-jms-pass= # JMS password to be used by recovery manager + spring.jta.narayana.recovery-modules=com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule,com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule # List of RecoveryModule implementations + spring.jta.narayana.xa-resource-orphan-filters=com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter,com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter # List of XAResourceOrphanFilter implementations # EMBEDDED MONGODB ({sc-spring-boot-autoconfigure}/mongo/embedded/EmbeddedMongoProperties.{sc-ext}[EmbeddedMongoProperties]) spring.mongodb.embedded.features=SYNC_DELAY # Comma-separated list of features to enable. diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml b/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml index 4d7d9c32c62..eac68e382d6 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/pom.xml @@ -1,57 +1,56 @@ - - 4.0.0 - - spring-boot-samples - org.springframework.boot - 1.4.0.BUILD-SNAPSHOT - - spring-boot-sample-jta-narayana - Spring Boot Narayana JTA Sample - Spring Boot Narayana JTA Sample - http://projects.spring.io/spring-boot/ - - ${basedir}/../.. - - - - org.springframework - spring-jms - - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.springframework.boot - spring-boot-starter-jta-narayana - - - org.springframework.boot - spring-boot-starter-hornetq - - - org.hornetq - hornetq-jms-server - - - com.h2database - h2 - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - \ No newline at end of file + + 4.0.0 + + spring-boot-samples + org.springframework.boot + 1.4.0.BUILD-SNAPSHOT + + spring-boot-sample-jta-narayana + Spring Boot Narayana JTA Sample + Spring Boot Narayana JTA Sample + http://projects.spring.io/spring-boot/ + + ${basedir}/../.. + + + + org.springframework + spring-jms + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-jta-narayana + + + org.springframework.boot + spring-boot-starter-hornetq + + + org.hornetq + hornetq-jms-server + + + com.h2database + h2 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java index f588e09f722..ddada8120cd 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Account.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java index eb8ec8e9801..ebf3ef4deba 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java index 72f0d756d46..4333fdae4a1 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/AccountService.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java index 90ff54fb1bd..7ccf4218b54 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/Messages.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java index 9585d14489e..29054ef91cb 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleNarayanaApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -26,7 +26,8 @@ import org.springframework.context.ApplicationContext; public class SampleNarayanaApplication { public static void main(String[] args) throws Exception { - ApplicationContext context = SpringApplication.run(SampleNarayanaApplication.class, args); + ApplicationContext context = SpringApplication + .run(SampleNarayanaApplication.class, args); AccountService service = context.getBean(AccountService.class); AccountRepository repository = context.getBean(AccountRepository.class); service.createAccountAndNotify("josh"); @@ -34,7 +35,8 @@ public class SampleNarayanaApplication { try { // Using username "error" will cause service to throw SampleRuntimeException service.createAccountAndNotify("error"); - } catch (SampleRuntimeException ex) { + } + catch (SampleRuntimeException ex) { // Log message to let test case know that exception was thrown System.out.println(ex.getMessage()); } diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java index f65d4848971..1e6a697e0b6 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/main/java/sample/narayana/SampleRuntimeException.java @@ -1,8 +1,21 @@ +/* + * Copyright 2012-2016 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.narayana; -/** - * @author Gytis Trikleris - */ public class SampleRuntimeException extends RuntimeException { public SampleRuntimeException(String message) { diff --git a/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java b/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java index 4ec0ab2b85f..df9c59fb3bb 100644 --- a/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-jta-narayana/src/test/java/sample/narayana/SampleNarayanaApplicationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -16,16 +16,18 @@ package sample.narayana; -import org.hamcrest.Matcher; -import org.hamcrest.core.SubstringMatcher; +import org.assertj.core.api.Condition; import org.junit.Rule; import org.junit.Test; -import org.springframework.boot.test.OutputCapture; -import static org.junit.Assert.assertThat; +import org.springframework.boot.test.rule.OutputCapture; + +import static org.assertj.core.api.Assertions.assertThat; /** - * @author Gytis Trikleris + * Basic integration tests for demo application. + * + * @author Gytis Trikleris */ public class SampleNarayanaApplicationTests { @@ -36,25 +38,22 @@ public class SampleNarayanaApplicationTests { public void testTransactionRollback() throws Exception { SampleNarayanaApplication.main(new String[] {}); String output = this.outputCapture.toString(); - assertThat(output, containsString(1, "---->")); - assertThat(output, containsString(1, "----> josh")); - assertThat(output, containsString(2, "Count is 1")); - assertThat(output, containsString(1, "Simulated error")); + assertThat(output).has(substring(1, "---->")); + assertThat(output).has(substring(1, "----> josh")); + assertThat(output).has(substring(2, "Count is 1")); + assertThat(output).has(substring(1, "Simulated error")); } - private Matcher containsString(final int times, String s) { - return new SubstringMatcher(s) { - - @Override - protected String relationship() { - return "containing " + times + " times"; - } + private Condition substring(final int times, final String substring) { + return new Condition( + "containing '" + substring + "' " + times + " times") { @Override - protected boolean evalSubstringOf(String s) { + public boolean matches(String value) { int i = 0; - while (s.contains(this.substring)) { - s = s.substring(s.indexOf(this.substring) + this.substring.length()); + while (value.contains(substring)) { + int beginIndex = value.indexOf(substring) + substring.length(); + value = value.substring(beginIndex); i++; } return i == times; diff --git a/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml b/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml index 9b689932e08..c39459c499a 100644 --- a/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml +++ b/spring-boot-starters/spring-boot-starter-jta-narayana/pom.xml @@ -1,48 +1,47 @@ - - 4.0.0 - - spring-boot-starters - org.springframework.boot - 1.4.0.BUILD-SNAPSHOT - - spring-boot-starter-jta-narayana - Spring Boot Narayana JTA Starter - Spring Boot Narayana JTA Starter - http://projects.spring.io/spring-boot/ - - ${basedir}/../.. - - - - org.springframework.boot - spring-boot-starter - - - org.jboss.narayana.jta - jta - - - org.jboss.narayana.jta - jdbc - - - org.jboss.narayana.jta - jms - - - org.jboss.narayana.jts - narayana-jts-integration - - - org.jboss - jboss-transaction-spi - - - javax.transaction - javax.transaction-api - - - \ No newline at end of file + + 4.0.0 + + spring-boot-starters + org.springframework.boot + 1.4.0.BUILD-SNAPSHOT + + spring-boot-starter-jta-narayana + Spring Boot Narayana JTA Starter + Spring Boot Narayana JTA Starter + http://projects.spring.io/spring-boot/ + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.jboss + jboss-transaction-spi + + + org.jboss.narayana.jta + jdbc + + + org.jboss.narayana.jta + jms + + + org.jboss.narayana.jta + jta + + + org.jboss.narayana.jts + narayana-jts-integration + + + javax.transaction + javax.transaction-api + + + diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java index 6ff48c18f9c..5f33ee29806 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -17,8 +17,6 @@ package org.springframework.boot.jta.narayana; import java.sql.SQLException; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.sql.XAConnection; import javax.sql.XADataSource; @@ -27,34 +25,56 @@ import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.util.Assert; /** - * XAResourceRecoveryHelper implementation which gets Xids, which needs to be recovered, from the database. + * XAResourceRecoveryHelper implementation which gets XIDs, which needs to be recovered, + * from the database. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ -public class DataSourceXAResourceRecoveryHelper implements XAResourceRecoveryHelper, XAResource { +public class DataSourceXAResourceRecoveryHelper + implements XAResourceRecoveryHelper, XAResource { + + private static final XAResource[] NO_XA_RESOURCES = {}; - private static final Logger LOGGER = Logger.getLogger(DataSourceXAResourceRecoveryHelper.class.getName()); + private static final Log logger = LogFactory + .getLog(DataSourceXAResourceRecoveryHelper.class); private final XADataSource xaDataSource; private final String user; - private final String pass; + private final String password; private XAConnection xaConnection; private XAResource delegate; + /** + * Create a new {@link DataSourceXAResourceRecoveryHelper} instance. + * @param xaDataSource the XA data source + */ public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource) { this(xaDataSource, null, null); } - public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user, String pass) { + /** + * Create a new {@link DataSourceXAResourceRecoveryHelper} instance. + * @param xaDataSource the XA data source + * @param user the database user or {@code null} + * @param password the database password or {@code null} + */ + public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user, + String password) { + Assert.notNull(xaDataSource, "XADataSource must not be null"); this.xaDataSource = xaDataSource; this.user = user; - this.pass = pass; + this.password = password; } @Override @@ -67,103 +87,104 @@ public class DataSourceXAResourceRecoveryHelper implements XAResourceRecoveryHel if (connect()) { return new XAResource[] { this }; } + return NO_XA_RESOURCES; + } - return new XAResource[0]; + private boolean connect() { + if (this.delegate == null) { + try { + this.xaConnection = getXaConnection(); + this.delegate = this.xaConnection.getXAResource(); + } + catch (SQLException ex) { + logger.warn("Failed to create connection", ex); + return false; + } + } + return true; + } + + private XAConnection getXaConnection() throws SQLException { + if (this.user == null && this.password == null) { + return this.xaDataSource.getXAConnection(); + } + return this.xaDataSource.getXAConnection(this.user, this.password); } @Override - public Xid[] recover(int i) throws XAException { + public Xid[] recover(int flag) throws XAException { try { - return this.delegate.recover(i); + return getDelegate(true).recover(flag); } finally { - if (i == XAResource.TMENDRSCAN) { + if (flag == XAResource.TMENDRSCAN) { disconnect(); } } } + private void disconnect() throws XAException { + try { + this.xaConnection.close(); + } + catch (SQLException e) { + logger.warn("Failed to close connection", e); + } + finally { + this.xaConnection = null; + this.delegate = null; + } + } + @Override - public void start(Xid xid, int i) throws XAException { - this.delegate.start(xid, i); + public void start(Xid xid, int flags) throws XAException { + getDelegate(true).start(xid, flags); } @Override - public void end(Xid xid, int i) throws XAException { - this.delegate.end(xid, i); + public void end(Xid xid, int flags) throws XAException { + getDelegate(true).end(xid, flags); } @Override public int prepare(Xid xid) throws XAException { - return this.delegate.prepare(xid); + return getDelegate(true).prepare(xid); } @Override - public void commit(Xid xid, boolean b) throws XAException { - this.delegate.commit(xid, b); + public void commit(Xid xid, boolean onePhase) throws XAException { + getDelegate(true).commit(xid, onePhase); } @Override public void rollback(Xid xid) throws XAException { - this.delegate.rollback(xid); + getDelegate(true).rollback(xid); } @Override public boolean isSameRM(XAResource xaResource) throws XAException { - return this.delegate.isSameRM(xaResource); + return getDelegate(true).isSameRM(xaResource); } @Override public void forget(Xid xid) throws XAException { - this.delegate.forget(xid); + getDelegate(true).forget(xid); } @Override public int getTransactionTimeout() throws XAException { - return this.delegate.getTransactionTimeout(); + return getDelegate(true).getTransactionTimeout(); } @Override - public boolean setTransactionTimeout(int i) throws XAException { - return this.delegate.setTransactionTimeout(i); - } - - private boolean connect() { - if (this.delegate != null) { - return true; - } - - try { - this.xaConnection = getXaConnection(); - this.delegate = this.xaConnection.getXAResource(); - } - catch (SQLException e) { - LOGGER.log(Level.WARNING, "Failed to create connection", e); - return false; - } - - return true; - } - - private void disconnect() throws XAException { - try { - this.xaConnection.close(); - } - catch (SQLException e) { - LOGGER.log(Level.WARNING, "Failed to close connection", e); - } - finally { - this.xaConnection = null; - this.delegate = null; - } + public boolean setTransactionTimeout(int seconds) throws XAException { + return getDelegate(true).setTransactionTimeout(seconds); } - private XAConnection getXaConnection() throws SQLException { - if (this.user == null && this.pass == null) { - return this.xaDataSource.getXAConnection(); - } - - return this.xaDataSource.getXAConnection(this.user, this.pass); + private XAResource getDelegate(boolean required) { + Assert.state(this.delegate != null || !required, + "Connection has not been opened"); + return this.delegate; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java index 8b7a4513d98..7ca2ca5b538 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -26,44 +26,54 @@ import org.springframework.core.Ordered; /** * {@link BeanFactoryPostProcessor} to automatically setup correct beans ordering. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ -public class NarayanaBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { +public class NarayanaBeanFactoryPostProcessor + implements BeanFactoryPostProcessor, Ordered { private static final String[] NO_BEANS = {}; private static final int ORDER = Ordered.LOWEST_PRECEDENCE; @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { - String[] transactionManagers = beanFactory.getBeanNamesForType(TransactionManager.class, true, false); - String[] recoveryManagers = beanFactory.getBeanNamesForType(NarayanaRecoveryManagerBean.class, true, false); - + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + String[] transactionManagers = beanFactory + .getBeanNamesForType(TransactionManager.class, true, false); + String[] recoveryManagers = beanFactory + .getBeanNamesForType(NarayanaRecoveryManagerBean.class, true, false); addBeanDependencies(beanFactory, transactionManagers, "javax.sql.DataSource"); addBeanDependencies(beanFactory, recoveryManagers, "javax.sql.DataSource"); - addBeanDependencies(beanFactory, transactionManagers, "javax.jms.ConnectionFactory"); + addBeanDependencies(beanFactory, transactionManagers, + "javax.jms.ConnectionFactory"); addBeanDependencies(beanFactory, recoveryManagers, "javax.jms.ConnectionFactory"); } - private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, String[] beanNames, String dependencyType) { + private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, + String[] beanNames, String dependencyType) { for (String beanName : beanNames) { addBeanDependencies(beanFactory, beanName, dependencyType); } } - private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, String beanName, String dependencyType) { - for (String dependentBeanName : getBeanNamesForType(beanFactory, dependencyType)) { + private void addBeanDependencies(ConfigurableListableBeanFactory beanFactory, + String beanName, String dependencyType) { + for (String dependentBeanName : getBeanNamesForType(beanFactory, + dependencyType)) { beanFactory.registerDependentBean(beanName, dependentBeanName); } } - private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, String type) { + private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, + String type) { try { return beanFactory.getBeanNamesForType(Class.forName(type), true, false); } catch (ClassNotFoundException ex) { // Ignore - } catch (NoClassDefFoundError ex) { + } + catch (NoClassDefFoundError ex) { // Ignore } return NO_BEANS; diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java index 311a2c503fd..08094c375c6 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -31,16 +31,17 @@ import org.springframework.beans.factory.InitializingBean; /** * Bean that configures Narayana transaction manager. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ public class NarayanaConfigurationBean implements InitializingBean { private static final String JBOSSTS_PROPERTIES_FILE_NAME = "jbossts-properties.xml"; - private final NarayanaProperties narayanaProperties; + private final NarayanaProperties properties; public NarayanaConfigurationBean(NarayanaProperties narayanaProperties) { - this.narayanaProperties = narayanaProperties; + this.properties = narayanaProperties; } @Override @@ -48,59 +49,75 @@ public class NarayanaConfigurationBean implements InitializingBean { if (isPropertiesFileAvailable()) { return; } - - setNodeIdentifier(this.narayanaProperties.getTransactionManagerId()); - setObjectStoreDir(this.narayanaProperties.getLogDir()); - setCommitOnePhase(this.narayanaProperties.isOnePhaseCommit()); - setDefaultTimeout(this.narayanaProperties.getDefaultTimeout()); - setPeriodicRecoveryPeriod(this.narayanaProperties.getPeriodicRecoveryPeriod()); - setRecoveryBackoffPeriod(this.narayanaProperties.getRecoveryBackoffPeriod()); - setXaResourceOrphanFilters(this.narayanaProperties.getXaResourceOrphanFilters()); - setRecoveryModules(this.narayanaProperties.getRecoveryModules()); - setExpiryScanners(this.narayanaProperties.getExpiryScanners()); + setNodeIdentifier(this.properties.getTransactionManagerId()); + setObjectStoreDir(this.properties.getLogDir()); + setCommitOnePhase(this.properties.isOnePhaseCommit()); + setDefaultTimeout(this.properties.getDefaultTimeout()); + setPeriodicRecoveryPeriod(this.properties.getPeriodicRecoveryPeriod()); + setRecoveryBackoffPeriod(this.properties.getRecoveryBackoffPeriod()); + setXaResourceOrphanFilters(this.properties.getXaResourceOrphanFilters()); + setRecoveryModules(this.properties.getRecoveryModules()); + setExpiryScanners(this.properties.getExpiryScanners()); } private boolean isPropertiesFileAvailable() { - return Thread.currentThread().getContextClassLoader().getResource(JBOSSTS_PROPERTIES_FILE_NAME) != null; + return Thread.currentThread().getContextClassLoader() + .getResource(JBOSSTS_PROPERTIES_FILE_NAME) != null; } - private void setNodeIdentifier(String nodeIdentifier) throws CoreEnvironmentBeanException { - BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier); + private void setNodeIdentifier(String nodeIdentifier) + throws CoreEnvironmentBeanException { + getPopulator(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier); } private void setObjectStoreDir(String objectStoreDir) { - BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).setObjectStoreDir(objectStoreDir); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") + getPopulator(ObjectStoreEnvironmentBean.class).setObjectStoreDir(objectStoreDir); + getPopulator(ObjectStoreEnvironmentBean.class, "communicationStore") + .setObjectStoreDir(objectStoreDir); + getPopulator(ObjectStoreEnvironmentBean.class, "stateStore") .setObjectStoreDir(objectStoreDir); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").setObjectStoreDir(objectStoreDir); } private void setCommitOnePhase(boolean isCommitOnePhase) { - BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).setCommitOnePhase(isCommitOnePhase); + getPopulator(CoordinatorEnvironmentBean.class) + .setCommitOnePhase(isCommitOnePhase); } private void setDefaultTimeout(int defaultTimeout) { - BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).setDefaultTimeout(defaultTimeout); + getPopulator(CoordinatorEnvironmentBean.class).setDefaultTimeout(defaultTimeout); } private void setPeriodicRecoveryPeriod(int periodicRecoveryPeriod) { - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setPeriodicRecoveryPeriod(periodicRecoveryPeriod); + getPopulator(RecoveryEnvironmentBean.class) + .setPeriodicRecoveryPeriod(periodicRecoveryPeriod); } private void setRecoveryBackoffPeriod(int recoveryBackoffPeriod) { - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setRecoveryBackoffPeriod(recoveryBackoffPeriod); + getPopulator(RecoveryEnvironmentBean.class) + .setRecoveryBackoffPeriod(recoveryBackoffPeriod); } private void setXaResourceOrphanFilters(List xaResourceOrphanFilters) { - BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).setXaResourceOrphanFilterClassNames(xaResourceOrphanFilters); + getPopulator(JTAEnvironmentBean.class) + .setXaResourceOrphanFilterClassNames(xaResourceOrphanFilters); } private void setRecoveryModules(List recoveryModules) { - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setRecoveryModuleClassNames(recoveryModules); + getPopulator(RecoveryEnvironmentBean.class) + .setRecoveryModuleClassNames(recoveryModules); } private void setExpiryScanners(List expiryScanners) { - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).setExpiryScannerClassNames(expiryScanners); + getPopulator(RecoveryEnvironmentBean.class) + .setExpiryScannerClassNames(expiryScanners); + } + + private T getPopulator(Class beanClass) { + return BeanPopulator.getDefaultInstance(beanClass); + } + + private T getPopulator(Class beanClass, String name) { + return BeanPopulator.getNamedInstance(beanClass, name); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java index 1421ec499f9..fd5c63f9966 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -29,16 +29,26 @@ import javax.sql.XADataSource; import com.arjuna.ats.internal.jdbc.ConnectionManager; import com.arjuna.ats.jdbc.TransactionalDriver; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + /** - * {@link DataSource} implementation wrapping {@link XADataSource} and using {@link ConnectionManager} to acquire connections. + * {@link DataSource} implementation wrapping {@link XADataSource} and using + * {@link ConnectionManager} to acquire connections. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ public class NarayanaDataSourceBean implements DataSource { private final XADataSource xaDataSource; + /** + * Create a new {@link NarayanaDataSourceBean} instance. + * @param xaDataSource the XA DataSource + */ public NarayanaDataSourceBean(XADataSource xaDataSource) { + Assert.notNull(xaDataSource, "XADataSource must not be null"); this.xaDataSource = xaDataSource; } @@ -46,17 +56,16 @@ public class NarayanaDataSourceBean implements DataSource { public Connection getConnection() throws SQLException { Properties properties = new Properties(); properties.put(TransactionalDriver.XADataSource, this.xaDataSource); - return ConnectionManager.create(null, properties); } @Override - public Connection getConnection(String username, String password) throws SQLException { + public Connection getConnection(String username, String password) + throws SQLException { Properties properties = new Properties(); properties.put(TransactionalDriver.XADataSource, this.xaDataSource); properties.put(TransactionalDriver.userName, username); properties.put(TransactionalDriver.password, password); - return ConnectionManager.create(null, properties); } @@ -91,20 +100,15 @@ public class NarayanaDataSourceBean implements DataSource { if (isWrapperFor(iface)) { return (T) this; } - else if (isWrapperFor(iface, this.xaDataSource.getClass())) { + if (ClassUtils.isAssignableValue(iface, this.xaDataSource)) { return (T) this.xaDataSource; } - throw new SQLException(getClass() + " is not a wrapper for " + iface); } @Override public boolean isWrapperFor(Class iface) throws SQLException { - return isWrapperFor(iface, getClass()); - } - - private boolean isWrapperFor(Class iface, Class wrapperIface) { - return iface.isAssignableFrom(wrapperIface); + return iface.isAssignableFrom(getClass()); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java index 1962fe43fbb..730d4736cb8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -22,10 +22,11 @@ import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; /** - * Subset of Narayana properties which can be configured via Spring configuration. Use jbossts-properties.xml for complete - * configuration. + * Subset of Narayana properties which can be configured via Spring configuration. Use + * jbossts-properties.xml for complete configuration. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ @ConfigurationProperties(prefix = NarayanaProperties.PROPERTIES_PREFIX) public class NarayanaProperties { @@ -56,12 +57,14 @@ public class NarayanaProperties { private int defaultTimeout = 60; /** - * Interval in which periodic recovery scans are performed in seconds. Default: 120 + * Interval in which periodic recovery scans are performed in seconds. Default: + * 120 */ private int periodicRecoveryPeriod = 120; /** - * Back off period between first and second phases of the recovery scan in seconds. Default: 10 + * Back off period between first and second phases of the recovery scan in seconds. + * Default: 10 */ private int recoveryBackoffPeriod = 10; @@ -88,8 +91,11 @@ public class NarayanaProperties { /** * List of orphan filters. Default: *
    - *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter
  • - *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter
  • + *
  • com.arjuna.ats.internal.jta.recovery.arjunacore. + * JTATransactionLogXAResourceOrphanFilter
  • + *
  • + * com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter + *
  • *
*/ private List xaResourceOrphanFilters = Arrays.asList( @@ -103,17 +109,19 @@ public class NarayanaProperties { *
  • com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule
  • * */ - private List recoveryModules = Arrays.asList("com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", + private List recoveryModules = Arrays.asList( + "com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule"); /** * List of expiry scanners. Default: *
      - *
    • com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner
    • + *
    • com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner + *
    • *
    */ - private List expiryScanners = Arrays - .asList("com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); + private List expiryScanners = Arrays.asList( + "com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); public String getLogDir() { return this.logDir; @@ -218,4 +226,5 @@ public class NarayanaProperties { public void setRecoveryJmsPass(String recoveryJmsPass) { this.recoveryJmsPass = recoveryJmsPass; } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java index 62c2f976bd9..3bbb47356b6 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -24,17 +24,20 @@ import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; /** * Bean to set up Narayana recovery manager. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ public class NarayanaRecoveryManagerBean implements InitializingBean, DisposableBean { private final RecoveryManagerService recoveryManagerService; public NarayanaRecoveryManagerBean(RecoveryManagerService recoveryManagerService) { + Assert.notNull(recoveryManagerService, "RecoveryManagerService must not be null"); this.recoveryManagerService = recoveryManagerService; } @@ -50,8 +53,10 @@ public class NarayanaRecoveryManagerBean implements InitializingBean, Disposable this.recoveryManagerService.destroy(); } - void registerXAResourceRecoveryHelper(XAResourceRecoveryHelper xaResourceRecoveryHelper) { - getXARecoveryModule(RecoveryManager.manager()).addXAResourceRecoveryHelper(xaResourceRecoveryHelper); + void registerXAResourceRecoveryHelper( + XAResourceRecoveryHelper xaResourceRecoveryHelper) { + getXARecoveryModule(RecoveryManager.manager()) + .addXAResourceRecoveryHelper(xaResourceRecoveryHelper); } private XARecoveryModule getXARecoveryModule(RecoveryManager recoveryManager) { @@ -60,8 +65,8 @@ public class NarayanaRecoveryManagerBean implements InitializingBean, Disposable return (XARecoveryModule) recoveryModule; } } - - throw new IllegalStateException("XARecoveryModule is not registered with recovery manager"); + throw new IllegalStateException( + "XARecoveryModule is not registered with recovery manager"); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java index 06c4f672659..01bdf220c45 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -26,41 +26,57 @@ import org.jboss.narayana.jta.jms.JmsXAResourceRecoveryHelper; import org.jboss.narayana.jta.jms.TransactionHelperImpl; import org.springframework.boot.jta.XAConnectionFactoryWrapper; +import org.springframework.util.Assert; /** - * {@link XAConnectionFactoryWrapper} that uses {@link ConnectionFactoryProxy} to wrap an {@link XAConnectionFactory}. + * {@link XAConnectionFactoryWrapper} that uses {@link ConnectionFactoryProxy} to wrap an + * {@link XAConnectionFactory}. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ public class NarayanaXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper { private final TransactionManager transactionManager; - private final NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + private final NarayanaRecoveryManagerBean recoveryManager; - private final NarayanaProperties narayanaProperties; + private final NarayanaProperties properties; + /** + * Create a new {@link NarayanaXAConnectionFactoryWrapper} instance. + * @param transactionManager the underlying transaction manager + * @param recoveryManager the underlying recovery manager + * @param properties the Narayana properties + */ public NarayanaXAConnectionFactoryWrapper(TransactionManager transactionManager, - NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, NarayanaProperties narayanaProperties) { + NarayanaRecoveryManagerBean recoveryManager, NarayanaProperties properties) { + Assert.notNull(transactionManager, "TransactionManager must not be null"); + Assert.notNull(recoveryManager, "RecoveryManager must not be null"); + Assert.notNull(properties, "Properties must not be null"); this.transactionManager = transactionManager; - this.narayanaRecoveryManagerBean = narayanaRecoveryManagerBean; - this.narayanaProperties = narayanaProperties; + this.recoveryManager = recoveryManager; + this.properties = properties; } @Override - public ConnectionFactory wrapConnectionFactory(XAConnectionFactory xaConnectionFactory) { - this.narayanaRecoveryManagerBean.registerXAResourceRecoveryHelper(getRecoveryHelper(xaConnectionFactory)); - - return new ConnectionFactoryProxy(xaConnectionFactory, new TransactionHelperImpl(this.transactionManager)); + public ConnectionFactory wrapConnectionFactory( + XAConnectionFactory xaConnectionFactory) { + XAResourceRecoveryHelper recoveryHelper = getRecoveryHelper(xaConnectionFactory); + this.recoveryManager.registerXAResourceRecoveryHelper(recoveryHelper); + return new ConnectionFactoryProxy(xaConnectionFactory, + new TransactionHelperImpl(this.transactionManager)); } - private XAResourceRecoveryHelper getRecoveryHelper(XAConnectionFactory xaConnectionFactory) { - if (this.narayanaProperties.getRecoveryJmsUser() == null && this.narayanaProperties.getRecoveryJmsPass() == null) { + private XAResourceRecoveryHelper getRecoveryHelper( + XAConnectionFactory xaConnectionFactory) { + if (this.properties.getRecoveryJmsUser() == null + && this.properties.getRecoveryJmsPass() == null) { return new JmsXAResourceRecoveryHelper(xaConnectionFactory); } - - return new JmsXAResourceRecoveryHelper(xaConnectionFactory, this.narayanaProperties.getRecoveryJmsUser(), - this.narayanaProperties.getRecoveryJmsPass()); + return new JmsXAResourceRecoveryHelper(xaConnectionFactory, + this.properties.getRecoveryJmsUser(), + this.properties.getRecoveryJmsPass()); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java index 7bb844164f4..e7705be90a7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -22,38 +22,48 @@ import javax.sql.XADataSource; import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper; import org.springframework.boot.jta.XADataSourceWrapper; +import org.springframework.util.Assert; /** - * {@link XADataSourceWrapper} that uses {@link NarayanaDataSourceBean} to wrap an {@link XADataSource}. + * {@link XADataSourceWrapper} that uses {@link NarayanaDataSourceBean} to wrap an + * {@link XADataSource}. * - * @author Gytis Trikleris + * @author Gytis Trikleris + * @since 1.4.0 */ public class NarayanaXADataSourceWrapper implements XADataSourceWrapper { - private final NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + private final NarayanaRecoveryManagerBean recoveryManager; - private final NarayanaProperties narayanaProperties; + private final NarayanaProperties properties; - public NarayanaXADataSourceWrapper(NarayanaRecoveryManagerBean narayanaRecoveryManagerBean, - NarayanaProperties narayanaProperties) { - this.narayanaRecoveryManagerBean = narayanaRecoveryManagerBean; - this.narayanaProperties = narayanaProperties; + /** + * Create a new {@link NarayanaXADataSourceWrapper} instance. + * @param recoveryManager the underlying recovery manager + * @param properties the Narayana properties + */ + public NarayanaXADataSourceWrapper(NarayanaRecoveryManagerBean recoveryManager, + NarayanaProperties properties) { + Assert.notNull(recoveryManager, "RecoveryManager must not be null"); + Assert.notNull(properties, "Properties must not be null"); + this.recoveryManager = recoveryManager; + this.properties = properties; } @Override public DataSource wrapDataSource(XADataSource dataSource) { - this.narayanaRecoveryManagerBean.registerXAResourceRecoveryHelper(getRecoveryHelper(dataSource)); - + XAResourceRecoveryHelper recoveryHelper = getRecoveryHelper(dataSource); + this.recoveryManager.registerXAResourceRecoveryHelper(recoveryHelper); return new NarayanaDataSourceBean(dataSource); } private XAResourceRecoveryHelper getRecoveryHelper(XADataSource dataSource) { - if (this.narayanaProperties.getRecoveryDbUser() == null && this.narayanaProperties.getRecoveryDbPass() == null) { + if (this.properties.getRecoveryDbUser() == null + && this.properties.getRecoveryDbPass() == null) { return new DataSourceXAResourceRecoveryHelper(dataSource); } - - return new DataSourceXAResourceRecoveryHelper(dataSource, this.narayanaProperties.getRecoveryDbUser(), - this.narayanaProperties.getRecoveryDbPass()); + return new DataSourceXAResourceRecoveryHelper(dataSource, + this.properties.getRecoveryDbUser(), this.properties.getRecoveryDbPass()); } } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java index ab9b74d23cb..06eff9acdda 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/DataSourceXAResourceRecoveryHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -27,14 +27,16 @@ import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.times; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.when; +import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link DataSourceXAResourceRecoveryHelper}. + * + * @author Gytis Trikleris */ public class DataSourceXAResourceRecoveryHelperTests { @@ -53,8 +55,8 @@ public class DataSourceXAResourceRecoveryHelperTests { this.xaResource = mock(XAResource.class); this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.xaDataSource); - when(this.xaDataSource.getXAConnection()).thenReturn(this.xaConnection); - when(this.xaConnection.getXAResource()).thenReturn(this.xaResource); + given(this.xaDataSource.getXAConnection()).willReturn(this.xaConnection); + given(this.xaConnection.getXAResource()).willReturn(this.xaResource); } @Test @@ -67,10 +69,12 @@ public class DataSourceXAResourceRecoveryHelperTests { } @Test - public void shouldCreateConnectionWithCredentialsAndGetXAResource() throws SQLException { - when(this.xaDataSource.getXAConnection(anyString(), anyString())).thenReturn(this.xaConnection); - this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.xaDataSource, "username", "password"); - + public void shouldCreateConnectionWithCredentialsAndGetXAResource() + throws SQLException { + given(this.xaDataSource.getXAConnection(anyString(), anyString())) + .willReturn(this.xaConnection); + this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.xaDataSource, + "username", "password"); XAResource[] xaResources = this.recoveryHelper.getXAResources(); assertThat(xaResources.length).isEqualTo(1); assertThat(xaResources[0]).isSameAs(this.recoveryHelper); @@ -80,10 +84,9 @@ public class DataSourceXAResourceRecoveryHelperTests { @Test public void shouldFailToCreateConnectionAndNotGetXAResource() throws SQLException { - when(this.xaDataSource.getXAConnection()).thenThrow(new SQLException("Test exception")); - + given(this.xaDataSource.getXAConnection()) + .willThrow(new SQLException("Test exception")); XAResource[] xaResources = this.recoveryHelper.getXAResources(); - assertThat(xaResources.length).isEqualTo(0); verify(this.xaDataSource, times(1)).getXAConnection(); verify(this.xaConnection, times(0)).getXAResource(); @@ -93,15 +96,14 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateRecoverCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.recover(XAResource.TMSTARTRSCAN); - verify(this.xaResource, times(1)).recover(XAResource.TMSTARTRSCAN); } @Test - public void shouldDelegateRecoverCallAndCloseConnection() throws XAException, SQLException { + public void shouldDelegateRecoverCallAndCloseConnection() + throws XAException, SQLException { this.recoveryHelper.getXAResources(); this.recoveryHelper.recover(XAResource.TMENDRSCAN); - verify(this.xaResource, times(1)).recover(XAResource.TMENDRSCAN); verify(this.xaConnection, times(1)).close(); } @@ -110,7 +112,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateStartCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.start(null, 0); - verify(this.xaResource, times(1)).start(null, 0); } @@ -118,7 +119,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateEndCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.end(null, 0); - verify(this.xaResource, times(1)).end(null, 0); } @@ -126,7 +126,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegatePrepareCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.prepare(null); - verify(this.xaResource, times(1)).prepare(null); } @@ -134,7 +133,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateCommitCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.commit(null, true); - verify(this.xaResource, times(1)).commit(null, true); } @@ -142,7 +140,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateRollbackCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.rollback(null); - verify(this.xaResource, times(1)).rollback(null); } @@ -150,7 +147,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateIsSameRMCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.isSameRM(null); - verify(this.xaResource, times(1)).isSameRM(null); } @@ -158,7 +154,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateForgetCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.forget(null); - verify(this.xaResource, times(1)).forget(null); } @@ -166,7 +161,6 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateGetTransactionTimeoutCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.getTransactionTimeout(); - verify(this.xaResource, times(1)).getTransactionTimeout(); } @@ -174,9 +168,7 @@ public class DataSourceXAResourceRecoveryHelperTests { public void shouldDelegateSetTransactionTimeoutCall() throws XAException { this.recoveryHelper.getXAResources(); this.recoveryHelper.setTransactionTimeout(0); - verify(this.xaResource, times(1)).setTransactionTimeout(0); } } - diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java index c0e25db46c6..07f50888c43 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaBeanFactoryPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -27,12 +27,14 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.spy; -import static org.mockito.BDDMockito.verify; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaBeanFactoryPostProcessor}. + * + * @author Gytis Trikleris */ public class NarayanaBeanFactoryPostProcessorTests { @@ -41,16 +43,17 @@ public class NarayanaBeanFactoryPostProcessorTests { @Test public void setsDependsOn() { DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); - this.context = new AnnotationConfigApplicationContext(beanFactory); this.context.register(Config.class); this.context.refresh(); - - verify(beanFactory).registerDependentBean("narayanaTransactionManager", "dataSource"); - verify(beanFactory).registerDependentBean("narayanaTransactionManager", "connectionFactory"); - verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", "dataSource"); - verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", "connectionFactory"); - + verify(beanFactory).registerDependentBean("narayanaTransactionManager", + "dataSource"); + verify(beanFactory).registerDependentBean("narayanaTransactionManager", + "connectionFactory"); + verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", + "dataSource"); + verify(beanFactory).registerDependentBean("narayanaRecoveryManagerBean", + "connectionFactory"); this.context.close(); } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java index e60e82781d0..a09221f8b51 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaConfigurationBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -25,52 +25,63 @@ import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean; import com.arjuna.ats.jta.common.JTAEnvironmentBean; import com.arjuna.common.internal.util.propertyservice.BeanPopulator; - import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaConfigurationBean}. + * + * @author Gytis Trikleris */ public class NarayanaConfigurationBeanTests { @Test public void shouldSetDefaultProperties() throws Exception { NarayanaProperties narayanaProperties = new NarayanaProperties(); - NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean(narayanaProperties); + NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean( + narayanaProperties); narayanaConfigurationBean.afterPropertiesSet(); - assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).getNodeIdentifier()).isEqualTo("1"); - assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).getObjectStoreDir()) - .isEqualTo("target/tx-object-store"); - assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore").getObjectStoreDir()) - .isEqualTo("target/tx-object-store"); - assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").getObjectStoreDir()) - .isEqualTo("target/tx-object-store"); - assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).isCommitOnePhase()).isTrue(); - assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).getDefaultTimeout()).isEqualTo(60); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getPeriodicRecoveryPeriod()).isEqualTo(120); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryBackoffPeriod()).isEqualTo(10); - + assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class) + .getNodeIdentifier()).isEqualTo("1"); + assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class) + .getObjectStoreDir()).isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator + .getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") + .getObjectStoreDir()).isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator + .getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore") + .getObjectStoreDir()).isEqualTo("target/tx-object-store"); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class) + .isCommitOnePhase()).isTrue(); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class) + .getDefaultTimeout()).isEqualTo(60); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getPeriodicRecoveryPeriod()).isEqualTo(120); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getRecoveryBackoffPeriod()).isEqualTo(10); List xaResourceOrphanFilters = Arrays.asList( "com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter", "com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter"); - assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceOrphanFilterClassNames()) - .isEqualTo(xaResourceOrphanFilters); + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class) + .getXaResourceOrphanFilterClassNames()) + .isEqualTo(xaResourceOrphanFilters); - List recoveryModules = Arrays.asList("com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", + List recoveryModules = Arrays.asList( + "com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule", "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule"); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryModuleClassNames()) - .isEqualTo(recoveryModules); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getRecoveryModuleClassNames()).isEqualTo(recoveryModules); - List expiryScanners = Arrays - .asList("com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getExpiryScannerClassNames()) - .isEqualTo(expiryScanners); + List expiryScanners = Arrays.asList( + "com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner"); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getExpiryScannerClassNames()).isEqualTo(expiryScanners); - assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceRecoveryClassNames()).isEmpty(); + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class) + .getXaResourceRecoveryClassNames()).isEmpty(); } @Test @@ -82,30 +93,44 @@ public class NarayanaConfigurationBeanTests { narayanaProperties.setPeriodicRecoveryPeriod(2); narayanaProperties.setRecoveryBackoffPeriod(3); narayanaProperties.setOnePhaseCommit(false); - narayanaProperties.setXaResourceOrphanFilters(Arrays.asList("test-filter-1", "test-filter-2")); - narayanaProperties.setRecoveryModules(Arrays.asList("test-module-1", "test-module-2")); - narayanaProperties.setExpiryScanners(Arrays.asList("test-scanner-1", "test-scanner-2")); - - NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean(narayanaProperties); + narayanaProperties.setXaResourceOrphanFilters( + Arrays.asList("test-filter-1", "test-filter-2")); + narayanaProperties + .setRecoveryModules(Arrays.asList("test-module-1", "test-module-2")); + narayanaProperties + .setExpiryScanners(Arrays.asList("test-scanner-1", "test-scanner-2")); + + NarayanaConfigurationBean narayanaConfigurationBean = new NarayanaConfigurationBean( + narayanaProperties); narayanaConfigurationBean.afterPropertiesSet(); - assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class).getNodeIdentifier()).isEqualTo("test-id"); - assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class).getObjectStoreDir()) - .isEqualTo("test-dir"); - assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore").getObjectStoreDir()) - .isEqualTo("test-dir"); - assertThat(BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore").getObjectStoreDir()) - .isEqualTo("test-dir"); - assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).isCommitOnePhase()).isFalse(); - assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class).getDefaultTimeout()).isEqualTo(1); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getPeriodicRecoveryPeriod()).isEqualTo(2); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryBackoffPeriod()).isEqualTo(3); - assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class).getXaResourceOrphanFilterClassNames()) - .isEqualTo(Arrays.asList("test-filter-1", "test-filter-2")); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getRecoveryModuleClassNames()) - .isEqualTo(Arrays.asList("test-module-1", "test-module-2")); - assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class).getExpiryScannerClassNames()) - .isEqualTo(Arrays.asList("test-scanner-1", "test-scanner-2")); + assertThat(BeanPopulator.getDefaultInstance(CoreEnvironmentBean.class) + .getNodeIdentifier()).isEqualTo("test-id"); + assertThat(BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class) + .getObjectStoreDir()).isEqualTo("test-dir"); + assertThat(BeanPopulator + .getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") + .getObjectStoreDir()).isEqualTo("test-dir"); + assertThat(BeanPopulator + .getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore") + .getObjectStoreDir()).isEqualTo("test-dir"); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class) + .isCommitOnePhase()).isFalse(); + assertThat(BeanPopulator.getDefaultInstance(CoordinatorEnvironmentBean.class) + .getDefaultTimeout()).isEqualTo(1); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getPeriodicRecoveryPeriod()).isEqualTo(2); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getRecoveryBackoffPeriod()).isEqualTo(3); + assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class) + .getXaResourceOrphanFilterClassNames()) + .isEqualTo(Arrays.asList("test-filter-1", "test-filter-2")); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getRecoveryModuleClassNames()) + .isEqualTo(Arrays.asList("test-module-1", "test-module-2")); + assertThat(BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) + .getExpiryScannerClassNames()) + .isEqualTo(Arrays.asList("test-scanner-1", "test-scanner-2")); } } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java index 3521a8f1d75..fada25acca9 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaDataSourceBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -30,24 +30,26 @@ import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.times; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.when; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaDataSourceBean}. + * + * @author Gytis Trikleris */ public class NarayanaDataSourceBeanTests { - private XADataSource mockDataSource; + private XADataSource dataSource; private NarayanaDataSourceBean dataSourceBean; @Before public void before() { - this.mockDataSource = mock(XADataSource.class); - this.dataSourceBean = new NarayanaDataSourceBean(this.mockDataSource); + this.dataSource = mock(XADataSource.class); + this.dataSourceBean = new NarayanaDataSourceBean(this.dataSource); } @Test @@ -62,32 +64,36 @@ public class NarayanaDataSourceBeanTests { @Test public void shouldUnwrapDataSource() throws SQLException { - assertThat(this.dataSourceBean.unwrap(DataSource.class)).isInstanceOf(DataSource.class); - assertThat(this.dataSourceBean.unwrap(DataSource.class)).isSameAs(this.dataSourceBean); + assertThat(this.dataSourceBean.unwrap(DataSource.class)) + .isInstanceOf(DataSource.class); + assertThat(this.dataSourceBean.unwrap(DataSource.class)) + .isSameAs(this.dataSourceBean); } @Test public void shouldUnwrapXaDataSource() throws SQLException { - assertThat(this.dataSourceBean.unwrap(XADataSource.class)).isInstanceOf(XADataSource.class); - assertThat(this.dataSourceBean.unwrap(XADataSource.class)).isSameAs(this.mockDataSource); + assertThat(this.dataSourceBean.unwrap(XADataSource.class)) + .isInstanceOf(XADataSource.class); + assertThat(this.dataSourceBean.unwrap(XADataSource.class)) + .isSameAs(this.dataSource); } @Test public void shouldGetConnectionAndCommit() throws SQLException { Connection mockConnection = mock(Connection.class); XAConnection mockXaConnection = mock(XAConnection.class); - when(mockXaConnection.getConnection()).thenReturn(mockConnection); - when(this.mockDataSource.getXAConnection()).thenReturn(mockXaConnection); + given(mockXaConnection.getConnection()).willReturn(mockConnection); + given(this.dataSource.getXAConnection()).willReturn(mockXaConnection); Properties properties = new Properties(); - properties.put(TransactionalDriver.XADataSource, this.mockDataSource); + properties.put(TransactionalDriver.XADataSource, this.dataSource); Connection connection = this.dataSourceBean.getConnection(); assertThat(connection).isInstanceOf(ConnectionImple.class); connection.commit(); - verify(this.mockDataSource, times(1)).getXAConnection(); + verify(this.dataSource, times(1)).getXAConnection(); verify(mockXaConnection, times(1)).getConnection(); verify(mockConnection, times(1)).commit(); } @@ -98,11 +104,12 @@ public class NarayanaDataSourceBeanTests { String password = "testPassword"; Connection mockConnection = mock(Connection.class); XAConnection mockXaConnection = mock(XAConnection.class); - when(mockXaConnection.getConnection()).thenReturn(mockConnection); - when(this.mockDataSource.getXAConnection(username, password)).thenReturn(mockXaConnection); + given(mockXaConnection.getConnection()).willReturn(mockConnection); + given(this.dataSource.getXAConnection(username, password)) + .willReturn(mockXaConnection); Properties properties = new Properties(); - properties.put(TransactionalDriver.XADataSource, this.mockDataSource); + properties.put(TransactionalDriver.XADataSource, this.dataSource); properties.put(TransactionalDriver.userName, username); properties.put(TransactionalDriver.password, password); @@ -111,7 +118,7 @@ public class NarayanaDataSourceBeanTests { connection.commit(); - verify(this.mockDataSource, times(1)).getXAConnection(username, password); + verify(this.dataSource, times(1)).getXAConnection(username, password); verify(mockXaConnection, times(1)).getConnection(); verify(mockConnection, times(1)).commit(); } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java index 5e66bba76d4..f90425ede27 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaRecoveryManagerBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -17,7 +17,6 @@ package org.springframework.boot.jta.narayana; import com.arjuna.ats.jbossatx.jta.RecoveryManagerService; - import org.junit.Before; import org.junit.Test; @@ -26,34 +25,34 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaRecoveryManagerBean}. + * + * @author Gytis Trikleris */ public class NarayanaRecoveryManagerBeanTests { - private RecoveryManagerService recoveryManagerService; + private RecoveryManagerService service; - private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean; + private NarayanaRecoveryManagerBean recoveryManager; @Before public void before() { - this.recoveryManagerService = mock(RecoveryManagerService.class); - this.narayanaRecoveryManagerBean = new NarayanaRecoveryManagerBean(this.recoveryManagerService); + this.service = mock(RecoveryManagerService.class); + this.recoveryManager = new NarayanaRecoveryManagerBean(this.service); } @Test public void shouldCreateAndStartRecoveryManagerService() throws Exception { - this.narayanaRecoveryManagerBean.afterPropertiesSet(); - - verify(this.recoveryManagerService, times(1)).create(); - verify(this.recoveryManagerService, times(1)).start(); + this.recoveryManager.afterPropertiesSet(); + verify(this.service, times(1)).create(); + verify(this.service, times(1)).start(); } @Test public void shouldStopAndDestroyRecoveryManagerService() throws Exception { - this.narayanaRecoveryManagerBean.destroy(); - - verify(this.recoveryManagerService, times(1)).stop(); - verify(this.recoveryManagerService, times(1)).destroy(); + this.recoveryManager.destroy(); + verify(this.service, times(1)).stop(); + verify(this.service, times(1)).destroy(); } } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java index 6105d5be2a9..e1a8143327d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXAConnectionFactoryWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -25,14 +25,16 @@ import org.jboss.narayana.jta.jms.JmsXAResourceRecoveryHelper; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.times; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.when; +import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaXAConnectionFactoryWrapper}. + * + * @author Gytis Trikleris */ public class NarayanaXAConnectionFactoryWrapperTests { @@ -40,34 +42,36 @@ public class NarayanaXAConnectionFactoryWrapperTests { private TransactionManager transactionManager = mock(TransactionManager.class); - private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean = mock(NarayanaRecoveryManagerBean.class); + private NarayanaRecoveryManagerBean recoveryManager = mock( + NarayanaRecoveryManagerBean.class); - private NarayanaProperties narayanaProperties = mock(NarayanaProperties.class); + private NarayanaProperties properties = mock(NarayanaProperties.class); - private NarayanaXAConnectionFactoryWrapper wrapper = new NarayanaXAConnectionFactoryWrapper(this.transactionManager, - this.narayanaRecoveryManagerBean, this.narayanaProperties); + private NarayanaXAConnectionFactoryWrapper wrapper = new NarayanaXAConnectionFactoryWrapper( + this.transactionManager, this.recoveryManager, this.properties); @Test public void wrap() { - ConnectionFactory wrapped = this.wrapper.wrapConnectionFactory(this.connectionFactory); + ConnectionFactory wrapped = this.wrapper + .wrapConnectionFactory(this.connectionFactory); assertThat(wrapped).isInstanceOf(ConnectionFactoryProxy.class); - verify(this.narayanaRecoveryManagerBean, times(1)) + verify(this.recoveryManager, times(1)) .registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class)); - verify(this.narayanaProperties, times(1)).getRecoveryJmsUser(); - verify(this.narayanaProperties, times(1)).getRecoveryJmsPass(); + verify(this.properties, times(1)).getRecoveryJmsUser(); + verify(this.properties, times(1)).getRecoveryJmsPass(); } @Test public void wrapWithCredentials() { - when(this.narayanaProperties.getRecoveryJmsUser()).thenReturn("userName"); - when(this.narayanaProperties.getRecoveryJmsPass()).thenReturn("password"); - ConnectionFactory wrapped = this.wrapper.wrapConnectionFactory(this.connectionFactory); - + given(this.properties.getRecoveryJmsUser()).willReturn("userName"); + given(this.properties.getRecoveryJmsPass()).willReturn("password"); + ConnectionFactory wrapped = this.wrapper + .wrapConnectionFactory(this.connectionFactory); assertThat(wrapped).isInstanceOf(ConnectionFactoryProxy.class); - verify(this.narayanaRecoveryManagerBean, times(1)) + verify(this.recoveryManager, times(1)) .registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class)); - verify(this.narayanaProperties, times(2)).getRecoveryJmsUser(); - verify(this.narayanaProperties, times(1)).getRecoveryJmsPass(); + verify(this.properties, times(2)).getRecoveryJmsUser(); + verify(this.properties, times(1)).getRecoveryJmsPass(); } } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java index 2b031898fde..aa1ac6f4a68 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/narayana/NarayanaXADataSourceWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2016 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. @@ -22,48 +22,49 @@ import javax.sql.XADataSource; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.times; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.when; +import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** - * @author Gytis Trikleris + * Tests for {@link NarayanaXADataSourceWrapper}. + * + * @author Gytis Trikleris */ public class NarayanaXADataSourceWrapperTests { private XADataSource dataSource = mock(XADataSource.class); - private NarayanaRecoveryManagerBean narayanaRecoveryManagerBean = mock(NarayanaRecoveryManagerBean.class); + private NarayanaRecoveryManagerBean recoveryManager = mock( + NarayanaRecoveryManagerBean.class); - private NarayanaProperties narayanaProperties = mock(NarayanaProperties.class); + private NarayanaProperties properties = mock(NarayanaProperties.class); - private NarayanaXADataSourceWrapper wrapper = new NarayanaXADataSourceWrapper(this.narayanaRecoveryManagerBean, - this.narayanaProperties); + private NarayanaXADataSourceWrapper wrapper = new NarayanaXADataSourceWrapper( + this.recoveryManager, this.properties); @Test public void wrap() { DataSource wrapped = this.wrapper.wrapDataSource(this.dataSource); - assertThat(wrapped).isInstanceOf(NarayanaDataSourceBean.class); - verify(this.narayanaRecoveryManagerBean, times(1)) - .registerXAResourceRecoveryHelper(any(DataSourceXAResourceRecoveryHelper.class)); - verify(this.narayanaProperties, times(1)).getRecoveryDbUser(); - verify(this.narayanaProperties, times(1)).getRecoveryDbPass(); + verify(this.recoveryManager, times(1)).registerXAResourceRecoveryHelper( + any(DataSourceXAResourceRecoveryHelper.class)); + verify(this.properties, times(1)).getRecoveryDbUser(); + verify(this.properties, times(1)).getRecoveryDbPass(); } @Test public void wrapWithCredentials() { - when(this.narayanaProperties.getRecoveryDbUser()).thenReturn("userName"); - when(this.narayanaProperties.getRecoveryDbPass()).thenReturn("password"); + given(this.properties.getRecoveryDbUser()).willReturn("userName"); + given(this.properties.getRecoveryDbPass()).willReturn("password"); DataSource wrapped = this.wrapper.wrapDataSource(this.dataSource); - assertThat(wrapped).isInstanceOf(NarayanaDataSourceBean.class); - verify(this.narayanaRecoveryManagerBean, times(1)) - .registerXAResourceRecoveryHelper(any(DataSourceXAResourceRecoveryHelper.class)); - verify(this.narayanaProperties, times(2)).getRecoveryDbUser(); - verify(this.narayanaProperties, times(1)).getRecoveryDbPass(); + verify(this.recoveryManager, times(1)).registerXAResourceRecoveryHelper( + any(DataSourceXAResourceRecoveryHelper.class)); + verify(this.properties, times(2)).getRecoveryDbUser(); + verify(this.properties, times(1)).getRecoveryDbPass(); } }