Merge pull request #5552 from gytis/spring-boot-starter-jta-narayana

* spring-boot-starter-jta-narayana:
  Polish contribution
  Add Narayana JTA support
pull/5622/head
Phillip Webb 9 years ago
commit 69a80596ba

@ -577,6 +577,16 @@
<artifactId>jooq</artifactId> <artifactId>jooq</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jta</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
<optional>true</optional>
</dependency>
<!-- Annotation processing --> <!-- Annotation processing -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

@ -40,7 +40,7 @@ import org.springframework.context.annotation.Import;
ActiveMQAutoConfiguration.class, HornetQAutoConfiguration.class, ActiveMQAutoConfiguration.class, HornetQAutoConfiguration.class,
HibernateJpaAutoConfiguration.class }) HibernateJpaAutoConfiguration.class })
@Import({ JndiJtaConfiguration.class, BitronixJtaConfiguration.class, @Import({ JndiJtaConfiguration.class, BitronixJtaConfiguration.class,
AtomikosJtaConfiguration.class }) AtomikosJtaConfiguration.class, NarayanaJtaConfiguration.class })
@EnableConfigurationProperties(JtaProperties.class) @EnableConfigurationProperties(JtaProperties.class)
public class JtaAutoConfiguration { public class JtaAutoConfiguration {

@ -0,0 +1,142 @@
/*
* 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 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.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 <a href="http://narayana.io/">Narayana</a>.
*
* @author Gytis Trikleris
* @since 1.4.0
*/
@Configuration
@ConditionalOnClass({ JtaTransactionManager.class,
com.arjuna.ats.jta.UserTransaction.class })
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public class NarayanaJtaConfiguration {
private final JtaProperties jtaProperties;
public NarayanaJtaConfiguration(JtaProperties jtaProperties) {
this.jtaProperties = jtaProperties;
}
@Bean
@ConditionalOnMissingBean
public NarayanaProperties narayanaProperties() {
return new NarayanaProperties();
}
@Bean
@ConditionalOnMissingBean
public NarayanaConfigurationBean narayanaConfiguration(
NarayanaProperties properties) {
if (this.jtaProperties.getLogDir() != null) {
properties.setLogDir(this.jtaProperties.getLogDir());
}
if (this.jtaProperties.getTransactionManagerId() != null) {
properties.setTransactionManagerId(
this.jtaProperties.getTransactionManagerId());
}
return new NarayanaConfigurationBean(properties);
}
@Bean
@DependsOn("narayanaConfiguration")
@ConditionalOnMissingBean
public UserTransaction narayanaUserTransaction() {
return com.arjuna.ats.jta.UserTransaction.userTransaction();
}
@Bean
@DependsOn("narayanaConfiguration")
@ConditionalOnMissingBean
public TransactionManager narayanaTransactionManager() {
return com.arjuna.ats.jta.TransactionManager.transactionManager();
}
@Bean
@DependsOn("narayanaConfiguration")
public RecoveryManagerService narayanaRecoveryManagerService() {
return new RecoveryManagerService();
}
@Bean
public NarayanaRecoveryManagerBean narayanaRecoveryManager(
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);
}
}
}

