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}