001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.jta.atomikos; 018 019import java.time.Duration; 020import java.util.Properties; 021 022import org.springframework.boot.context.properties.ConfigurationProperties; 023 024/** 025 * Bean friendly variant of 026 * <a href="http://www.atomikos.com/Documentation/JtaProperties">Atomikos configuration 027 * properties</a>. Allows for setter based configuration and is amiable to relaxed data 028 * binding. 029 * 030 * @author Phillip Webb 031 * @author Stephane Nicoll 032 * @since 1.2.0 033 * @see #asProperties() 034 */ 035@ConfigurationProperties(prefix = "spring.jta.atomikos.properties") 036public class AtomikosProperties { 037 038 /** 039 * Transaction manager implementation that should be started. 040 */ 041 private String service; 042 043 /** 044 * Maximum timeout that can be allowed for transactions. 045 */ 046 private Duration maxTimeout = Duration.ofMillis(300000); 047 048 /** 049 * Default timeout for JTA transactions. 050 */ 051 private Duration defaultJtaTimeout = Duration.ofMillis(10000); 052 053 /** 054 * Maximum number of active transactions. 055 */ 056 private int maxActives = 50; 057 058 /** 059 * Whether to enable disk logging. 060 */ 061 private boolean enableLogging = true; 062 063 /** 064 * The transaction manager's unique name. Defaults to the machine's IP address. If you 065 * plan to run more than one transaction manager against one database you must set 066 * this property to a unique value. 067 */ 068 private String transactionManagerUniqueName; 069 070 /** 071 * Whether sub-transactions should be joined when possible. 072 */ 073 private boolean serialJtaTransactions = true; 074 075 /** 076 * Specify whether sub-transactions are allowed. 077 */ 078 private boolean allowSubTransactions = true; 079 080 /** 081 * Whether a VM shutdown should trigger forced shutdown of the transaction core. 082 */ 083 private boolean forceShutdownOnVmExit; 084 085 /** 086 * How long should normal shutdown (no-force) wait for transactions to complete. 087 */ 088 private long defaultMaxWaitTimeOnShutdown = Long.MAX_VALUE; 089 090 /** 091 * Transactions log file base name. 092 */ 093 private String logBaseName = "tmlog"; 094 095 /** 096 * Directory in which the log files should be stored. Defaults to the current working 097 * directory. 098 */ 099 private String logBaseDir; 100 101 /** 102 * Interval between checkpoints, expressed as the number of log writes between two 103 * checkpoints. A checkpoint reduces the log file size at the expense of adding some 104 * overhead in the runtime. 105 */ 106 private long checkpointInterval = 500; 107 108 /** 109 * Whether to use different (and concurrent) threads for two-phase commit on the 110 * participating resources. 111 */ 112 private boolean threadedTwoPhaseCommit; 113 114 private final Recovery recovery = new Recovery(); 115 116 /** 117 * Specifies the transaction manager implementation that should be started. There is 118 * no default value and this must be set. Generally, 119 * {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory} is the 120 * value you should set. 121 * @param service the service 122 */ 123 public void setService(String service) { 124 this.service = service; 125 } 126 127 public String getService() { 128 return this.service; 129 } 130 131 /** 132 * Specifies the maximum timeout that can be allowed for transactions. Defaults to 133 * {@literal 300000}. This means that calls to UserTransaction.setTransactionTimeout() 134 * with a value higher than configured here will be max'ed to this value. 135 * @param maxTimeout the max timeout 136 */ 137 public void setMaxTimeout(Duration maxTimeout) { 138 this.maxTimeout = maxTimeout; 139 } 140 141 public Duration getMaxTimeout() { 142 return this.maxTimeout; 143 } 144 145 /** 146 * The default timeout for JTA transactions (optional, defaults to {@literal 10000} 147 * ms). 148 * @param defaultJtaTimeout the default JTA timeout 149 */ 150 public void setDefaultJtaTimeout(Duration defaultJtaTimeout) { 151 this.defaultJtaTimeout = defaultJtaTimeout; 152 } 153 154 public Duration getDefaultJtaTimeout() { 155 return this.defaultJtaTimeout; 156 } 157 158 /** 159 * Specifies the maximum number of active transactions. Defaults to {@literal 50}. A 160 * negative value means infinite amount. You will get an {@code IllegalStateException} 161 * with error message "Max number of active transactions reached" if you call 162 * {@code UserTransaction.begin()} while there are already n concurrent transactions 163 * running, n being this value. 164 * @param maxActives the max activities 165 */ 166 public void setMaxActives(int maxActives) { 167 this.maxActives = maxActives; 168 } 169 170 public int getMaxActives() { 171 return this.maxActives; 172 } 173 174 /** 175 * Specifies if disk logging should be enabled or not. Defaults to true. It is useful 176 * for JUnit testing, or to profile code without seeing the transaction manager's 177 * activity as a hot spot but this should never be disabled on production or data 178 * integrity cannot be guaranteed. 179 * @param enableLogging if logging is enabled 180 */ 181 public void setEnableLogging(boolean enableLogging) { 182 this.enableLogging = enableLogging; 183 } 184 185 public boolean isEnableLogging() { 186 return this.enableLogging; 187 } 188 189 /** 190 * Specifies the transaction manager's unique name. Defaults to the machine's IP 191 * address. If you plan to run more than one transaction manager against one database 192 * you must set this property to a unique value or you might run into duplicate 193 * transaction ID (XID) problems that can be quite subtle (example: 194 * {@literal http://fogbugz.atomikos.com/default.asp?community.6.2225.7}). If multiple 195 * instances need to use the same properties file then the easiest way to ensure 196 * uniqueness for this property is by referencing a system property specified at VM 197 * startup. 198 * @param uniqueName the unique name 199 */ 200 public void setTransactionManagerUniqueName(String uniqueName) { 201 this.transactionManagerUniqueName = uniqueName; 202 } 203 204 public String getTransactionManagerUniqueName() { 205 return this.transactionManagerUniqueName; 206 } 207 208 /** 209 * Specifies if subtransactions should be joined when possible. Defaults to true. When 210 * false, no attempt to call {@code XAResource.start(TM_JOIN)} will be made for 211 * different but related subtransactions. This setting has no effect on resource 212 * access within one and the same transaction. If you don't use subtransactions then 213 * this setting can be ignored. 214 * @param serialJtaTransactions if serial JTA transactions are supported 215 */ 216 public void setSerialJtaTransactions(boolean serialJtaTransactions) { 217 this.serialJtaTransactions = serialJtaTransactions; 218 } 219 220 public boolean isSerialJtaTransactions() { 221 return this.serialJtaTransactions; 222 } 223 224 public void setAllowSubTransactions(boolean allowSubTransactions) { 225 this.allowSubTransactions = allowSubTransactions; 226 } 227 228 public boolean isAllowSubTransactions() { 229 return this.allowSubTransactions; 230 } 231 232 /** 233 * Specifies whether VM shutdown should trigger forced shutdown of the transaction 234 * core. Defaults to false. 235 * @param forceShutdownOnVmExit if VM shutdown should be forced 236 */ 237 public void setForceShutdownOnVmExit(boolean forceShutdownOnVmExit) { 238 this.forceShutdownOnVmExit = forceShutdownOnVmExit; 239 } 240 241 public boolean isForceShutdownOnVmExit() { 242 return this.forceShutdownOnVmExit; 243 } 244 245 /** 246 * Specifies how long should a normal shutdown (no-force) wait for transactions to 247 * complete. Defaults to {@literal Long.MAX_VALUE}. 248 * @param defaultMaxWaitTimeOnShutdown the default max wait time on shutdown 249 */ 250 public void setDefaultMaxWaitTimeOnShutdown(long defaultMaxWaitTimeOnShutdown) { 251 this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown; 252 } 253 254 public long getDefaultMaxWaitTimeOnShutdown() { 255 return this.defaultMaxWaitTimeOnShutdown; 256 } 257 258 /** 259 * Specifies the transactions log file base name. Defaults to {@literal tmlog}. The 260 * transactions logs are stored in files using this name appended with a number and 261 * the extension {@literal .log}. At checkpoint, a new transactions log file is 262 * created and the number is incremented. 263 * @param logBaseName the log base name 264 */ 265 public void setLogBaseName(String logBaseName) { 266 this.logBaseName = logBaseName; 267 } 268 269 public String getLogBaseName() { 270 return this.logBaseName; 271 } 272 273 /** 274 * Specifies the directory in which the log files should be stored. Defaults to the 275 * current working directory. This directory should be a stable storage like a SAN, 276 * RAID or at least backed up location. The transactions logs files are as important 277 * as the data themselves to guarantee consistency in case of failures. 278 * @param logBaseDir the log base dir 279 */ 280 public void setLogBaseDir(String logBaseDir) { 281 this.logBaseDir = logBaseDir; 282 } 283 284 public String getLogBaseDir() { 285 return this.logBaseDir; 286 } 287 288 /** 289 * Specifies the interval between checkpoints. A checkpoint reduces the log file size 290 * at the expense of adding some overhead in the runtime. Defaults to {@literal 500}. 291 * @param checkpointInterval the checkpoint interval 292 */ 293 public void setCheckpointInterval(long checkpointInterval) { 294 this.checkpointInterval = checkpointInterval; 295 } 296 297 public long getCheckpointInterval() { 298 return this.checkpointInterval; 299 } 300 301 /** 302 * Specifies whether or not to use different (and concurrent) threads for two-phase 303 * commit on the participating resources. Setting this to {@literal true} implies that 304 * the commit is more efficient since waiting for acknowledgements is done in 305 * parallel. Defaults to {@literal true}. If you set this to {@literal false}, then 306 * commits will happen in the order that resources are accessed within the 307 * transaction. 308 * @param threadedTwoPhaseCommit if threaded two phase commits should be used 309 */ 310 public void setThreadedTwoPhaseCommit(boolean threadedTwoPhaseCommit) { 311 this.threadedTwoPhaseCommit = threadedTwoPhaseCommit; 312 } 313 314 public boolean isThreadedTwoPhaseCommit() { 315 return this.threadedTwoPhaseCommit; 316 } 317 318 public Recovery getRecovery() { 319 return this.recovery; 320 } 321 322 /** 323 * Returns the properties as a {@link Properties} object that can be used with 324 * Atomikos. 325 * @return the properties 326 */ 327 public Properties asProperties() { 328 Properties properties = new Properties(); 329 set(properties, "service", getService()); 330 set(properties, "max_timeout", getMaxTimeout()); 331 set(properties, "default_jta_timeout", getDefaultJtaTimeout()); 332 set(properties, "max_actives", getMaxActives()); 333 set(properties, "enable_logging", isEnableLogging()); 334 set(properties, "tm_unique_name", getTransactionManagerUniqueName()); 335 set(properties, "serial_jta_transactions", isSerialJtaTransactions()); 336 set(properties, "allow_subtransactions", isAllowSubTransactions()); 337 set(properties, "force_shutdown_on_vm_exit", isForceShutdownOnVmExit()); 338 set(properties, "default_max_wait_time_on_shutdown", 339 getDefaultMaxWaitTimeOnShutdown()); 340 set(properties, "log_base_name", getLogBaseName()); 341 set(properties, "log_base_dir", getLogBaseDir()); 342 set(properties, "checkpoint_interval", getCheckpointInterval()); 343 set(properties, "threaded_2pc", isThreadedTwoPhaseCommit()); 344 Recovery recovery = getRecovery(); 345 set(properties, "forget_orphaned_log_entries_delay", 346 recovery.getForgetOrphanedLogEntriesDelay()); 347 set(properties, "recovery_delay", recovery.getDelay()); 348 set(properties, "oltp_max_retries", recovery.getMaxRetries()); 349 set(properties, "oltp_retry_interval", recovery.getRetryInterval()); 350 return properties; 351 } 352 353 private void set(Properties properties, String key, Object value) { 354 String id = "com.atomikos.icatch." + key; 355 if (value != null && !properties.containsKey(id)) { 356 properties.setProperty(id, asString(value)); 357 } 358 } 359 360 private String asString(Object value) { 361 if (value instanceof Duration) { 362 return String.valueOf(((Duration) value).toMillis()); 363 } 364 return value.toString(); 365 } 366 367 /** 368 * Recovery specific settings. 369 */ 370 public static class Recovery { 371 372 /** 373 * Delay after which recovery can cleanup pending ('orphaned') log entries. 374 */ 375 private Duration forgetOrphanedLogEntriesDelay = Duration.ofMillis(86400000); 376 377 /** 378 * Delay between two recovery scans. 379 */ 380 private Duration delay = Duration.ofMillis(10000); 381 382 /** 383 * Number of retry attempts to commit the transaction before throwing an 384 * exception. 385 */ 386 private int maxRetries = 5; 387 388 /** 389 * Delay between retry attempts. 390 */ 391 private Duration retryInterval = Duration.ofMillis(10000); 392 393 public Duration getForgetOrphanedLogEntriesDelay() { 394 return this.forgetOrphanedLogEntriesDelay; 395 } 396 397 public void setForgetOrphanedLogEntriesDelay( 398 Duration forgetOrphanedLogEntriesDelay) { 399 this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay; 400 } 401 402 public Duration getDelay() { 403 return this.delay; 404 } 405 406 public void setDelay(Duration delay) { 407 this.delay = delay; 408 } 409 410 public int getMaxRetries() { 411 return this.maxRetries; 412 } 413 414 public void setMaxRetries(int maxRetries) { 415 this.maxRetries = maxRetries; 416 } 417 418 public Duration getRetryInterval() { 419 return this.retryInterval; 420 } 421 422 public void setRetryInterval(Duration retryInterval) { 423 this.retryInterval = retryInterval; 424 } 425 426 } 427 428}