@ -100,6 +100,7 @@
<jaxen.version>1.1.6</jaxen.version> <jaxen.version>1.1.6</jaxen.version>
<jaybird.version>2.2.10</jaybird.version> <jaybird.version>2.2.10</jaybird.version>
<jboss-logging.version>3.3.0.Final</jboss-logging.version> <jboss-logging.version>3.3.0.Final</jboss-logging.version>
<jboss-transaction-spi.version>7.3.0.Final</jboss-transaction-spi.version>
<jdom2.version>2.0.6</jdom2.version> <jdom2.version>2.0.6</jdom2.version>
<jedis.version>2.8.1</jedis.version> <jedis.version>2.8.1</jedis.version>
<jersey.version>2.22.2</jersey.version> <jersey.version>2.22.2</jersey.version>
@ -124,6 +125,7 @@
<mockito.version>1.10.19</mockito.version> <mockito.version>1.10.19</mockito.version>
<mongodb.version>2.14.2</mongodb.version> <mongodb.version>2.14.2</mongodb.version>
<mysql.version>5.1.38</mysql.version> <mysql.version>5.1.38</mysql.version>
<narayana.version>5.3.2.Final</narayana.version>
<nekohtml.version>1.9.22</nekohtml.version> <nekohtml.version>1.9.22</nekohtml.version>
<neo4j-ogm.version>2.0.0</neo4j-ogm.version> <neo4j-ogm.version>2.0.0</neo4j-ogm.version>
<postgresql.version>9.4.1208.jre7</postgresql.version> <postgresql.version>9.4.1208.jre7</postgresql.version>
@ -429,6 +431,11 @@
<artifactId>spring-boot-starter-mustache</artifactId> <artifactId>spring-boot-starter-mustache</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version> <version>1.4.0.BUILD-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-narayana</artifactId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-remote-shell</artifactId> <artifactId>spring-boot-starter-remote-shell</artifactId>
@ -1733,11 +1740,36 @@
<artifactId>javassist</artifactId> <artifactId>javassist</artifactId>
<version>${javassist.version}</version> <version>${javassist.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-transaction-spi</artifactId>
<version>${jboss-transaction-spi.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<version>${jboss-logging.version}</version> <version>${jboss-logging.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jdbc</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jms</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jta</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.jdom</groupId> <groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId> <artifactId>jdom2</artifactId>

@ -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.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. 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.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.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]) # 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.features=SYNC_DELAY # Comma-separated list of features to enable.
spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use. spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use.

@ -63,6 +63,7 @@
<module>spring-boot-sample-jpa</module> <module>spring-boot-sample-jpa</module>
<module>spring-boot-sample-jta-atomikos</module> <module>spring-boot-sample-jta-atomikos</module>
<module>spring-boot-sample-jta-bitronix</module> <module>spring-boot-sample-jta-bitronix</module>
<module>spring-boot-sample-jta-narayana</module>
<module>spring-boot-sample-jta-jndi</module> <module>spring-boot-sample-jta-jndi</module>
<module>spring-boot-sample-liquibase</module> <module>spring-boot-sample-liquibase</module>
<module>spring-boot-sample-logback</module> <module>spring-boot-sample-logback</module>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-samples</artifactId>
<groupId>org.springframework.boot</groupId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-jta-narayana</artifactId>
<name>Spring Boot Narayana JTA Sample</name>
<description>Spring Boot Narayana JTA Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-narayana</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hornetq</artifactId>
</dependency>
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-server</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,43 @@
/*
* 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;
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;
}
}

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

@ -0,0 +1,47 @@
/*
* 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;
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");
}
}
}

@ -0,0 +1,30 @@
/*
* 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;
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);
}
}

@ -0,0 +1,47 @@
/*
* 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;
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();
}
}

@ -0,0 +1,25 @@
/*
* 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;
public class SampleRuntimeException extends RuntimeException {
public SampleRuntimeException(String message) {
super(message);
}
}

@ -0,0 +1,5 @@
spring.hornetq.mode=embedded
spring.hornetq.embedded.enabled=true
spring.hornetq.embedded.queues=accounts
logging.level.com.arjuna=INFO

@ -0,0 +1,65 @@
/*
* 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;
import org.assertj.core.api.Condition;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.test.rule.OutputCapture;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Basic integration tests for demo application.
*
* @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).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 Condition<String> substring(final int times, final String substring) {
return new Condition<String>(
"containing '" + substring + "' " + times + " times") {
@Override
public boolean matches(String value) {
int i = 0;
while (value.contains(substring)) {
int beginIndex = value.indexOf(substring) + substring.length();
value = value.substring(beginIndex);
i++;
}
return i == times;
}
};
}
}

@ -48,6 +48,7 @@
<module>spring-boot-starter-jooq</module> <module>spring-boot-starter-jooq</module>
<module>spring-boot-starter-jta-atomikos</module> <module>spring-boot-starter-jta-atomikos</module>
<module>spring-boot-starter-jta-bitronix</module> <module>spring-boot-starter-jta-bitronix</module>
<module>spring-boot-starter-jta-narayana</module>
<module>spring-boot-starter-logging</module> <module>spring-boot-starter-logging</module>
<module>spring-boot-starter-log4j2</module> <module>spring-boot-starter-log4j2</module>
<module>spring-boot-starter-mail</module> <module>spring-boot-starter-mail</module>

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starters</artifactId>
<groupId>org.springframework.boot</groupId>
<version>1.4.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-jta-narayana</artifactId>
<name>Spring Boot Narayana JTA Starter</name>
<description>Spring Boot Narayana JTA Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-transaction-spi</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jms</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jta</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
</dependency>
</dependencies>
</project>

@ -229,6 +229,31 @@
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jta</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jta</groupId>
<artifactId>jms</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-transaction-spi</artifactId>
<optional>true</optional>
</dependency>
<!-- Annotation processing --> <!-- Annotation processing -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

@ -0,0 +1,190 @@
/*
* 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 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 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.
*
* @author Gytis Trikleris
* @since 1.4.0
*/
public class DataSourceXAResourceRecoveryHelper
implements XAResourceRecoveryHelper, XAResource {
private static final XAResource[] NO_XA_RESOURCES = {};
private static final Log logger = LogFactory
.getLog(DataSourceXAResourceRecoveryHelper.class);
private final XADataSource xaDataSource;
private final String user;
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);
}
/**
* 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.password = password;
}
@Override
public boolean initialise(String properties) {
return true;
}
@Override
public XAResource[] getXAResources() {
if (connect()) {
return new XAResource[] { this };
}
return NO_XA_RESOURCES;
}
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 flag) throws XAException {
try {
return getDelegate(true).recover(flag);
}
finally {
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 flags) throws XAException {
getDelegate(true).start(xid, flags);
}
@Override
public void end(Xid xid, int flags) throws XAException {
getDelegate(true).end(xid, flags);
}
@Override
public int prepare(Xid xid) throws XAException {
return getDelegate(true).prepare(xid);
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
getDelegate(true).commit(xid, onePhase);
}
@Override
public void rollback(Xid xid) throws XAException {
getDelegate(true).rollback(xid);
}
@Override
public boolean isSameRM(XAResource xaResource) throws XAException {
return getDelegate(true).isSameRM(xaResource);
}
@Override
public void forget(Xid xid) throws XAException {
getDelegate(true).forget(xid);
}
@Override
public int getTransactionTimeout() throws XAException {
return getDelegate(true).getTransactionTimeout();
}
@Override
public boolean setTransactionTimeout(int seconds) throws XAException {
return getDelegate(true).setTransactionTimeout(seconds);
}
private XAResource getDelegate(boolean required) {
Assert.state(this.delegate != null || !required,
"Connection has not been opened");
return this.delegate;
}
}

@ -0,0 +1,87 @@
/*
* 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 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
* @since 1.4.0
*/
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;
}
}

