diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java index ef94176806..17bd27441a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/transaction/jta/JtaAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -40,7 +40,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -166,22 +165,7 @@ public class JtaAutoConfigurationTests { AtomikosJtaConfiguration.class); this.context.refresh(); - File epochFile = new File("target/transaction-logs/" - + InetAddress.getLocalHost().getHostAddress() + ".tm0.epoch"); - assertThat(epochFile.isFile()).isTrue(); - } - - @Test - public void customAtomikosTransactionManagerName() throws BeansException, Exception { - this.context = new AnnotationConfigApplicationContext(); - TestPropertyValues.of( - "spring.jta.transactionManagerId:custom", - "spring.jta.logDir:target/transaction-logs").applyTo(this.context); - this.context.register(JtaPropertiesConfiguration.class, - AtomikosJtaConfiguration.class); - this.context.refresh(); - - File epochFile = new File("target/transaction-logs/custom0.epoch"); + File epochFile = new File("target/transaction-logs/tmlog0.log"); assertThat(epochFile.isFile()).isTrue(); } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 340bdb5749..484ab93dbb 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -52,7 +52,7 @@ 1.5.5 1.8.10 3.8.0 - 3.9.3 + 4.0.4 2.1.4 1.6.14 2.5.1 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 d81cb8c53f..e3ba59c908 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -738,6 +738,7 @@ content into your application; rather pick only the properties that you need. spring.jta.atomikos.datasource.reap-timeout=0 # The reap timeout, in seconds, for borrowed connections. 0 denotes no limit. spring.jta.atomikos.datasource.test-query= # SQL query or statement used to validate a connection before returning it. spring.jta.atomikos.datasource.unique-resource-name=dataSource # The unique name used to identify the resource during recovery. + spring.jta.atomikos.properties.allow-sub-transactions=true # Specify if sub-transactions are allowed. spring.jta.atomikos.properties.checkpoint-interval=500 # Interval between checkpoints. spring.jta.atomikos.properties.console-file-count=1 # Number of debug logs files that can be created. spring.jta.atomikos.properties.console-file-limit=-1 # How many bytes can be stored at most in debug logs files. @@ -751,6 +752,10 @@ content into your application; rather pick only the properties that you need. spring.jta.atomikos.properties.max-actives=50 # Maximum number of active transactions. spring.jta.atomikos.properties.max-timeout=300000 # Maximum timeout (in milliseconds) that can be allowed for transactions. spring.jta.atomikos.properties.output-dir= # Directory in which to store the debug log files. + spring.jta.atomikos.properties.recovery.delay=10000 # Delay between two recovery scans. + spring.jta.atomikos.properties.recovery.forget-orphaned-log-entries-delay=86400000 # Delay after which recovery can cleanup pending ('orphaned') log entries. + spring.jta.atomikos.properties.recovery.max-retries=5 # Number of retries attempts to commit the transaction before throwing an exception. + spring.jta.atomikos.properties.recovery.retry-interval=10000 # Delay between retry attempts. spring.jta.atomikos.properties.serial-jta-transactions=true # Specify if sub-transactions should be joined when possible. spring.jta.atomikos.properties.service= # Transaction manager implementation that should be started. spring.jta.atomikos.properties.threaded-two-phase-commit=false # Use different (and concurrent) threads for two-phase commit on the participating resources. diff --git a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java index fe008d470c..9401184a25 100644 --- a/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java +++ b/spring-boot/src/main/java/org/springframework/boot/jta/atomikos/AtomikosProperties.java @@ -71,6 +71,11 @@ public class AtomikosProperties { */ private boolean serialJtaTransactions = true; + /** + * Specify if sub-transactions are allowed. + */ + private boolean allowSubTransactions = true; + /** * Specify if a VM shutdown should trigger forced shutdown of the transaction core. */ @@ -126,6 +131,8 @@ public class AtomikosProperties { */ private boolean threadedTwoPhaseCommit; + private final Recovery recovery = new Recovery(); + /** * Specifies the transaction manager implementation that should be started. There is * no default value and this must be set. Generally, @@ -235,6 +242,14 @@ public class AtomikosProperties { return this.serialJtaTransactions; } + public void setAllowSubTransactions(boolean allowSubTransactions) { + this.allowSubTransactions = allowSubTransactions; + } + + public boolean isAllowSubTransactions() { + return this.allowSubTransactions; + } + /** * Specifies whether VM shutdown should trigger forced shutdown of the transaction * core. Defaults to false. @@ -370,6 +385,10 @@ public class AtomikosProperties { return this.threadedTwoPhaseCommit; } + public Recovery getRecovery() { + return this.recovery; + } + /** * Returns the properties as a {@link Properties} object that can be used with * Atomikos. @@ -384,6 +403,7 @@ public class AtomikosProperties { set(properties, "enable_logging", isEnableLogging()); set(properties, "tm_unique_name", getTransactionManagerUniqueName()); set(properties, "serial_jta_transactions", isSerialJtaTransactions()); + set(properties, "allow_subtransactions", isAllowSubTransactions()); set(properties, "force_shutdown_on_vm_exit", isForceShutdownOnVmExit()); set(properties, "log_base_name", getLogBaseName()); set(properties, "log_base_dir", getLogBaseDir()); @@ -394,6 +414,12 @@ public class AtomikosProperties { set(properties, "console_file_count", getConsoleFileCount()); set(properties, "console_file_limit", getConsoleFileLimit()); set(properties, "threaded_2pc", isThreadedTwoPhaseCommit()); + Recovery recovery = getRecovery(); + set(properties, "forget_orphaned_log_entries_delay", + recovery.getForgetOrphanedLogEntriesDelay()); + set(properties, "recovery_delay", recovery.getDelay()); + set(properties, "oltp_max_retries", recovery.getMaxRetries()); + set(properties, "oltp_retry_interval", recovery.getRetryInterval()); return properties; } @@ -404,4 +430,64 @@ public class AtomikosProperties { } } + /** + * Recovery specific settings. + */ + public static class Recovery { + + /** + * Delay after which recovery can cleanup pending ('orphaned') log entries. + */ + private long forgetOrphanedLogEntriesDelay = 86400000; + + /** + * Delay between two recovery scans. + */ + private long delay = 10000; + + /** + * Number of retries attempts to commit the transaction before throwing an + * exception. + */ + private int maxRetries = 5; + + /** + * Delay between retry attempts. + */ + private long retryInterval = 10000; + + public long getForgetOrphanedLogEntriesDelay() { + return this.forgetOrphanedLogEntriesDelay; + } + + public void setForgetOrphanedLogEntriesDelay(long forgetOrphanedLogEntriesDelay) { + this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay; + } + + public long getDelay() { + return this.delay; + } + + public void setDelay(long delay) { + this.delay = delay; + } + + public int getMaxRetries() { + return this.maxRetries; + } + + public void setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + } + + public long getRetryInterval() { + return this.retryInterval; + } + + public void setRetryInterval(long retryInterval) { + this.retryInterval = retryInterval; + } + + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java index aaac5a8a5b..afc66e8f2d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/jta/atomikos/AtomikosPropertiesTests.java @@ -16,14 +16,13 @@ package org.springframework.boot.jta.atomikos; -import java.lang.reflect.Method; import java.util.Properties; import org.assertj.core.data.MapEntry; import org.junit.Test; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PropertiesLoaderUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -47,6 +46,7 @@ public class AtomikosPropertiesTests { this.properties.setEnableLogging(true); this.properties.setTransactionManagerUniqueName("uniqueName"); this.properties.setSerialJtaTransactions(true); + this.properties.setAllowSubTransactions(false); this.properties.setForceShutdownOnVmExit(true); this.properties.setLogBaseName("logBaseName"); this.properties.setLogBaseDir("logBaseDir"); @@ -57,8 +57,12 @@ public class AtomikosPropertiesTests { this.properties.setConsoleFileCount(5); this.properties.setConsoleFileLimit(6); this.properties.setThreadedTwoPhaseCommit(true); + this.properties.getRecovery().setForgetOrphanedLogEntriesDelay(2000); + this.properties.getRecovery().setDelay(3000); + this.properties.getRecovery().setMaxRetries(10); + this.properties.getRecovery().setRetryInterval(4000); - assertThat(this.properties.asProperties().size()).isEqualTo(17); + assertThat(this.properties.asProperties().size()).isEqualTo(22); assertProperty("com.atomikos.icatch.service", "service"); assertProperty("com.atomikos.icatch.max_timeout", "1"); assertProperty("com.atomikos.icatch.default_jta_timeout", "2"); @@ -66,6 +70,7 @@ public class AtomikosPropertiesTests { assertProperty("com.atomikos.icatch.enable_logging", "true"); assertProperty("com.atomikos.icatch.tm_unique_name", "uniqueName"); assertProperty("com.atomikos.icatch.serial_jta_transactions", "true"); + assertProperty("com.atomikos.icatch.allow_subtransactions", "false"); assertProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "true"); assertProperty("com.atomikos.icatch.log_base_name", "logBaseName"); assertProperty("com.atomikos.icatch.log_base_dir", "logBaseDir"); @@ -76,6 +81,10 @@ public class AtomikosPropertiesTests { assertProperty("com.atomikos.icatch.console_file_count", "5"); assertProperty("com.atomikos.icatch.console_file_limit", "6"); assertProperty("com.atomikos.icatch.threaded_2pc", "true"); + assertProperty("com.atomikos.icatch.forget_orphaned_log_entries_delay", "2000"); + assertProperty("com.atomikos.icatch.recovery_delay", "3000"); + assertProperty("com.atomikos.icatch.oltp_max_retries", "10"); + assertProperty("com.atomikos.icatch.oltp_retry_interval", "4000"); } @Test @@ -87,16 +96,22 @@ public class AtomikosPropertiesTests { "com.atomikos.icatch.default_jta_timeout", "com.atomikos.icatch.max_actives", "com.atomikos.icatch.enable_logging", "com.atomikos.icatch.serial_jta_transactions", + "com.atomikos.icatch.allow_subtransactions", "com.atomikos.icatch.force_shutdown_on_vm_exit", "com.atomikos.icatch.log_base_name", "com.atomikos.icatch.checkpoint_interval", - "com.atomikos.icatch.threaded_2pc")); + "com.atomikos.icatch.threaded_2pc", + "com.atomikos.icatch.forget_orphaned_log_entries_delay", + "com.atomikos.icatch.oltp_max_retries", + "com.atomikos.icatch.oltp_retry_interval")); assertThat(properties).contains( + entry("com.atomikos.icatch.recovery_delay", defaultSettings.get( + "com.atomikos.icatch.default_jta_timeout")), entry("com.atomikos.icatch.console_log_level", "WARN"), entry("com.atomikos.icatch.console_file_name", "tm.out"), entry("com.atomikos.icatch.console_file_count", "1"), entry("com.atomikos.icatch.console_file_limit", "-1")); - assertThat(properties).hasSize(13); + assertThat(properties).hasSize(18); } private MapEntry[] defaultOf(Properties defaultSettings, String... keys) { @@ -110,12 +125,9 @@ public class AtomikosPropertiesTests { private Properties loadDefaultSettings() { try { - Class target = ClassUtils.forName( - "com.atomikos.icatch.standalone.UserTransactionServiceImp", - getClass().getClassLoader()); - Method m = target.getMethod("getDefaultProperties"); - m.setAccessible(true); - return (Properties) ReflectionUtils.invokeMethod(m, null); + + return PropertiesLoaderUtils.loadProperties( + new ClassPathResource("transactions-defaults.properties")); } catch (Exception ex) { throw new IllegalStateException("Failed to get default from Atomikos", ex);