@ -0,0 +1,123 @@
/*
* 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 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
* @since 1.4.0
*/
public class NarayanaConfigurationBean implements InitializingBean {
private static final String JBOSSTS_PROPERTIES_FILE_NAME = "jbossts-properties.xml";
private final NarayanaProperties properties;
public NarayanaConfigurationBean(NarayanaProperties narayanaProperties) {
this.properties = narayanaProperties;
}
@Override
public void afterPropertiesSet() throws Exception {
if (isPropertiesFileAvailable()) {
return;
}
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;
}
private void setNodeIdentifier(String nodeIdentifier)
throws CoreEnvironmentBeanException {
getPopulator(CoreEnvironmentBean.class).setNodeIdentifier(nodeIdentifier);
}
private void setObjectStoreDir(String objectStoreDir) {
getPopulator(ObjectStoreEnvironmentBean.class).setObjectStoreDir(objectStoreDir);
getPopulator(ObjectStoreEnvironmentBean.class, "communicationStore")
.setObjectStoreDir(objectStoreDir);
getPopulator(ObjectStoreEnvironmentBean.class, "stateStore")
.setObjectStoreDir(objectStoreDir);
}
private void setCommitOnePhase(boolean isCommitOnePhase) {
getPopulator(CoordinatorEnvironmentBean.class)
.setCommitOnePhase(isCommitOnePhase);
}
private void setDefaultTimeout(int defaultTimeout) {
getPopulator(CoordinatorEnvironmentBean.class).setDefaultTimeout(defaultTimeout);
}
private void setPeriodicRecoveryPeriod(int periodicRecoveryPeriod) {
getPopulator(RecoveryEnvironmentBean.class)
.setPeriodicRecoveryPeriod(periodicRecoveryPeriod);
}
private void setRecoveryBackoffPeriod(int recoveryBackoffPeriod) {
getPopulator(RecoveryEnvironmentBean.class)
.setRecoveryBackoffPeriod(recoveryBackoffPeriod);
}
private void setXaResourceOrphanFilters(List<String> xaResourceOrphanFilters) {
getPopulator(JTAEnvironmentBean.class)
.setXaResourceOrphanFilterClassNames(xaResourceOrphanFilters);
}
private void setRecoveryModules(List<String> recoveryModules) {
getPopulator(RecoveryEnvironmentBean.class)
.setRecoveryModuleClassNames(recoveryModules);
}
private void setExpiryScanners(List<String> expiryScanners) {
getPopulator(RecoveryEnvironmentBean.class)
.setExpiryScannerClassNames(expiryScanners);
}
private <T> T getPopulator(Class<T> beanClass) {
return BeanPopulator.getDefaultInstance(beanClass);
}
private <T> T getPopulator(Class<T> beanClass, String name) {
return BeanPopulator.getNamedInstance(beanClass, name);
}
}

@ -0,0 +1,114 @@
/*
* 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 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;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link DataSource} implementation wrapping {@link XADataSource} and using
* {@link ConnectionManager} to acquire connections.
*
* @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;
}
@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> T unwrap(Class<T> iface) throws SQLException {
if (isWrapperFor(iface)) {
return (T) this;
}
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 iface.isAssignableFrom(getClass());
}
}

@ -0,0 +1,230 @@
/*
* 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 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
* @since 1.4.0
*/
@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: <code>true</code>.
*/
private boolean onePhaseCommit = true;
/**
* Transaction timeout in seconds. Default: <code>60</code>.
*/
private int defaultTimeout = 60;
/**
* Interval in which periodic recovery scans are performed in seconds. Default:
* <code>120</code>
*/
private int periodicRecoveryPeriod = 120;
/**
* Back off period between first and second phases of the recovery scan in seconds.
* Default: <code>10</code>
*/
private int recoveryBackoffPeriod = 10;
/**
* Database username to be used by recovery manager. Default: <code>null</code>
*/
private String recoveryDbUser = null;
/**
* Database password to be used by recovery manager. Default: <code>null</code>
*/
private String recoveryDbPass = null;
/**
* JMS username to be used by recovery manager. Default: <code>null</code>
*/
private String recoveryJmsUser = null;
/**
* JMS password to be used by recovery manager. Default: <code>null</code>
*/
private String recoveryJmsPass = null;
/**
* List of orphan filters. Default:
* <ul>
* <li>com.arjuna.ats.internal.jta.recovery.arjunacore.
* JTATransactionLogXAResourceOrphanFilter</li>
* <li>
* com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter
* </li>
* </ul>
*/
private List<String> xaResourceOrphanFilters = Arrays.asList(
"com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter",
"com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter");
/**
* List of recovery modules. Default:
* <ul>
* <li>com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule</li>
* <li>com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule</li>
* </ul>
*/
private List<String> recoveryModules = Arrays.asList(
"com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule",
"com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule");
/**
* List of expiry scanners. Default:
* <ul>
* <li>com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner
* </li>
* </ul>
*/
private List<String> 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<String> getXaResourceOrphanFilters() {
return this.xaResourceOrphanFilters;
}
public void setXaResourceOrphanFilters(List<String> xaResourceOrphanFilters) {
this.xaResourceOrphanFilters = xaResourceOrphanFilters;
}
public List<String> getRecoveryModules() {
return this.recoveryModules;
}
public void setRecoveryModules(List<String> recoveryModules) {
this.recoveryModules = recoveryModules;
}
public List<String> getExpiryScanners() {
return this.expiryScanners;
}
public void setExpiryScanners(List<String> 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;
}
}

@ -0,0 +1,72 @@
/*
* 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 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;
import org.springframework.util.Assert;
/**
* Bean to set up Narayana recovery manager.
*
* @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;
}
@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");
}
}

@ -0,0 +1,82 @@
/*
* 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 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;
import org.springframework.util.Assert;
/**
* {@link XAConnectionFactoryWrapper} that uses {@link ConnectionFactoryProxy} to wrap an
* {@link XAConnectionFactory}.
*
* @author Gytis Trikleris
* @since 1.4.0
*/
public class NarayanaXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper {
private final TransactionManager transactionManager;
private final NarayanaRecoveryManagerBean recoveryManager;
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 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.recoveryManager = recoveryManager;
this.properties = properties;
}
@Override
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.properties.getRecoveryJmsUser() == null
&& this.properties.getRecoveryJmsPass() == null) {
return new JmsXAResourceRecoveryHelper(xaConnectionFactory);
}
return new JmsXAResourceRecoveryHelper(xaConnectionFactory,
this.properties.getRecoveryJmsUser(),
this.properties.getRecoveryJmsPass());
}
}

@ -0,0 +1,69 @@
/*
* 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 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;
import org.springframework.util.Assert;
/**
* {@link XADataSourceWrapper} that uses {@link NarayanaDataSourceBean} to wrap an
* {@link XADataSource}.
*
* @author Gytis Trikleris
* @since 1.4.0
*/
public class NarayanaXADataSourceWrapper implements XADataSourceWrapper {
private final NarayanaRecoveryManagerBean recoveryManager;
private final NarayanaProperties properties;
/**
* 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) {
XAResourceRecoveryHelper recoveryHelper = getRecoveryHelper(dataSource);
this.recoveryManager.registerXAResourceRecoveryHelper(recoveryHelper);
return new NarayanaDataSourceBean(dataSource);
}
private XAResourceRecoveryHelper getRecoveryHelper(XADataSource dataSource) {
if (this.properties.getRecoveryDbUser() == null
&& this.properties.getRecoveryDbPass() == null) {
return new DataSourceXAResourceRecoveryHelper(dataSource);
}
return new DataSourceXAResourceRecoveryHelper(dataSource,
this.properties.getRecoveryDbUser(), this.properties.getRecoveryDbPass());
}
}

@ -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;

@ -0,0 +1,174 @@
/*
* 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 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.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;
/**
* Tests for {@link DataSourceXAResourceRecoveryHelper}.
*
* @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);
given(this.xaDataSource.getXAConnection()).willReturn(this.xaConnection);
given(this.xaConnection.getXAResource()).willReturn(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 {
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);
verify(this.xaDataSource, times(1)).getXAConnection("username", "password");
verify(this.xaConnection, times(1)).getXAResource();
}
@Test
public void shouldFailToCreateConnectionAndNotGetXAResource() throws SQLException {
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();
}
@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);
}
}

@ -0,0 +1,90 @@
/*
* 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 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.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link NarayanaBeanFactoryPostProcessor}.
*
* @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();
}
}
}

@ -0,0 +1,136 @@
/*
* 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 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;
/**
* 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.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<String> 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<String> 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<String> 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"));
}
}

@ -0,0 +1,126 @@
/*
* 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 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.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link NarayanaDataSourceBean}.
*
* @author Gytis Trikleris
*/
public class NarayanaDataSourceBeanTests {
private XADataSource dataSource;
private NarayanaDataSourceBean dataSourceBean;
@Before
public void before() {
this.dataSource = mock(XADataSource.class);
this.dataSourceBean = new NarayanaDataSourceBean(this.dataSource);
}
@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.dataSource);
}
@Test
public void shouldGetConnectionAndCommit() throws SQLException {
Connection mockConnection = mock(Connection.class);
XAConnection mockXaConnection = mock(XAConnection.class);
given(mockXaConnection.getConnection()).willReturn(mockConnection);
given(this.dataSource.getXAConnection()).willReturn(mockXaConnection);
Properties properties = new Properties();
properties.put(TransactionalDriver.XADataSource, this.dataSource);
Connection connection = this.dataSourceBean.getConnection();
assertThat(connection).isInstanceOf(ConnectionImple.class);
connection.commit();
verify(this.dataSource, 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);
given(mockXaConnection.getConnection()).willReturn(mockConnection);
given(this.dataSource.getXAConnection(username, password))
.willReturn(mockXaConnection);
Properties properties = new Properties();
properties.put(TransactionalDriver.XADataSource, this.dataSource);
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.dataSource, times(1)).getXAConnection(username, password);
verify(mockXaConnection, times(1)).getConnection();
verify(mockConnection, times(1)).commit();
}
}

@ -0,0 +1,58 @@
/*
* 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 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;
/**
* Tests for {@link NarayanaRecoveryManagerBean}.
*
* @author Gytis Trikleris
*/
public class NarayanaRecoveryManagerBeanTests {
private RecoveryManagerService service;
private NarayanaRecoveryManagerBean recoveryManager;
@Before
public void before() {
this.service = mock(RecoveryManagerService.class);
this.recoveryManager = new NarayanaRecoveryManagerBean(this.service);
}
@Test
public void shouldCreateAndStartRecoveryManagerService() throws Exception {
this.recoveryManager.afterPropertiesSet();
verify(this.service, times(1)).create();
verify(this.service, times(1)).start();
}
@Test
public void shouldStopAndDestroyRecoveryManagerService() throws Exception {
this.recoveryManager.destroy();
verify(this.service, times(1)).stop();
verify(this.service, times(1)).destroy();
}
}

@ -0,0 +1,77 @@
/*
* 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 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.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;
/**
* Tests for {@link NarayanaXAConnectionFactoryWrapper}.
*
* @author Gytis Trikleris
*/
public class NarayanaXAConnectionFactoryWrapperTests {
private XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
private TransactionManager transactionManager = mock(TransactionManager.class);
private NarayanaRecoveryManagerBean recoveryManager = mock(
NarayanaRecoveryManagerBean.class);
private NarayanaProperties properties = mock(NarayanaProperties.class);
private NarayanaXAConnectionFactoryWrapper wrapper = new NarayanaXAConnectionFactoryWrapper(
this.transactionManager, this.recoveryManager, this.properties);
@Test
public void wrap() {
ConnectionFactory wrapped = this.wrapper
.wrapConnectionFactory(this.connectionFactory);
assertThat(wrapped).isInstanceOf(ConnectionFactoryProxy.class);
verify(this.recoveryManager, times(1))
.registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class));
verify(this.properties, times(1)).getRecoveryJmsUser();
verify(this.properties, times(1)).getRecoveryJmsPass();
}
@Test
public void wrapWithCredentials() {
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.recoveryManager, times(1))
.registerXAResourceRecoveryHelper(any(JmsXAResourceRecoveryHelper.class));
verify(this.properties, times(2)).getRecoveryJmsUser();
verify(this.properties, times(1)).getRecoveryJmsPass();
}
}

@ -0,0 +1,70 @@
/*
* 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 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.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;
/**
* Tests for {@link NarayanaXADataSourceWrapper}.
*
* @author Gytis Trikleris
*/
public class NarayanaXADataSourceWrapperTests {
private XADataSource dataSource = mock(XADataSource.class);
private NarayanaRecoveryManagerBean recoveryManager = mock(
NarayanaRecoveryManagerBean.class);
private NarayanaProperties properties = mock(NarayanaProperties.class);
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.recoveryManager, times(1)).registerXAResourceRecoveryHelper(
any(DataSourceXAResourceRecoveryHelper.class));
verify(this.properties, times(1)).getRecoveryDbUser();
verify(this.properties, times(1)).getRecoveryDbPass();
}
@Test
public void wrapWithCredentials() {
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.recoveryManager, times(1)).registerXAResourceRecoveryHelper(
any(DataSourceXAResourceRecoveryHelper.class));
verify(this.properties, times(2)).getRecoveryDbUser();
verify(this.properties, times(1)).getRecoveryDbPass();
}
}
Loading…
Cancel
Save