diff --git a/.github/Atomikos_Logo_Background.png b/.github/Atomikos_Logo_Background.png
new file mode 100644
index 000000000..4f396cf59
Binary files /dev/null and b/.github/Atomikos_Logo_Background.png differ
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..6f48ba968
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,21 @@
+---
+name: Bug report
+about: Create a report to help us improve our next community release!
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+ 1. What you were doing (or trying to do)
+ 1. What happened
+ 1. What you expected to happen instead
+ 1. What TransactionsEssentials version you are using
+
+**Additional context**
+Add any other context about the problem here.
+
+_I understand that this project is not intended for support - because bug reports may or may not be considered for inclusion some day (in a future release). If this is issue is important to me then I can go to https://www.atomikos.com/Main/SupportOverview and arrange a paid support subscription._
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000..066b2d920
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/atomikos_horizontal_logo.jpg b/.github/atomikos_horizontal_logo.jpg
new file mode 100644
index 000000000..6952ce799
Binary files /dev/null and b/.github/atomikos_horizontal_logo.jpg differ
diff --git a/.github/inconsistency.jpg b/.github/inconsistency.jpg
new file mode 100644
index 000000000..9f895b294
Binary files /dev/null and b/.github/inconsistency.jpg differ
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..2ce979175
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,3 @@
+_Write your description here. BTW we're really honoured to receive your help, but please understand that some pull requests may be rejected for several reasons - in particular because it might conflict with the commercial offerings of Atomikos. Without making money on something, we would not have been able to give TransactionsEssentials for free in the first place! Thanks for understanding..._
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..759bbe274
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+private/
+specs/
+target/
+.hg/
+.settings/
+.classpath
+.hgignore
+.hgtags
+.project
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 000000000..9d98a14b1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+language: java
+
+jdk:
+ - openjdk6
+
+install: mvn install -DskipTests=true -Popensource
+
+script: mvn clean test -Popensource
+
+
+notifications:
+ email:
+ - guy@atomikos.com
+ - pascal.leclercq@gmail.com
\ No newline at end of file
diff --git a/CODE-OF-CONDUCT.MD b/CODE-OF-CONDUCT.MD
new file mode 100644
index 000000000..2db907eda
--- /dev/null
+++ b/CODE-OF-CONDUCT.MD
@@ -0,0 +1,12 @@
+# Our Code of Conduct
+
+We try to manage this community with 2 simple rules:
+
+ 1. Focus on improving TransactionsEssentials and its user-friendliness.
+ 1. Intend no harm towards fellow members.
+
+Let these rules guide everything you say and do here.
+
+## Reporting Violations
+
+If you suspect any violations then let us know by emailing to webmaster at atomikos dot com.
\ No newline at end of file
diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD
new file mode 100644
index 000000000..f2fb03314
--- /dev/null
+++ b/CONTRIBUTING.MD
@@ -0,0 +1,62 @@
+# Contributing to Atomikos TransactionsEssentials
+
+Thank you very much for considering a contribution to our project!
+
+Please follow the guidelines on this page to make things go smooth...
+
+## Most Wanted
+
+We're happy with all the help we can get (and you want to give), but we think that the following would be the most useful contributions:
+
+ - Vote on existing feature requests you need most - so we know which ones are priority for the community.
+ - Give feedback on planned features before we build them - so we can avoid implementing useless things.
+ - Report on missing features so we can make our product better for you. Better still: send us a PR with the implementation.
+ - Did you already fork and make changes for yourself? Why not send us a PR to check if we can use this, too.
+ - Report bugs - so we can maximize stability (or even better if you can: send us a PR with a fix).
+ - Create some visibility by starring the project, blogging about it, ...
+
+## How to Request a Feature
+
+You can request a new feature by opening an issue and choosing the feature request template.
+
+## How to Report a Bug
+
+You can report bugs by opening an issue and choosing the bug report template.
+
+## How to Contribute Code (Features or Fixes)
+
+ 1. If you need inspiration: check our open issues with contribution labels (i.e., ["good first issue"](https://github.com/atomikos/transactions-essentials/labels/good%20first%20issue) or ["help wanted"](https://github.com/atomikos/transactions-essentials/labels/help%20wanted))...
+ 1. Implement a minimal set of changes in your fork of our sources.
+ 1. Create a pull request for us to merge.
+ 1. As part of the pull request, you will be asked to accept our contributor license terms.
+
+
+## Other Ways to Contribute
+
+ 1. Vote on issues you find useful - this will help us prioritize work for the future releases!
+ 1. Give feedback on planned features before we build them - so we can do them the right way.
+ 1. Help us grow this community by [recommending us](https://www.atomikos.com/Main/RecommendUs) or starring this repository.
+ 1. Feel free to share bug reports, usage problems or clarity issues...
+ 1. Help us learn: [share your story](https://www.surveymonkey.com/r/XMS5987) so we can use your insight to improve the project.
+ 1. Share configuration examples (or other useful documentation) on our [documentation wiki](https://www.atomikos.com/Documentation).
+ 1. Write a blog post about how you use this project.
+ 1. Request a new feature if you find that useful functionality is missing.
+ 1. Consider getting a paid subscription so we (Atomikos) can continue working full-time on this technology :-)
+
+## How Pull Requests Can Get Accepted
+
+ - We review every pull request, although it may take a while.
+ - Please respond to any questions why might have - so we can accept your pull request.
+ - Issues for which we had questions but receive no answer within 2 weeks will likely be closed without being incorporated.
+ - Accepted pull requests require post-processing on our end, because we typically add the required tests to our test suites. We'll probably also change the code here and there to match our design style.
+
+## What If... My Pull Request Is Rejected?
+
+It happens, don't take it personally:
+
+ - Our open source work is only sustainable because of the money we make on commercial support and power features, so we may reject pull requests that we believe to be in conflict with our commercial distribution. Otherwise, there would not be this open source code base to contribute to in the first place...
+ - We try to keep the product scope focused so we may also reject pull requests that we feel are not aligned with our long-term vision.
+
+You can also look at it in a positive way: here on github it is easy to fork and modify whatever you like, so the project fits your specific needs. In some cases, those needs overlap with our vision and that's the perfect pull request. In other cases, there is not much overlap - but then you still have your fork with your custom changes. That's a lot better than in the past, when we did not have a public repository at all.
+
+_Thanks for your understanding!_
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 000000000..f893d6c08
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,17 @@
+pipeline {
+ agent any
+ tools {
+ maven 'maven-3.6.0'
+ }
+// Use the pomfix tool to validate that bundle dependencies are properly declared
+ stage('Build') {
+ steps {
+ retry(3) {
+ checkout scm
+ }
+ withMaven(maven: 'maven-3.6.0', jdk: 'jdk8-latest', mavenOpts: '-Xmx1024m -Xms512m') {
+ sh 'mvn clean install'
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 000000000..cfebf9b15
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,4 @@
+LICENSE CONDITIONS
+
+See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..2663afed2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,133 @@
+
+
+
+
+
+## Highlights
+
+* **Distributed transaction management for Java** - so your Java application gets **instant reliability** without design efforts in your code.
+* **Microservices support** - so you can have one global transaction spanning several services.
+* **JEE compatible** - so it integrates effortlessly in your **Spring** or **Tomcat** configuration.
+* **Javax and JakartaEE compatible** - so you can use the latest greatest ecosystem if you want to.
+* **Light-weight** - so your **microservices** can use it, too.
+* **Embeddable in your code** - so you can **test everything in the IDE** and avoid late integration issues at deployment time.
+* **OSGi support** - so you can use OSGi containers also.
+* **Connection pooling for JDBC and JMS** - so you get maximum **performance**.
+* **Built-in support for Hibernate and JPA** - so you can use your favorite persistence framework.
+* **Automatic crash / restart recovery** - so your incomplete **distributed transactions are cleaned up** and your data returns to a consistent state.
+* **Cloud-native design** - so your applications are ready for **deployment to your cloud**.
+* **Commercial support available** - if you're serious about transactions in your business.
+
+## NEW: JakartaEE support
+
+Relevant jars are transformed into jakarta equivalents so we support both Javax and Jakarta.
+
+## NEW: Microservice Support
+
+With microservices, the typical monolith ACID transaction becomes split into 2 or more local transactions, one in each microservice. This can quickly lead to inconsistencies - for instance if there are network timeouts or other failures:
+
+
+
+Some concrete examples of possible inconsistencies:
+
+ * For network timeouts, microservice 1 does not see the difference between a failure/rollback of microservice 2 and a commit at microservice 2. The global system state (and consistency) is undefined.
+ * For retries (when microservice 1 calls microservice 2 a number of times until it does succeed) we depend on microservice 2 offering a correct implementation of an idempotent consumer. This is hard and error-prone.
+ * Also for retries, microservice 1 has to remember that it needs to retry at microservice 2. This adds technical complexity to an otherwise simple scenario.
+ * For failures when invoking microservice 2 there is no easy alternative but to retry (due to the above). This means you can't have microservice 1 forget about microservice 2 and try an equivalent "competing" implementation. Why? Because it just might be that the invocation of microservice 2 did actually commit.
+
+We allow 1 global transaction, even across separate microservices and you don't have to code anything for that. In the example shown, a failed call means rollback at microservice 2, and if you want then you can safely try an alternative microservice instead (even within the same global transaction).
+
+Working samples are included in our official download. This enables safer retries (if you do want to retry - that is your choice) plus the notion of a global commit.
+
+For more information on how to fine-tune microservice transactions: check out our online course.
+
+## Using TransactionsEssentials
+
+### Getting Started
+
+See [Getting Started](http://www.atomikos.com/Documentation/GettingStarted) for general documentation.
+
+### Documentation
+
+See documentation at [www.atomikos.com/Documentation](http://www.atomikos.com/Documentation/)
+
+### Code Samples
+
+Register and download from [www.atomikos.com](http://www.atomikos.com/)
+
+### Releases
+
+Register and download from [www.atomikos.com](http://www.atomikos.com/) and get documentation plus working sample applications
+
+Or check [Maven Central (without the samples or documentation)](http://search.maven.org)
+
+
+## Governance & Participating
+
+### Joining
+
+See our [Community Page](http://www.atomikos.com/Main/AtomikosCommunity) for how to join us.
+
+### Building From Source
+
+Pull latest from repo `git pull origin master` and try `mvn clean install -Popensource`.
+
+### Contributing
+
+The fastest way to contribute is by starring this project. Thank you :-)
+
+See our [contributor guidelines](CONTRIBUTING.MD) for inspiration and guidance.
+
+### Code of conduct
+
+See our [code of conduct](CODE-OF-CONDUCT.MD) for details and how to report violations.
+
+### About This Repository
+
+This GitHub project is merged with - and updated regularly from - our internal development repository to work towards our next open source release (note: stable maintenance releases and our commercial power features are managed outside GitHub).
+
+IMPORTANT: we (Atomikos) don't develop on GitHub ourselves (yet) so you won't see a lot of our commits here - only refreshes when we merge + push to GitHub. That is because at least initially, the sole purpose of this project is to allow interested GitHub community members to fork and contribute useful features to what we have.
+
+Activities here are probably higher when:
+
+ - we prepare a new community release (milestone), and
+ - the 3 months of stabilization period after that (during which we publish lots of bug fixes)
+ - (check the milestones page with due dates to get an impression...)
+
+After that, we are busy upgrading our customers and on-boarding new customers so you will see less activity here. That's because most customer work is done in our private repositories.
+
+## Special Thanks / Featured Contributors
+
+You know how it goes, one always forgets to mention someone - but the following fine people have all delivered memorable technical contributions to this project in one way or another.
+
+So: **thank you!** (and apologies to the superstars not mentioned here):
+
+ - [@pascalleclercq](https://github.com/pascalleclercq)
+ - [@tkrah](https://github.com/tkrah)
+ - [@dpocock](https://github.com/dpocock)
+ - [@lorban](https://github.com/lorban)
+ - [@dandiep](https://github.com/dandiep)
+ - [@gregw](https://github.com/gregw)
+ - [@janbartel](https://github.com/janbartel)
+ - [@Fungrim](https://github.com/Fungrim)
+
+
+## License
+
+See our [license policy page](http://www.atomikos.com/Main/WhichLicenseApplies).
+
+## Feedback Wanted!
+
+Do you think something's missing on this page? Please open an issue to let us know!
+
+_Copyright (c) 2000-2023, Atomikos - all rights reserved. Visit [www.atomikos.com](http://www.atomikos.com/) for more..._
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..1b697c643
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,360 @@
+
+
+ 4.0.0
+ com.atomikos
+ atomikos-parent
+ 6.0.1-SNAPSHOT
+
+ pom
+ Atomikos All POM
+ http://www.atomikos.com/
+ Reliability through Atomicity: manage your distributed transactions and protect your mission critical data
+
+
+ Atomikos Multiple Licensing Scheme
+ http://www.atomikos.com/Main/WhichLicenseApplies
+ repo
+
+
+
+
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+ Atomikos
+ info@atomikos.com
+ Atomikos
+ https://www.atomikos.com
+
+
+
+ scm:hg:http://atomikos.repositoryhosting.com/hg/atomikos/development
+ scm:hg:http://atomikos.repositoryhosting.com/hg/atomikos/development
+ http://www.atomikos.com/
+ HEAD
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.3.1
+
+ false
+ -Xdoclint:none
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.1
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.4
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.4.3
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.14
+
+
+ com.atlassian.maven.plugins
+ maven-clover2-plugin
+ 3.0.1
+
+
+ com.agilejava.docbkx
+ docbkx-maven-plugin
+ 2.0.11
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.3.1
+
+
+ com.mycila.maven-license-plugin
+ maven-license-plugin
+ 1.8.0
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 2.5.4
+
+
+ org.ops4j.pax.exam
+ maven-paxexam-plugin
+ 1.2.2
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+ 2.4.1
+
+
+ org.eclipse.transformer
+ transformer-maven-plugin
+ 0.5.0
+
+
+
+
+
+
+ org.eclipse.transformer
+ transformer-maven-plugin
+ true
+
+ jakarta
+
+ true
+
+
+
+
+ default-jar
+
+ jar
+
+
+
+ ${project.groupId}
+ ${project.artifactId}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+ -Xmx1024m
+
+ **/*TestJUnit.java
+
+
+ ${project.build.testOutputDirectory}
+
+
+
+ maven-javadoc-plugin
+
+ com.atomikos
+
+
+
+
+ org.apache.maven.plugins
+ maven-release-plugin
+
+ true
+
+ clean package javadoc:aggregate-jar install
+ deploy
+ -DskipTests -Passemble
+ ${releaseVersion}
+ @{project.version}
+
+
+
+
+
+
+ org.apache.maven.wagon
+ wagon-ftp
+ 1.0-beta-6
+
+
+
+
+
+
+
+ org.mockito
+ mockito-core
+ 4.4.0
+ test
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.17.2
+ test
+
+
+ javax.interceptor
+ javax.interceptor-api
+ 1.2.2
+ provided
+
+
+
+
+
+
+ local
+
+
+
+ maven-release-plugin
+
+ false
+ true
+
+
+
+ maven-deploy-plugin
+
+ true
+
+
+
+
+
+
+ public
+ private
+ specs
+
+
+
+ all
+
+ true
+
+
+
+ atomikos.website.maven.repo
+ ftp://www.atomikos.com
+
+
+ internal.snapshot.repo
+ Atomikos Internal Snapshot Repository
+ file://${user.home}/.m2/repository
+
+
+
+
+
+ public
+ private
+ specs
+
+
+
+
+ opensource
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.3.1
+
+ false
+ -Xdoclint:none
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+ public
+
+
+
+ sonatype-nexus-snapshots
+ Sonatype Nexus Snapshots
+ https://oss.sonatype.org/content/repositories/snapshots/
+
+
+ sonatype-nexus-staging
+ Nexus Release Repository
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+
+
+ release-sign-artifacts
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ jdepend-maven-plugin
+ 2.0
+
+
+
+
+
diff --git a/public/header.txt b/public/header.txt
new file mode 100644
index 000000000..1424b4cd9
--- /dev/null
+++ b/public/header.txt
@@ -0,0 +1,5 @@
+Copyright (C) 2000-2024 Atomikos
+
+LICENSE CONDITIONS
+
+See http://www.atomikos.com/Main/WhichLicenseApplies for details.
diff --git a/public/pom.xml b/public/pom.xml
new file mode 100644
index 000000000..387ddc462
--- /dev/null
+++ b/public/pom.xml
@@ -0,0 +1,107 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ atomikos-parent
+ 6.0.1-SNAPSHOT
+
+ ate
+ pom
+ Atomikos Transactions Essentials
+
+
+ 2000-2024
+ true
+
+
+
+ util
+ transactions-api
+ transactions
+ transactions-jta
+ transactions-jdbc
+ transactions-jms
+ transactions-osgi
+ transactions-hibernate2
+ transactions-hibernate3
+ transactions-hibernate4
+ transactions-eclipselink
+ transactions-jndi-provider
+ transactions-essentials
+ transactions-essentials-jakarta
+ transactions-remoting
+ spring-boot2
+ spring-boot3
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ true
+
+
+ attach-sources
+ package
+
+ jar-no-fork
+
+
+
+
+
+
+
+
+ false
+ com.mycila.maven-license-plugin
+ maven-license-plugin
+
+
+ header.txt
+ false
+ true
+ false
+
+ src/**
+
+
+ src/test/**
+ target/**
+ .clover/**
+ private/**
+ axt/**
+
+ true
+
+ true
+
+ ${project.inceptionYear}
+ Atomikos
+ info@atomikos.com
+
+ UTF-8
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
diff --git a/public/spring-boot2/pom.xml b/public/spring-boot2/pom.xml
new file mode 100644
index 000000000..60c1e7a5a
--- /dev/null
+++ b/public/spring-boot2/pom.xml
@@ -0,0 +1,37 @@
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ spring-boot2
+ pom
+
+ 2.3.4.RELEASE
+
+
+ transactions-spring-boot
+ transactions-spring-boot-starter
+ transactions-spring-boot-integration-tests
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ pom
+ import
+ ${boot.version}
+
+
+
+
+
+ org.mockito
+ mockito-core
+ 4.4.0
+ test
+
+
+
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/.gitignore b/public/spring-boot2/transactions-spring-boot-integration-tests/.gitignore
new file mode 100644
index 000000000..8fbc30c96
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/.gitignore
@@ -0,0 +1 @@
+transaction-logs
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/pom.xml b/public/spring-boot2/transactions-spring-boot-integration-tests/pom.xml
new file mode 100644
index 000000000..8a7ff9964
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ spring-boot2
+ 6.0.1-SNAPSHOT
+
+ transactions-spring-boot-integration-tests
+ Transactions Spring Boot Integration Tests
+
+
+ com.atomikos
+ transactions-spring-boot-starter
+ 6.0.1-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-artemis
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework
+ spring-jms
+
+
+ com.h2database
+ h2
+
+
+ org.apache.activemq
+ artemis-jms-server
+
+
+ org.apache.geronimo.specs
+ geronimo-jms_2.0_spec
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.3.4.RELEASE
+ pom
+ import
+
+
+
+
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Account.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Account.java
new file mode 100644
index 000000000..2ca6d45ca
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Account.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class Account {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+
+ private String username;
+
+ Account() {
+ }
+
+ public Account(String username) {
+ this.username = username;
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountRepository.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountRepository.java
new file mode 100644
index 000000000..a850470ac
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountRepository.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AccountRepository extends JpaRepository {
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountService.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountService.java
new file mode 100644
index 000000000..3a90fcf30
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/AccountService.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import javax.transaction.Transactional;
+
+import org.springframework.jms.core.JmsTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@Transactional
+public class AccountService {
+
+ private final JmsTemplate jmsTemplate;
+
+ private final AccountRepository accountRepository;
+
+ 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 RuntimeException("Simulated error");
+ }
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Messages.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Messages.java
new file mode 100644
index 000000000..96e114c96
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/Messages.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class Messages {
+
+ @JmsListener(destination = "accounts")
+ public void onMessage(String content) {
+ System.out.println("----> " + content);
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/SampleAtomikosApplication.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/SampleAtomikosApplication.java
new file mode 100644
index 000000000..b442d2c5a
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/java/com/atomikos/spring/integrationtest/SampleAtomikosApplication.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import java.io.Closeable;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+
+@SpringBootApplication
+public class SampleAtomikosApplication {
+
+ public static void main(String... args) throws Exception {
+ ApplicationContext context = SpringApplication.run(SampleAtomikosApplication.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 {
+ service.createAccountAndNotify("error");
+ }
+ catch (Exception ex) {
+ System.out.println(ex.getMessage());
+ }
+ System.out.println("Count is " + repository.count());
+ Thread.sleep(100);
+ ((Closeable) context).close();
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/resources/application.properties b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/resources/application.properties
new file mode 100644
index 000000000..62ba22b88
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/main/resources/application.properties
@@ -0,0 +1,11 @@
+#
+# Copyright (C) 2000-2024 Atomikos
+#
+# LICENSE CONDITIONS
+#
+# See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+#
+
+logging.level.com.atomikos=DEBUG
+spring.artemis.embedded.queues=accounts
+spring.jpa.open-in-view=true
diff --git a/public/spring-boot2/transactions-spring-boot-integration-tests/src/test/java/com/atomikos/spring/integrationtest/SampleAtomikosApplicationTests.java b/public/spring-boot2/transactions-spring-boot-integration-tests/src/test/java/com/atomikos/spring/integrationtest/SampleAtomikosApplicationTests.java
new file mode 100644
index 000000000..583b098c1
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-integration-tests/src/test/java/com/atomikos/spring/integrationtest/SampleAtomikosApplicationTests.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring.integrationtest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.system.CapturedOutput;
+import org.springframework.boot.test.system.OutputCaptureExtension;
+import org.springframework.util.StringUtils;
+
+/**
+ * Basic integration tests for demo application.
+ */
+@ExtendWith(OutputCaptureExtension.class)
+class SampleAtomikosApplicationTests {
+
+ @Test
+ void testTransactionRollback(CapturedOutput output) throws Exception {
+ SampleAtomikosApplication.main();
+ assertThat(output).satisfies(numberOfOccurrences("---->", 1));
+ assertThat(output).satisfies(numberOfOccurrences("----> josh", 1));
+ assertThat(output).satisfies(numberOfOccurrences("Count is 1", 2));
+ assertThat(output).satisfies(numberOfOccurrences("Simulated error", 1));
+ }
+
+ @Test
+ void testTransactionRollbackWithoutSpringBoot23JtaAutoConfiguration(CapturedOutput output) throws Exception {
+ SampleAtomikosApplication.main("--spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration");
+ assertThat(output).satisfies(numberOfOccurrences("---->", 1));
+ assertThat(output).satisfies(numberOfOccurrences("----> josh", 1));
+ assertThat(output).satisfies(numberOfOccurrences("Count is 1", 2));
+ assertThat(output).satisfies(numberOfOccurrences("Simulated error", 1));
+ }
+
+ private Consumer numberOfOccurrences(String substring, int expectedCount) {
+ return (charSequence) -> {
+ int count = StringUtils.countOccurrencesOf(charSequence.toString(), substring);
+ assertThat(count).isEqualTo(expectedCount);
+ };
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot-starter/pom.xml b/public/spring-boot2/transactions-spring-boot-starter/pom.xml
new file mode 100644
index 000000000..056f34a12
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot-starter/pom.xml
@@ -0,0 +1,44 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ spring-boot2
+ 6.0.1-SNAPSHOT
+
+ transactions-spring-boot-starter
+ Transactions Spring Boot Starter
+
+
+ com.atomikos
+ transactions-spring-boot
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jms
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jdbc
+ 6.0.1-SNAPSHOT
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+ true
+
+
+
+
+
diff --git a/public/spring-boot2/transactions-spring-boot/.gitignore b/public/spring-boot2/transactions-spring-boot/.gitignore
new file mode 100644
index 000000000..8fbc30c96
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/.gitignore
@@ -0,0 +1 @@
+transaction-logs
diff --git a/public/spring-boot2/transactions-spring-boot/pom.xml b/public/spring-boot2/transactions-spring-boot/pom.xml
new file mode 100644
index 000000000..6919530ed
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ spring-boot2
+ 6.0.1-SNAPSHOT
+
+ transactions-spring-boot
+ Transactions Spring Boot
+
+
+ jakarta.jms
+ jakarta.jms-api
+ provided
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+ provided
+
+
+ com.atomikos
+ transactions-jdbc
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jms
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ org.springframework
+ spring-tx
+
+
+ org.springframework.boot
+ spring-boot
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ 2.3.4.RELEASE
+ true
+
+
+
diff --git a/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosAutoConfiguration.java b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosAutoConfiguration.java
new file mode 100644
index 000000000..88fa9c302
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosAutoConfiguration.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import java.util.Properties;
+
+import javax.jms.Message;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
+import org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jdbc.XADataSourceWrapper;
+import org.springframework.boot.jms.XAConnectionFactoryWrapper;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import com.atomikos.icatch.config.UserTransactionService;
+import com.atomikos.icatch.config.UserTransactionServiceImp;
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for Atomikos JTA.
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties({ SpringJtaAtomikosProperties.class, AtomikosProperties.class})
+@ConditionalOnClass({ JtaTransactionManager.class, UserTransactionManager.class })
+@ConditionalOnMissingBean(org.springframework.transaction.TransactionManager.class)
+@AutoConfigureBefore(
+ value = { XADataSourceAutoConfiguration.class, ActiveMQAutoConfiguration.class, ArtemisAutoConfiguration.class, HibernateJpaAutoConfiguration.class },
+ name = "org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration")
+class AtomikosAutoConfiguration {
+
+ @Bean(initMethod = "init", destroyMethod = "shutdownWait")
+ @ConditionalOnMissingBean(UserTransactionService.class)
+ UserTransactionServiceImp userTransactionService(SpringJtaAtomikosProperties springJtaAtomikosProperties, AtomikosProperties atomikosProperties) {
+ Properties properties = new Properties();
+ properties.putAll(springJtaAtomikosProperties.asProperties());
+ properties.putAll(atomikosProperties.asProperties());
+ return new UserTransactionServiceImp(properties);
+ }
+
+
+ @Bean(initMethod = "init", destroyMethod = "close")
+ @ConditionalOnMissingBean(TransactionManager.class)
+ UserTransactionManager atomikosTransactionManager(UserTransactionService userTransactionService) throws Exception {
+ UserTransactionManager manager = new UserTransactionManager();
+ manager.setStartupTransactionService(false);
+ manager.setForceShutdown(true);
+ return manager;
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(XADataSourceWrapper.class)
+ AtomikosXADataSourceWrapper xaDataSourceWrapper() {
+ return new AtomikosXADataSourceWrapper();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ static AtomikosDependsOnBeanFactoryPostProcessor atomikosDependsOnBeanFactoryPostProcessor() {
+ return new AtomikosDependsOnBeanFactoryPostProcessor();
+ }
+
+ @Bean
+ JtaTransactionManager transactionManager(UserTransaction userTransaction, TransactionManager transactionManager,
+ ObjectProvider transactionManagerCustomizers) {
+ JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, transactionManager);
+ transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(jtaTransactionManager));
+ return jtaTransactionManager;
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnClass(Message.class)
+ static class AtomikosJtaJmsConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean(XAConnectionFactoryWrapper.class)
+ AtomikosXAConnectionFactoryWrapper xaConnectionFactoryWrapper() {
+ return new AtomikosXAConnectionFactoryWrapper();
+ }
+
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosConnectionFactoryBean.java b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosConnectionFactoryBean.java
new file mode 100644
index 000000000..a1af32944
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosConnectionFactoryBean.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Spring friendly version of {@link com.atomikos.jms.AtomikosConnectionFactoryBean}.
+ */
+@SuppressWarnings("serial")
+@ConfigurationProperties(prefix = "spring.jta.atomikos.connectionfactory")
+public class AtomikosConnectionFactoryBean extends com.atomikos.jms.AtomikosConnectionFactoryBean
+ implements BeanNameAware, InitializingBean, DisposableBean {
+
+ private String beanName;
+
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (!StringUtils.hasLength(getUniqueResourceName())) {
+ setUniqueResourceName(this.beanName);
+ }
+ init();
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ close();
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBean.java b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBean.java
new file mode 100644
index 000000000..01ed82c65
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBean.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.springframework.beans.factory.BeanNameAware;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Spring friendly version of {@link com.atomikos.jdbc.AtomikosDataSourceBean}.
+ */
+@SuppressWarnings("serial")
+@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource")
+public class AtomikosDataSourceBean extends com.atomikos.jdbc.AtomikosDataSourceBean
+ implements BeanNameAware, InitializingBean, DisposableBean {
+
+ private String beanName;
+
+ @Override
+ public void setBeanName(String name) {
+ this.beanName = name;
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ if (!StringUtils.hasLength(getUniqueResourceName())) {
+ setUniqueResourceName(this.beanName);
+ }
+ init();
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ close();
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBeanMetadata.java b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBeanMetadata.java
new file mode 100644
index 000000000..d145b55e4
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourceBeanMetadata.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.springframework.boot.jdbc.metadata.AbstractDataSourcePoolMetadata;
+
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+
+public class AtomikosDataSourceBeanMetadata extends AbstractDataSourcePoolMetadata {
+
+
+ public AtomikosDataSourceBeanMetadata(AtomikosDataSourceBean dataSource) {
+ super(dataSource);
+ }
+
+
+ @Override
+ public Integer getActive() {
+ return getDataSource().poolTotalSize() - getDataSource().poolAvailableSize();
+ }
+
+ @Override
+ public Integer getMax() {
+ return getDataSource().getMaxPoolSize();
+ }
+
+ @Override
+ public Integer getMin() {
+ return getDataSource().getMinPoolSize();
+ }
+
+ @Override
+ public String getValidationQuery() {
+ return getDataSource().getTestQuery();
+ }
+
+ @Override
+ public Boolean getDefaultAutoCommit() {
+ return false;
+ }
+
+}
diff --git a/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourcePoolMetadataProvidersConfiguration.java b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourcePoolMetadataProvidersConfiguration.java
new file mode 100644
index 000000000..b05592386
--- /dev/null
+++ b/public/spring-boot2/transactions-spring-boot/src/main/java/com/atomikos/spring/AtomikosDataSourcePoolMetadataProvidersConfiguration.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.jdbc.DataSourceUnwrapper;
+import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+import com.atomikos.jdbc.AtomikosNonXADataSourceBean;
+
+/**
+ * Register the {@link AtomikosDataSourcePoolMetadataProvidersConfiguration} for the AtomikosDataSourceBeans. For use in the Spring Boot actuator:
+ * https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-metrics-jdbc.
+ * This configuration must be explicitly imported:
+ *
+ */
+@Configuration
+public class AtomikosDataSourcePoolMetadataProvidersConfiguration {
+
+ @Configuration
+ @ConditionalOnClass(AtomikosDataSourceBean.class)
+ static class AtomikosDataSourceBeanMetadataProviderConfiguration {
+
+ @Bean
+ public DataSourcePoolMetadataProvider atomikosDataSourceBeanMetadataProvider() {
+
+ return (dataSource) -> {
+ AtomikosDataSourceBean atomikosDataSource = DataSourceUnwrapper.unwrap(dataSource, AtomikosDataSourceBean.class);
+ if (atomikosDataSource != null) {
+ return new AtomikosDataSourceBeanMetadata(atomikosDataSource);
+ }
+ return null;
+ };
+ }
+ }
+
+ @Configuration
+ @ConditionalOnClass(AtomikosNonXADataSourceBean.class)
+ static class AtomikosNonXADataSourceBeanMetadataProviderConfiguration {
+
+ @Bean
+ public DataSourcePoolMetadataProvider atomikosNonXADataSourceBeanMetadataProvider() {
+
+ return (dataSource) -> {
+ AtomikosNonXADataSourceBean atomikosDataSource = DataSourceUnwrapper.unwrap(dataSource, AtomikosNonXADataSourceBean.class);
+ if (atomikosDataSource != null) {
+ return new AtomikosNonXADataSourceBeanMetadata(atomikosDataSource);
+ }
+ return null;
+ };
+ }
+ }
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessor.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessor.java
new file mode 100644
index 000000000..70d5579f5
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessor.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.core.Ordered;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link BeanFactoryPostProcessor} to automatically setup the recommended
+ * {@link BeanDefinition#setDependsOn(String[]) dependsOn} settings for
+ * correct Atomikos
+ * ordering.
+ */
+public class AtomikosDependsOnBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
+
+ private static final String[] NO_BEANS = {};
+
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+ String[] transactionManagers = beanFactory.getBeanNamesForType(UserTransactionManager.class, true, false);
+ for (String transactionManager : transactionManagers) {
+ addTransactionManagerDependencies(beanFactory, transactionManager);
+ }
+ addMessageDrivenContainerDependencies(beanFactory, transactionManagers);
+ }
+
+ private void addTransactionManagerDependencies(ConfigurableListableBeanFactory beanFactory,
+ String transactionManager) {
+ BeanDefinition bean = beanFactory.getBeanDefinition(transactionManager);
+ Set dependsOn = new LinkedHashSet<>(asList(bean.getDependsOn()));
+ int initialSize = dependsOn.size();
+ addDependencies(beanFactory, "jakarta.jms.ConnectionFactory", dependsOn);
+ addDependencies(beanFactory, "javax.sql.DataSource", dependsOn);
+ if (dependsOn.size() != initialSize) {
+ bean.setDependsOn(StringUtils.toStringArray(dependsOn));
+ }
+ }
+
+ private void addMessageDrivenContainerDependencies(ConfigurableListableBeanFactory beanFactory,
+ String[] transactionManagers) {
+ String[] messageDrivenContainers = getBeanNamesForType(beanFactory,
+ "com.atomikos.jms.extra.MessageDrivenContainer");
+ for (String messageDrivenContainer : messageDrivenContainers) {
+ BeanDefinition bean = beanFactory.getBeanDefinition(messageDrivenContainer);
+ Set dependsOn = new LinkedHashSet<>(asList(bean.getDependsOn()));
+ dependsOn.addAll(asList(transactionManagers));
+ bean.setDependsOn(StringUtils.toStringArray(dependsOn));
+ }
+ }
+
+ private void addDependencies(ConfigurableListableBeanFactory beanFactory, String type, Set dependsOn) {
+ dependsOn.addAll(asList(getBeanNamesForType(beanFactory, type)));
+ }
+
+ private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, String type) {
+ try {
+ return beanFactory.getBeanNamesForType(Class.forName(type), true, false);
+ }
+ catch (ClassNotFoundException | NoClassDefFoundError ex) {
+ // Ignore
+ }
+ return NO_BEANS;
+ }
+
+ private List asList(String[] array) {
+ return (array != null) ? Arrays.asList(array) : Collections.emptyList();
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosNonXADataSourceBeanMetadata.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosNonXADataSourceBeanMetadata.java
new file mode 100644
index 000000000..8412286f2
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosNonXADataSourceBeanMetadata.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.springframework.boot.jdbc.metadata.AbstractDataSourcePoolMetadata;
+
+import com.atomikos.jdbc.AtomikosNonXADataSourceBean;
+
+public class AtomikosNonXADataSourceBeanMetadata extends AbstractDataSourcePoolMetadata {
+
+
+ public AtomikosNonXADataSourceBeanMetadata(AtomikosNonXADataSourceBean dataSource) {
+ super(dataSource);
+ }
+
+
+ @Override
+ public Integer getActive() {
+ return getDataSource().poolTotalSize() - getDataSource().poolAvailableSize();
+ }
+
+ @Override
+ public Integer getMax() {
+ return getDataSource().getMaxPoolSize();
+ }
+
+ @Override
+ public Integer getMin() {
+ return getDataSource().getMinPoolSize();
+ }
+
+ @Override
+ public String getValidationQuery() {
+ return getDataSource().getTestQuery();
+ }
+
+ @Override
+ public Boolean getDefaultAutoCommit() {
+ return false;
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosProperties.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosProperties.java
new file mode 100644
index 000000000..eadfba2d5
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosProperties.java
@@ -0,0 +1,411 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import java.time.Duration;
+import java.util.Properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Bean friendly variant of
+ * Atomikos configuration
+ * properties. Allows for setter based configuration and is amiable to relaxed data
+ * binding.
+ * @see #asProperties()
+ */
+@ConfigurationProperties(prefix = "atomikos.properties")
+public class AtomikosProperties {
+
+ /**
+ * Transaction manager implementation that should be started.
+ */
+ private String service;
+
+ /**
+ * Maximum timeout that can be allowed for transactions.
+ */
+ private Duration maxTimeout;
+
+ /**
+ * Default timeout for JTA transactions.
+ */
+ private Duration defaultJtaTimeout;
+
+ /**
+ * Maximum number of active transactions.
+ */
+ private Integer maxActives;
+
+ /**
+ * Whether to enable disk logging.
+ */
+ private Boolean enableLogging;
+
+ /**
+ * The transaction manager's unique name. Defaults to the machine's IP address. If you
+ * plan to run more than one transaction manager against one database you must set
+ * this property to a unique value.
+ */
+ private String transactionManagerUniqueName;
+
+ /**
+ * Whether sub-transactions should be joined when possible.
+ */
+ private Boolean serialJtaTransactions;
+
+ /**
+ * Specify whether sub-transactions are allowed.
+ */
+ private Boolean allowSubTransactions;
+
+ /**
+ * Whether a VM shutdown should trigger forced shutdown of the transaction core.
+ */
+ private Boolean forceShutdownOnVmExit;
+
+ /**
+ * How long should normal shutdown (no-force) wait for transactions to complete.
+ */
+ private Long defaultMaxWaitTimeOnShutdown;
+
+ /**
+ * Transactions log file base name.
+ */
+ private String logBaseName;
+
+ /**
+ * Directory in which the log files should be stored. Defaults to the current working
+ * directory.
+ */
+ private String logBaseDir;
+
+ /**
+ * Interval between checkpoints, expressed as the number of log writes between two
+ * checkpoints. A checkpoint reduces the log file size at the expense of adding some
+ * overhead in the runtime.
+ */
+ private Long checkpointInterval;
+
+ private Boolean throwOnHeuristic;
+
+
+ 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,
+ * {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory} is the
+ * value you should set.
+ * @param service the service
+ */
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public String getService() {
+ return this.service;
+ }
+
+ /**
+ * Specifies the maximum timeout that can be allowed for transactions. Defaults to
+ * {@literal 300000}. This means that calls to UserTransaction.setTransactionTimeout()
+ * with a value higher than configured here will be max'ed to this value.
+ * @param maxTimeout the max timeout
+ */
+ public void setMaxTimeout(Duration maxTimeout) {
+ this.maxTimeout = maxTimeout;
+ }
+
+ public Duration getMaxTimeout() {
+ return this.maxTimeout;
+ }
+
+ /**
+ * The default timeout for JTA transactions (optional, defaults to {@literal 10000}
+ * ms).
+ * @param defaultJtaTimeout the default JTA timeout
+ */
+ public void setDefaultJtaTimeout(Duration defaultJtaTimeout) {
+ this.defaultJtaTimeout = defaultJtaTimeout;
+ }
+
+ public Duration getDefaultJtaTimeout() {
+ return this.defaultJtaTimeout;
+ }
+
+ /**
+ * Specifies the maximum number of active transactions. Defaults to {@literal 50}. A
+ * negative value means infinite amount. You will get an {@code IllegalStateException}
+ * with error message "Max number of active transactions reached" if you call
+ * {@code UserTransaction.begin()} while there are already n concurrent transactions
+ * running, n being this value.
+ * @param maxActives the max activities
+ */
+ public void setMaxActives(Integer maxActives) {
+ this.maxActives = maxActives;
+ }
+
+ public Integer getMaxActives() {
+ return this.maxActives;
+ }
+
+ /**
+ * Specifies if disk logging should be enabled or not. Defaults to true. It is useful
+ * for JUnit testing, or to profile code without seeing the transaction manager's
+ * activity as a hot spot but this should never be disabled on production or data
+ * integrity cannot be guaranteed.
+ * @param enableLogging if logging is enabled
+ */
+ public void setEnableLogging(Boolean enableLogging) {
+ this.enableLogging = enableLogging;
+ }
+
+ public Boolean isEnableLogging() {
+ return this.enableLogging;
+ }
+
+ /**
+ * Specifies the transaction manager's unique name. Defaults to the machine's IP
+ * address. If you plan to run more than one transaction manager against one database
+ * you must set this property to a unique value or you might run into duplicate
+ * transaction ID (XID) problems that can be quite subtle (example:
+ * {@literal https://fogbugz.atomikos.com/default.asp?community.6.2225.7}). If
+ * multiple instances need to use the same properties file then the easiest way to
+ * ensure uniqueness for this property is by referencing a system property specified
+ * at VM startup.
+ * @param uniqueName the unique name
+ */
+ public void setTransactionManagerUniqueName(String uniqueName) {
+ this.transactionManagerUniqueName = uniqueName;
+ }
+
+ public String getTransactionManagerUniqueName() {
+ return this.transactionManagerUniqueName;
+ }
+
+ /**
+ * Specifies if subtransactions should be joined when possible. Defaults to true. When
+ * false, no attempt to call {@code XAResource.start(TM_JOIN)} will be made for
+ * different but related subtransactions. This setting has no effect on resource
+ * access within one and the same transaction. If you don't use subtransactions then
+ * this setting can be ignored.
+ * @param serialJtaTransactions if serial JTA transactions are supported
+ */
+ public void setSerialJtaTransactions(Boolean serialJtaTransactions) {
+ this.serialJtaTransactions = serialJtaTransactions;
+ }
+
+ public Boolean isSerialJtaTransactions() {
+ 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.
+ * @param forceShutdownOnVmExit if VM shutdown should be forced
+ */
+ public void setForceShutdownOnVmExit(Boolean forceShutdownOnVmExit) {
+ this.forceShutdownOnVmExit = forceShutdownOnVmExit;
+ }
+
+ public Boolean isForceShutdownOnVmExit() {
+ return this.forceShutdownOnVmExit;
+ }
+
+ /**
+ * Specifies how long should a normal shutdown (no-force) wait for transactions to
+ * complete. Defaults to {@literal Long.MAX_VALUE}.
+ * @param defaultMaxWaitTimeOnShutdown the default max wait time on shutdown
+ */
+ public void setDefaultMaxWaitTimeOnShutdown(Long defaultMaxWaitTimeOnShutdown) {
+ this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown;
+ }
+
+ public Long getDefaultMaxWaitTimeOnShutdown() {
+ return this.defaultMaxWaitTimeOnShutdown;
+ }
+
+ /**
+ * Specifies the transactions log file base name. Defaults to {@literal tmlog}. The
+ * transactions logs are stored in files using this name appended with a number and
+ * the extension {@literal .log}. At checkpoint, a new transactions log file is
+ * created and the number is incremented.
+ * @param logBaseName the log base name
+ */
+ public void setLogBaseName(String logBaseName) {
+ this.logBaseName = logBaseName;
+ }
+
+ public String getLogBaseName() {
+ return this.logBaseName;
+ }
+
+ /**
+ * Specifies the directory in which the log files should be stored. Defaults to the
+ * current working directory. This directory should be a stable storage like a SAN,
+ * RAID or at least backed up location. The transactions logs files are as important
+ * as the data themselves to guarantee consistency in case of failures.
+ * @param logBaseDir the log base dir
+ */
+ public void setLogBaseDir(String logBaseDir) {
+ this.logBaseDir = logBaseDir;
+ }
+
+ public String getLogBaseDir() {
+ return this.logBaseDir;
+ }
+
+ /**
+ * Specifies the interval between checkpoints. A checkpoint reduces the log file size
+ * at the expense of adding some overhead in the runtime. Defaults to {@literal 500}.
+ * @param checkpointInterval the checkpoint interval
+ */
+ public void setCheckpointInterval(Long checkpointInterval) {
+ this.checkpointInterval = checkpointInterval;
+ }
+
+ public Long getCheckpointInterval() {
+ return this.checkpointInterval;
+ }
+
+
+ public Recovery getRecovery() {
+ return this.recovery;
+ }
+
+ public void setThrowOnHeuristic(Boolean throwOnHeuristic) {
+ this.throwOnHeuristic = throwOnHeuristic;
+ }
+
+ public Boolean isThrowOnHeuristic() {
+ return this.throwOnHeuristic;
+ }
+
+ /**
+ * Returns the properties as a {@link Properties} object that can be used with
+ * Atomikos.
+ * @return the properties
+ */
+ public Properties asProperties() {
+ Properties properties = new Properties();
+ set(properties, "service", getService());
+ set(properties, "max_timeout", getMaxTimeout());
+ set(properties, "default_jta_timeout", getDefaultJtaTimeout());
+ set(properties, "max_actives", getMaxActives());
+ 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, "default_max_wait_time_on_shutdown", getDefaultMaxWaitTimeOnShutdown());
+ set(properties, "log_base_name", getLogBaseName());
+ set(properties, "log_base_dir", getOrDeduceLogBaseDir());
+ set(properties, "checkpoint_interval", getCheckpointInterval());
+ 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());
+ set(properties, "throw_on_heuristic", isThrowOnHeuristic());
+ return properties;
+ }
+
+ private String getOrDeduceLogBaseDir() {
+ if(StringUtils.hasText(this.logBaseDir)) {
+ return this.logBaseDir;
+ }
+ return null;
+
+ }
+
+ private void set(Properties properties, String key, Object value) {
+ String id = "com.atomikos.icatch." + key;
+ if (value != null && !properties.containsKey(id)) {
+ properties.setProperty(id, asString(value));
+ }
+ }
+
+ private String asString(Object value) {
+ if (value instanceof Duration) {
+ return String.valueOf(((Duration) value).toMillis());
+ }
+ return value.toString();
+ }
+
+ /**
+ * Recovery specific settings.
+ */
+ public static class Recovery {
+
+ /**
+ * Delay after which recovery can cleanup pending ('orphaned') log entries.
+ */
+ private Duration forgetOrphanedLogEntriesDelay;
+
+ /**
+ * Delay between two recovery scans.
+ */
+ private Duration delay;
+
+ /**
+ * Number of retry attempts to commit the transaction before throwing an
+ * exception.
+ */
+ private Integer maxRetries;
+
+ /**
+ * Delay between retry attempts.
+ */
+ private Duration retryInterval;
+
+ public Duration getForgetOrphanedLogEntriesDelay() {
+ return this.forgetOrphanedLogEntriesDelay;
+ }
+
+ public void setForgetOrphanedLogEntriesDelay(Duration forgetOrphanedLogEntriesDelay) {
+ this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay;
+ }
+
+ public Duration getDelay() {
+ return this.delay;
+ }
+
+ public void setDelay(Duration delay) {
+ this.delay = delay;
+ }
+
+ public Integer getMaxRetries() {
+ return this.maxRetries;
+ }
+
+ public void setMaxRetries(Integer maxRetries) {
+ this.maxRetries = maxRetries;
+ }
+
+ public Duration getRetryInterval() {
+ return this.retryInterval;
+ }
+
+ public void setRetryInterval(Duration retryInterval) {
+ this.retryInterval = retryInterval;
+ }
+
+
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapper.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapper.java
new file mode 100644
index 000000000..6b5431c2c
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapper.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.XAConnectionFactory;
+
+import org.springframework.boot.jms.XAConnectionFactoryWrapper;
+
+/**
+ * {@link XAConnectionFactoryWrapper} that uses an {@link AtomikosConnectionFactoryBean}
+ * to wrap a {@link XAConnectionFactory}.
+ */
+public class AtomikosXAConnectionFactoryWrapper implements XAConnectionFactoryWrapper {
+
+ @Override
+ public ConnectionFactory wrapConnectionFactory(XAConnectionFactory connectionFactory) {
+ AtomikosConnectionFactoryBean bean = new AtomikosConnectionFactoryBean();
+ bean.setXaConnectionFactory(connectionFactory);
+ return bean;
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXADataSourceWrapper.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXADataSourceWrapper.java
new file mode 100644
index 000000000..7d2c02c4f
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/AtomikosXADataSourceWrapper.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import javax.sql.XADataSource;
+
+import org.springframework.boot.jdbc.XADataSourceWrapper;
+
+/**
+ * {@link XADataSourceWrapper} that uses an {@link AtomikosDataSourceBean} to wrap a
+ * {@link XADataSource}.
+ */
+public class AtomikosXADataSourceWrapper implements XADataSourceWrapper {
+
+ @Override
+ public AtomikosDataSourceBean wrapDataSource(XADataSource dataSource) throws Exception {
+ AtomikosDataSourceBean bean = new AtomikosDataSourceBean();
+ bean.setXaDataSource(dataSource);
+ return bean;
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/SpringJtaAtomikosProperties.java b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/SpringJtaAtomikosProperties.java
new file mode 100644
index 000000000..aa11902c5
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/java/com/atomikos/spring/SpringJtaAtomikosProperties.java
@@ -0,0 +1,412 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import java.time.Duration;
+import java.util.Properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.StringUtils;
+
+/**
+ * Bean friendly variant of
+ * Atomikos configuration
+ * properties. Allows for setter based configuration and is amiable to relaxed data
+ * binding.
+ * @see #asProperties()
+ */
+@ConfigurationProperties(prefix = "spring.jta.atomikos.properties")
+public class SpringJtaAtomikosProperties {
+
+ /**
+ * Transaction manager implementation that should be started.
+ */
+ private String service;
+
+ /**
+ * Maximum timeout that can be allowed for transactions.
+ */
+ private Duration maxTimeout;
+
+ /**
+ * Default timeout for JTA transactions.
+ */
+ private Duration defaultJtaTimeout;
+
+ /**
+ * Maximum number of active transactions.
+ */
+ private Integer maxActives;
+
+ /**
+ * Whether to enable disk logging.
+ */
+ private Boolean enableLogging;
+
+ /**
+ * The transaction manager's unique name. Defaults to the machine's IP address. If you
+ * plan to run more than one transaction manager against one database you must set
+ * this property to a unique value.
+ */
+ private String transactionManagerUniqueName;
+
+ /**
+ * Whether sub-transactions should be joined when possible.
+ */
+ private Boolean serialJtaTransactions;
+
+ /**
+ * Specify whether sub-transactions are allowed.
+ */
+ private Boolean allowSubTransactions;
+
+ /**
+ * Whether a VM shutdown should trigger forced shutdown of the transaction core.
+ */
+ private Boolean forceShutdownOnVmExit;
+
+ /**
+ * How long should normal shutdown (no-force) wait for transactions to complete.
+ */
+ private Long defaultMaxWaitTimeOnShutdown;
+
+ /**
+ * Transactions log file base name.
+ */
+ private String logBaseName;
+
+ /**
+ * Directory in which the log files should be stored. Defaults to the current working
+ * directory.
+ */
+ private String logBaseDir;
+
+ /**
+ * Interval between checkpoints, expressed as the number of log writes between two
+ * checkpoints. A checkpoint reduces the log file size at the expense of adding some
+ * overhead in the runtime.
+ */
+ private Long checkpointInterval;
+
+ private Boolean throwOnHeuristic;
+
+
+ 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,
+ * {@literal com.atomikos.icatch.standalone.UserTransactionServiceFactory} is the
+ * value you should set.
+ * @param service the service
+ */
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public String getService() {
+ return this.service;
+ }
+
+ /**
+ * Specifies the maximum timeout that can be allowed for transactions. Defaults to
+ * {@literal 300000}. This means that calls to UserTransaction.setTransactionTimeout()
+ * with a value higher than configured here will be max'ed to this value.
+ * @param maxTimeout the max timeout
+ */
+ public void setMaxTimeout(Duration maxTimeout) {
+ this.maxTimeout = maxTimeout;
+ }
+
+ public Duration getMaxTimeout() {
+ return this.maxTimeout;
+ }
+
+ /**
+ * The default timeout for JTA transactions (optional, defaults to {@literal 10000}
+ * ms).
+ * @param defaultJtaTimeout the default JTA timeout
+ */
+ public void setDefaultJtaTimeout(Duration defaultJtaTimeout) {
+ this.defaultJtaTimeout = defaultJtaTimeout;
+ }
+
+ public Duration getDefaultJtaTimeout() {
+ return this.defaultJtaTimeout;
+ }
+
+ /**
+ * Specifies the maximum number of active transactions. Defaults to {@literal 50}. A
+ * negative value means infinite amount. You will get an {@code IllegalStateException}
+ * with error message "Max number of active transactions reached" if you call
+ * {@code UserTransaction.begin()} while there are already n concurrent transactions
+ * running, n being this value.
+ * @param maxActives the max activities
+ */
+ public void setMaxActives(Integer maxActives) {
+ this.maxActives = maxActives;
+ }
+
+ public Integer getMaxActives() {
+ return this.maxActives;
+ }
+
+ /**
+ * Specifies if disk logging should be enabled or not. Defaults to true. It is useful
+ * for JUnit testing, or to profile code without seeing the transaction manager's
+ * activity as a hot spot but this should never be disabled on production or data
+ * integrity cannot be guaranteed.
+ * @param enableLogging if logging is enabled
+ */
+ public void setEnableLogging(Boolean enableLogging) {
+ this.enableLogging = enableLogging;
+ }
+
+ public Boolean isEnableLogging() {
+ return this.enableLogging;
+ }
+
+ /**
+ * Specifies the transaction manager's unique name. Defaults to the machine's IP
+ * address. If you plan to run more than one transaction manager against one database
+ * you must set this property to a unique value or you might run into duplicate
+ * transaction ID (XID) problems that can be quite subtle (example:
+ * {@literal https://fogbugz.atomikos.com/default.asp?community.6.2225.7}). If
+ * multiple instances need to use the same properties file then the easiest way to
+ * ensure uniqueness for this property is by referencing a system property specified
+ * at VM startup.
+ * @param uniqueName the unique name
+ */
+ public void setTransactionManagerUniqueName(String uniqueName) {
+ this.transactionManagerUniqueName = uniqueName;
+ }
+
+ public String getTransactionManagerUniqueName() {
+ return this.transactionManagerUniqueName;
+ }
+
+ /**
+ * Specifies if subtransactions should be joined when possible. Defaults to true. When
+ * false, no attempt to call {@code XAResource.start(TM_JOIN)} will be made for
+ * different but related subtransactions. This setting has no effect on resource
+ * access within one and the same transaction. If you don't use subtransactions then
+ * this setting can be ignored.
+ * @param serialJtaTransactions if serial JTA transactions are supported
+ */
+ public void setSerialJtaTransactions(Boolean serialJtaTransactions) {
+ this.serialJtaTransactions = serialJtaTransactions;
+ }
+
+ public Boolean isSerialJtaTransactions() {
+ 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.
+ * @param forceShutdownOnVmExit if VM shutdown should be forced
+ */
+ public void setForceShutdownOnVmExit(Boolean forceShutdownOnVmExit) {
+ this.forceShutdownOnVmExit = forceShutdownOnVmExit;
+ }
+
+ public Boolean isForceShutdownOnVmExit() {
+ return this.forceShutdownOnVmExit;
+ }
+
+ /**
+ * Specifies how long should a normal shutdown (no-force) wait for transactions to
+ * complete. Defaults to {@literal Long.MAX_VALUE}.
+ * @param defaultMaxWaitTimeOnShutdown the default max wait time on shutdown
+ */
+ public void setDefaultMaxWaitTimeOnShutdown(Long defaultMaxWaitTimeOnShutdown) {
+ this.defaultMaxWaitTimeOnShutdown = defaultMaxWaitTimeOnShutdown;
+ }
+
+ public Long getDefaultMaxWaitTimeOnShutdown() {
+ return this.defaultMaxWaitTimeOnShutdown;
+ }
+
+ /**
+ * Specifies the transactions log file base name. Defaults to {@literal tmlog}. The
+ * transactions logs are stored in files using this name appended with a number and
+ * the extension {@literal .log}. At checkpoint, a new transactions log file is
+ * created and the number is incremented.
+ * @param logBaseName the log base name
+ */
+ public void setLogBaseName(String logBaseName) {
+ this.logBaseName = logBaseName;
+ }
+
+ public String getLogBaseName() {
+ return this.logBaseName;
+ }
+
+ /**
+ * Specifies the directory in which the log files should be stored. Defaults to the
+ * current working directory. This directory should be a stable storage like a SAN,
+ * RAID or at least backed up location. The transactions logs files are as important
+ * as the data themselves to guarantee consistency in case of failures.
+ * @param logBaseDir the log base dir
+ */
+ public void setLogBaseDir(String logBaseDir) {
+ this.logBaseDir = logBaseDir;
+ }
+
+ public String getLogBaseDir() {
+ return this.logBaseDir;
+ }
+
+ /**
+ * Specifies the interval between checkpoints. A checkpoint reduces the log file size
+ * at the expense of adding some overhead in the runtime. Defaults to {@literal 500}.
+ * @param checkpointInterval the checkpoint interval
+ */
+ public void setCheckpointInterval(Long checkpointInterval) {
+ this.checkpointInterval = checkpointInterval;
+ }
+
+ public Long getCheckpointInterval() {
+ return this.checkpointInterval;
+ }
+
+
+ public Recovery getRecovery() {
+ return this.recovery;
+ }
+
+ public void setThrowOnHeuristic(Boolean throwOnHeuristic) {
+ this.throwOnHeuristic = throwOnHeuristic;
+ }
+
+ public Boolean isThrowOnHeuristic() {
+ return this.throwOnHeuristic;
+ }
+
+ /**
+ * Returns the properties as a {@link Properties} object that can be used with
+ * Atomikos.
+ * @return the properties
+ */
+ public Properties asProperties() {
+ Properties properties = new Properties();
+ set(properties, "service", getService());
+ set(properties, "max_timeout", getMaxTimeout());
+ set(properties, "default_jta_timeout", getDefaultJtaTimeout());
+ set(properties, "max_actives", getMaxActives());
+ 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, "default_max_wait_time_on_shutdown", getDefaultMaxWaitTimeOnShutdown());
+ set(properties, "log_base_name", getLogBaseName());
+ set(properties, "log_base_dir", getOrDeduceLogBaseDir());
+ set(properties, "checkpoint_interval", getCheckpointInterval());
+ 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());
+ set(properties, "throw_on_heuristic", isThrowOnHeuristic());
+ return properties;
+ }
+
+ private String getOrDeduceLogBaseDir() {
+ if(StringUtils.hasText(this.logBaseDir)) {
+ return this.logBaseDir;
+ }
+ return null;
+
+ }
+
+ private void set(Properties properties, String key, Object value) {
+ String id = "com.atomikos.icatch." + key;
+ if (value != null && !properties.containsKey(id)) {
+ properties.setProperty(id, asString(value));
+ }
+ }
+
+ private String asString(Object value) {
+ if (value instanceof Duration) {
+ return String.valueOf(((Duration) value).toMillis());
+ }
+ return value.toString();
+ }
+
+ /**
+ * Recovery specific settings.
+ */
+ public static class Recovery {
+
+ /**
+ * Delay after which recovery can cleanup pending ('orphaned') log entries.
+ */
+ private Duration forgetOrphanedLogEntriesDelay;
+
+ /**
+ * Delay between two recovery scans.
+ */
+ private Duration delay;
+
+ /**
+ * Number of retry attempts to commit the transaction before throwing an
+ * exception.
+ */
+ private Integer maxRetries;
+
+ /**
+ * Delay between retry attempts.
+ */
+ private Duration retryInterval;
+
+ public Duration getForgetOrphanedLogEntriesDelay() {
+ return this.forgetOrphanedLogEntriesDelay;
+ }
+
+ public void setForgetOrphanedLogEntriesDelay(Duration forgetOrphanedLogEntriesDelay) {
+ this.forgetOrphanedLogEntriesDelay = forgetOrphanedLogEntriesDelay;
+ }
+
+ public Duration getDelay() {
+ return this.delay;
+ }
+
+ public void setDelay(Duration delay) {
+ this.delay = delay;
+ }
+
+ public Integer getMaxRetries() {
+ return this.maxRetries;
+ }
+
+ public void setMaxRetries(Integer maxRetries) {
+ this.maxRetries = maxRetries;
+ }
+
+ public Duration getRetryInterval() {
+ return this.retryInterval;
+ }
+
+ public void setRetryInterval(Duration retryInterval) {
+ this.retryInterval = retryInterval;
+ }
+
+
+ }
+
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/public/spring-boot3/transactions-spring-boot3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..479dc84ea
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.atomikos.spring.AtomikosAutoConfiguration
\ No newline at end of file
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosAutoConfigurationJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosAutoConfigurationJUnit.java
new file mode 100644
index 000000000..21ff58e37
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosAutoConfigurationJUnit.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import jakarta.transaction.UserTransaction;
+
+import org.junit.Test;
+import org.springframework.boot.autoconfigure.transaction.TransactionProperties;
+import org.springframework.boot.jdbc.XADataSourceWrapper;
+import org.springframework.boot.jms.XAConnectionFactoryWrapper;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import com.atomikos.icatch.config.UserTransactionService;
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+/**
+ * Tests for {@link AtomikosAutoConfiguration}.
+ */
+public class AtomikosAutoConfigurationJUnit {
+
+ @Test
+ public void sanityCheck() {
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TransactionProperties.class,
+ AtomikosAutoConfiguration.class)) {
+ context.getBean(AtomikosProperties.class);
+ context.getBean(UserTransactionService.class);
+ context.getBean(UserTransactionManager.class);
+ context.getBean(UserTransaction.class);
+ context.getBean(XADataSourceWrapper.class);
+ context.getBean(XAConnectionFactoryWrapper.class);
+ context.getBean(AtomikosDependsOnBeanFactoryPostProcessor.class);
+ context.getBean(JtaTransactionManager.class);
+ }
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosConnectionFactoryBeanTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosConnectionFactoryBeanTestJUnit.java
new file mode 100644
index 000000000..b02cb0c91
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosConnectionFactoryBeanTestJUnit.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link AtomikosConnectionFactoryBean}.
+ */
+public class AtomikosConnectionFactoryBeanTestJUnit {
+
+ @Test
+ public void beanMethods() throws Exception {
+ MockAtomikosConnectionFactoryBean bean = spy(new MockAtomikosConnectionFactoryBean());
+ bean.setBeanName("bean");
+ bean.afterPropertiesSet();
+ assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
+ verify(bean).init();
+ verify(bean, never()).close();
+ bean.destroy();
+ verify(bean).close();
+ }
+
+ @SuppressWarnings("serial")
+ static class MockAtomikosConnectionFactoryBean extends AtomikosConnectionFactoryBean {
+
+ @Override
+ public synchronized void init() {
+ }
+
+ @Override
+ public synchronized void close() {
+ }
+
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDataSourceBeanTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDataSourceBeanTestJUnit.java
new file mode 100644
index 000000000..c03bc79ac
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDataSourceBeanTestJUnit.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link AtomikosDataSourceBean}.
+ */
+public class AtomikosDataSourceBeanTestJUnit {
+
+ @Test
+ public void beanMethods() throws Exception {
+ MockAtomikosDataSourceBean bean = spy(new MockAtomikosDataSourceBean());
+ bean.setBeanName("bean");
+ bean.afterPropertiesSet();
+ assertThat(bean.getUniqueResourceName()).isEqualTo("bean");
+ verify(bean).init();
+ verify(bean, never()).close();
+ bean.destroy();
+ verify(bean).close();
+ }
+
+ @SuppressWarnings("serial")
+ static class MockAtomikosDataSourceBean extends AtomikosDataSourceBean {
+
+ @Override
+ public synchronized void init() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessorTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessorTestJUnit.java
new file mode 100644
index 000000000..bf2523688
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosDependsOnBeanFactoryPostProcessorTestJUnit.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import jakarta.jms.ConnectionFactory;
+import javax.sql.DataSource;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+import com.atomikos.jms.extra.MessageDrivenContainer;
+import org.junit.Test;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AtomikosDependsOnBeanFactoryPostProcessor}.
+ */
+public class AtomikosDependsOnBeanFactoryPostProcessorTestJUnit {
+
+ private AnnotationConfigApplicationContext context;
+
+ @Test
+ public void setsDependsOn() {
+ this.context = new AnnotationConfigApplicationContext(Config.class);
+ assertDependsOn("dataSource");
+ assertDependsOn("connectionFactory");
+ assertDependsOn("userTransactionManager", "dataSource", "connectionFactory");
+ assertDependsOn("messageDrivenContainer", "userTransactionManager");
+ this.context.close();
+ }
+
+ private void assertDependsOn(String bean, String... expected) {
+ BeanDefinition definition = this.context.getBeanDefinition(bean);
+ if (definition.getDependsOn() == null) {
+ assertThat(expected).as("No dependsOn expected for " + bean).isEmpty();
+ return;
+ }
+ HashSet dependsOn = new HashSet<>(Arrays.asList(definition.getDependsOn()));
+ assertThat(dependsOn).isEqualTo(new HashSet<>(Arrays.asList(expected)));
+ }
+
+ @Configuration(proxyBeanMethods = false)
+ static class Config {
+
+ @Bean
+ DataSource dataSource() {
+ return mock(DataSource.class);
+ }
+
+ @Bean
+ ConnectionFactory connectionFactory() {
+ return mock(ConnectionFactory.class);
+ }
+
+ @Bean
+ UserTransactionManager userTransactionManager() {
+ return mock(UserTransactionManager.class);
+ }
+
+ @Bean
+ MessageDrivenContainer messageDrivenContainer() {
+ return mock(MessageDrivenContainer.class);
+ }
+
+ @Bean
+ static AtomikosDependsOnBeanFactoryPostProcessor atomikosPostProcessor() {
+ return new AtomikosDependsOnBeanFactoryPostProcessor();
+ }
+
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosPropertiesTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosPropertiesTestJUnit.java
new file mode 100644
index 000000000..6add27e6f
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosPropertiesTestJUnit.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link AtomikosProperties}.
+ */
+public class AtomikosPropertiesTestJUnit {
+
+ private AtomikosProperties properties = new AtomikosProperties();
+
+ @Test
+ public void testProperties() {
+ this.properties.setService("service");
+ this.properties.setMaxTimeout(Duration.ofMillis(1));
+ this.properties.setDefaultJtaTimeout(Duration.ofMillis(2));
+ this.properties.setMaxActives(3);
+ this.properties.setEnableLogging(true);
+ this.properties.setTransactionManagerUniqueName("uniqueName");
+ this.properties.setSerialJtaTransactions(true);
+ this.properties.setAllowSubTransactions(false);
+ this.properties.setForceShutdownOnVmExit(true);
+ this.properties.setDefaultMaxWaitTimeOnShutdown(20l);
+ this.properties.setLogBaseName("logBaseName");
+ this.properties.setLogBaseDir("logBaseDir");
+ this.properties.setCheckpointInterval(4l);
+ this.properties.getRecovery().setForgetOrphanedLogEntriesDelay(Duration.ofMillis(2000));
+ this.properties.getRecovery().setDelay(Duration.ofMillis(3000));
+ this.properties.getRecovery().setMaxRetries(10);
+ this.properties.getRecovery().setRetryInterval(Duration.ofMillis(4000));
+ this.properties.setThrowOnHeuristic(true);
+ assertThat(this.properties.asProperties().size()).isEqualTo(18);
+ assertProperty("com.atomikos.icatch.service", "service");
+ assertProperty("com.atomikos.icatch.max_timeout", "1");
+ assertProperty("com.atomikos.icatch.default_jta_timeout", "2");
+ assertProperty("com.atomikos.icatch.max_actives", "3");
+ 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.default_max_wait_time_on_shutdown", "20");
+ assertProperty("com.atomikos.icatch.log_base_name", "logBaseName");
+ assertProperty("com.atomikos.icatch.log_base_dir", "logBaseDir");
+ assertProperty("com.atomikos.icatch.checkpoint_interval", "4");
+ 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");
+ assertProperty("com.atomikos.icatch.throw_on_heuristic", "true");
+ }
+
+ @Test
+ public void testDefaultProperties() {
+ Properties properties = this.properties.asProperties();
+ List keys = new ArrayList<>();
+ keys.add("com.atomikos.icatch.max_timeout");
+ keys.add("com.atomikos.icatch.default_jta_timeout");
+ keys.add("com.atomikos.icatch.max_actives");
+ keys.add("com.atomikos.icatch.enable_logging");
+ keys.add("com.atomikos.icatch.serial_jta_transactions");
+ keys.add("com.atomikos.icatch.allow_subtransactions");
+ keys.add("com.atomikos.icatch.force_shutdown_on_vm_exit");
+ keys.add("com.atomikos.icatch.default_max_wait_time_on_shutdown");
+ keys.add("com.atomikos.icatch.log_base_name");
+ keys.add("com.atomikos.icatch.checkpoint_interval");
+ keys.add("com.atomikos.icatch.forget_orphaned_log_entries_delay");
+ keys.add("com.atomikos.icatch.oltp_max_retries");
+ keys.add("com.atomikos.icatch.oltp_retry_interval");
+ keys.add("com.atomikos.icatch.throw_on_heuristic");
+ assertThat(properties).isEmpty();
+ }
+
+ private void assertProperty(String key, String value) {
+ assertThat(this.properties.asProperties().getProperty(key)).isEqualTo(value);
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapperTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapperTestJUnit.java
new file mode 100644
index 000000000..00422f957
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXAConnectionFactoryWrapperTestJUnit.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import jakarta.jms.ConnectionFactory;
+import jakarta.jms.XAConnectionFactory;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AtomikosXAConnectionFactoryWrapper}.
+ */
+public class AtomikosXAConnectionFactoryWrapperTestJUnit {
+
+ @Test
+ public void wrap() {
+ XAConnectionFactory connectionFactory = mock(XAConnectionFactory.class);
+ AtomikosXAConnectionFactoryWrapper wrapper = new AtomikosXAConnectionFactoryWrapper();
+ ConnectionFactory wrapped = wrapper.wrapConnectionFactory(connectionFactory);
+ assertThat(wrapped).isInstanceOf(AtomikosConnectionFactoryBean.class);
+ assertThat(((AtomikosConnectionFactoryBean) wrapped).getXaConnectionFactory()).isSameAs(connectionFactory);
+ }
+
+}
diff --git a/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXADataSourceWrapperTestJUnit.java b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXADataSourceWrapperTestJUnit.java
new file mode 100644
index 000000000..bff614b58
--- /dev/null
+++ b/public/spring-boot3/transactions-spring-boot3/src/test/java/com/atomikos/spring/AtomikosXADataSourceWrapperTestJUnit.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.spring;
+
+import javax.sql.DataSource;
+import javax.sql.XADataSource;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link AtomikosXADataSourceWrapper}.
+ */
+public class AtomikosXADataSourceWrapperTestJUnit {
+
+ @Test
+ public void wrap() throws Exception {
+ XADataSource dataSource = mock(XADataSource.class);
+ AtomikosXADataSourceWrapper wrapper = new AtomikosXADataSourceWrapper();
+ DataSource wrapped = wrapper.wrapDataSource(dataSource);
+ assertThat(wrapped).isInstanceOf(AtomikosDataSourceBean.class);
+ assertThat(((AtomikosDataSourceBean) wrapped).getXaDataSource()).isSameAs(dataSource);
+ }
+
+}
diff --git a/public/transactions-api/pom.xml b/public/transactions-api/pom.xml
new file mode 100644
index 000000000..9d65032ae
--- /dev/null
+++ b/public/transactions-api/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ transactions-api
+ Transactions API
+
+
+
+ jdepend
+ jdepend
+ 2.9.1
+ test
+
+
+
+
+
+
diff --git a/public/transactions-api/src/main/java/com/atomikos/datasource/RecoverableResource.java b/public/transactions-api/src/main/java/com/atomikos/datasource/RecoverableResource.java
new file mode 100644
index 000000000..81f8b4161
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/datasource/RecoverableResource.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.datasource;
+
+import java.util.Collection;
+
+import com.atomikos.icatch.RecoveryService;
+import com.atomikos.recovery.PendingTransactionRecord;
+
+
+ /**
+ * A Recoverable Resource is the abstraction of a resource
+ * that supports recoverable work (i.e., that supports Participant
+ * instances). A recoverable resource is invoked at recovery time by the transaction core.
+ */
+
+public interface RecoverableResource
+{
+
+ /**
+ * Initializes this resource with the recovery service.
+ * This method is called by the transaction service during
+ * intialization of the transaction service or when the
+ * resource is added, whichever comes last. If the
+ * resource wants to recover, it should subsequently
+ * ask the recoveryService
+ * to do so.
+ * @param recoveryService The recovery service. This instance
+ * can be used by the resource to ask recovery from the
+ * transaction engine.
+ * @throws ResourceException On errors.
+ */
+
+ void setRecoveryService ( RecoveryService recoveryService )
+ throws ResourceException;
+
+
+ /**
+ * Closes the resource for shutdown.
+ * This notifies the resource that it is no longer needed.
+ */
+
+ void close() throws ResourceException;
+
+ /**
+ * Gets the name of the resource. Names should be unique
+ * within one TM domain.
+ * @return String The name.
+ */
+
+ String getName();
+
+ /**
+ * Tests if a resource is the same as another one.
+ */
+
+ boolean isSameRM(RecoverableResource res)
+ throws ResourceException;
+
+ /**
+ * Tests if the resource is closed.
+ * @return boolean True if the resource is closed.
+ */
+ boolean isClosed();
+
+
+ /**
+ * Instructs the resource to recover.
+ *
+ * @param startOfRecoveryScan
+ * @param expiredCommittingCoordinators
+ * @param indoubtForeignCoordinatorsToKeep
+ * @return True if this resource has no more participants for the supplied expiredCommittingCoordinators.
+ */
+ boolean recover(long startOfRecoveryScan, Collection expiredCommittingCoordinators, Collection indoubtForeignCoordinatorsToKeep);
+
+ /**
+ *
+ * @return True if this resource has pending participants from the last recovery scan.
+ */
+ boolean hasPendingParticipantsFromLastRecoveryScan();
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceException.java b/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceException.java
new file mode 100644
index 000000000..0ce545c7a
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.datasource;
+
+
+/**
+* Exception on the level of the resource manager.
+* Contains more detailed info of actual underlying exception.
+*/
+
+public class ResourceException extends com.atomikos.icatch.SysException
+{
+
+ public ResourceException(String msg){
+ super(msg);
+
+ }
+
+ public ResourceException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
+
diff --git a/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceTransaction.java b/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceTransaction.java
new file mode 100644
index 000000000..2783c45dc
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/datasource/ResourceTransaction.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.datasource;
+
+
+/**
+ * The notion of a local transaction executed on a resource.
+ * Serves as a handle towards the transaction management module.
+ */
+
+public interface ResourceTransaction
+{
+
+
+ /**
+ * Suspends the work, so that underlying resources can
+ * be used for a next (sibling) invocation.
+ *
+ */
+
+ void suspend() throws IllegalStateException,ResourceException;
+
+ /**
+ * Resumes a previously suspended tx.
+ *
+ */
+
+ void resume() throws IllegalStateException,ResourceException;
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/datasource/TransactionalResource.java b/public/transactions-api/src/main/java/com/atomikos/datasource/TransactionalResource.java
new file mode 100644
index 000000000..225ef343f
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/datasource/TransactionalResource.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.datasource;
+
+import com.atomikos.icatch.CompositeTransaction;
+
+/**
+ * Represents the abstraction of a data source that
+ * supports transactions and recovery.
+ */
+
+public interface TransactionalResource extends RecoverableResource
+{
+ /**
+ * Gets or creates a ResourceTransaction. This instructs the resource
+ * to internally start a context for a new transaction.
+ * If the resource decides to return a new instance, it should
+ * also make sure that before returning, the new resource
+ * transaction is registered as a participant for the supplied
+ * composite transaction.
+ *
+ */
+
+ ResourceTransaction
+ getResourceTransaction ( CompositeTransaction compositeTransaction )
+ throws IllegalStateException, ResourceException;
+
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeCoordinator.java b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeCoordinator.java
new file mode 100644
index 000000000..5ebded6bb
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeCoordinator.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * Represents the per-server work done
+ * as part of the same global (root) transaction scope.
+ */
+
+public interface CompositeCoordinator
+{
+
+ /**
+ * @return The coordinatorId.
+ */
+
+ String getCoordinatorId();
+
+ /**
+ *
+ * @return The top-level root's coordinatorId.
+ */
+ String getRootId();
+
+
+ /**
+ *
+ *@return RecoveryCoordinator.
+ */
+
+ RecoveryCoordinator getRecoveryCoordinator();
+
+}
+
+
+
+
+
+
+
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransaction.java b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransaction.java
new file mode 100644
index 000000000..7173da5ba
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransaction.java
@@ -0,0 +1,263 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+
+package com.atomikos.icatch;
+
+import java.util.Properties;
+import java.util.Stack;
+
+import com.atomikos.recovery.TxState;
+
+/**
+ * Represents a nested part of a global
+ * composite transaction. Each invocation of a server is
+ * represented by an instance of this type. For transaction
+ * proxies (instances that represent non-local transactions),
+ * all non-trivial methods are allowed to generate
+ * an exception of type UnsupportedOperationException. The only
+ * methods that always work for all instances are the simple
+ * getters and test methods.
+ *
+ *
+ *
+ */
+
+public interface CompositeTransaction
+{
+
+ /**
+ * Gets the current state.
+ * @return Object One of the state constants.
+ * @see TxState
+ */
+ TxState getState();
+
+ /**
+ *
+ * @return boolean True if this is the root transaction,
+ * i.e. the first transaction created in a (possibly distributed) hierarchy.
+ */
+
+ boolean isRoot();
+
+ /**
+ * @return Stack A stack of ancestors, bottom one is the root.
+ */
+
+ Stack getLineage();
+
+
+ /**
+ *
+ * @return String The tid for the tx.
+ */
+
+ String getTid();
+
+ /**
+ *
+ * @param otherCompositeTransaction
+ *
+ * @return boolean True if this instance is an ancestor of the supplied transaction.
+ */
+
+ boolean isAncestorOf( CompositeTransaction otherCompositeTransaction );
+
+ /**
+ * @param otherCompositeTransaction
+ *
+ * @return boolean True if this instance is a descendant of the other instance.
+ */
+
+ boolean isDescendantOf( CompositeTransaction otherCompositeTransaction );
+
+
+ /**
+ * @param otherCompositeTransaction
+ *
+ * @return True if related. That is: if both share the same root transaction.
+ */
+
+ boolean isRelatedTransaction ( CompositeTransaction otherCompositeTransaction );
+
+
+ /**
+ * @param otherCompositeTransaction
+ * @return True if both are the same.
+ */
+
+ boolean isSameTransaction ( CompositeTransaction otherCompositeTransaction );
+
+ /**
+ * @return CompositeCoordinator
+ */
+
+ CompositeCoordinator getCompositeCoordinator()
+ throws SysException;
+
+
+
+ /**
+ * @param participant
+ *
+ * @return RecoveryCoordinator Whom to ask for indoubt timeout resolution.
+ */
+
+ RecoveryCoordinator addParticipant ( Participant participant )
+ throws SysException,
+ java.lang.IllegalStateException;
+
+
+ /**
+ *
+ * @param sync
+ * @throws IllegalStateException
+ * @throws SysException
+ */
+
+ void registerSynchronization(Synchronization sync)
+ throws
+ IllegalStateException,
+ SysException;
+
+
+
+ /**
+ * Resources that support lock inheritance can use this feature
+ * to be notified whenever a lock should be inherited.
+ *
+ * @param subtxaware
+ * @throws SysException
+ * @throws java.lang.IllegalStateException
+ */
+
+ void addSubTxAwareParticipant( SubTxAwareParticipant subtxaware )
+ throws SysException,
+ java.lang.IllegalStateException;
+
+
+ /**
+ * Serial mode is an optimized way for lock inheritance:
+ * no locks among related transactions are necessary if all related
+ * transactions are executed serially with respect to each other.
+ * The serial property is set by the root transaction and is
+ * propagated to all its subtransactions.
+ *
+ * @return
+ */
+ boolean isSerial();
+
+ /**
+ *
+ * @return boolean True if started in the local VM.
+ * For imported transactions, this is false.
+ */
+
+ boolean isLocal ();
+
+
+ /**
+ *
+ * @return The extent.
+ */
+
+ Extent getExtent();
+
+
+ /**
+ * @return long The transaction timeout in millis.
+ */
+
+ long getTimeout();
+
+ /**
+ * Marks the transaction so that the only possible
+ * termination is rollback.
+ *
+ */
+
+ void setRollbackOnly();
+
+ /**
+ * Commits the composite transaction.
+ *
+ * @exception HeurMixedException On heuristic mixed outcome.
+ * @exception SysException For unexpected failures.
+ * @exception SecurityException If calling thread does not have
+ * right to commit.
+ * @exception HeurHazardException In case of heuristic hazard.
+ * @exception RollbackException If the transaction was rolled back.
+ */
+
+ void commit()
+ throws
+ HeurMixedException,
+ HeurHazardException,
+ SysException,java.lang.SecurityException,
+ RollbackException;
+
+
+
+ /**
+ * Rollback of the current transaction.
+ * @exception IllegalStateException If prepared or inactive.
+ * @exception SysException If unexpected error.
+ */
+
+ void rollback()
+ throws IllegalStateException, SysException;
+
+
+ /**
+ * Sets metadata property information on the transaction object.
+ * Once set, metadata properties can't be overwritten. Properties
+ * are inherited by all subtransactions created after the set.
+ * Properties may or may not be propagated along with
+ * exported transactions (depending on the protocol).
+ *
+ * @param name
+ * @param value
+ */
+
+ void setProperty ( String name , String value );
+
+ /**
+ * Gets the specified metadata property.
+ * @param name The name of the property.
+ * @return The property, or null if not set.
+ */
+
+ String getProperty ( String name );
+
+ /**
+ * Gets all properties of this instance.
+ * @return The (cloned) properties of this transaction.
+ */
+
+ Properties getProperties();
+
+ CompositeTransaction createSubTransaction();
+
+ /**
+ *Set serial mode for root.
+ *This only works on the root itself, and can not be undone.
+ *After this, no parallel calls are allowed in any descendant.
+ *@exception IllegalStateException If called for non-root tx.
+ *@exception SysException For unexpected errors.
+ */
+
+ void setSerial() throws IllegalStateException, SysException;
+
+}
+
+
+
+
+
+
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransactionManager.java b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransactionManager.java
new file mode 100644
index 000000000..7bc530c71
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/CompositeTransactionManager.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * This interface outlines the API for managing composite transactions
+ * in the local VM.
+ */
+
+public interface CompositeTransactionManager
+{
+ /**
+ * Starts a new (sub)transaction (not an activity) for the current thread.
+ * Associates the current thread with that instance.
+ *
+ * NOTE: subtransactions should not be mixed: either each subtransaction is
+ * an activity, or not (default). Use suspend/resume if mixed models are necessary:
+ * for instance, if you want to create a normal transaction within an activity, then
+ * suspend the activity first before starting the transaction. Afterwards, resume the
+ * activity.
+ *
+ * @param Timeout (in millis) for the transaction.
+ *
+ * @return CompositeTransaction The new instance.
+ * @exception SysException Unexpected error.
+ * @exception IllegalStateException If there is an existing transaction that is
+ * an activity instead of a classical transaction.
+ */
+
+ CompositeTransaction createCompositeTransaction ( long timeout )
+ throws SysException, IllegalStateException;
+
+ /**
+ * @return CompositeTransaction The instance for the current thread, null if none.
+ *
+ * @exception SysException On unexpected failure.
+ */
+
+ CompositeTransaction getCompositeTransaction () throws SysException;
+
+ /**
+ * Gets the composite transaction with the given id.
+ * This method is useful e.g. for retrieving a suspended
+ * transaction by its id.
+ *
+ * @param tid The id of the transaction.
+ * @return CompositeTransaction The transaction with the given id,
+ * or null if not found.
+ * @exception SysException Unexpected failure.
+ */
+
+ CompositeTransaction getCompositeTransaction ( String tid )
+ throws SysException;
+
+ /**
+ * Re-maps the calling thread to the given transaction.
+ *
+ * @param compositeTransaction
+ * @exception IllegalStateException If this thread has a transaction context already.
+ * @exception SysException
+ */
+
+ void resume ( CompositeTransaction compositeTransaction )
+ throws IllegalStateException, SysException;
+
+ /**
+ * Suspends the transaction context for the current thread.
+ * This method suspends the entire transaction tree, including any parent transactions.
+ *
+ * @return CompositeTransaction The transaction for the current thread.
+ *
+ * @exception SysException
+ */
+
+ CompositeTransaction suspend() throws SysException ;
+
+ /**
+ * Recreate a composite transaction based on an imported context. Needed by
+ * the application's communication layer.
+ *
+ * @param context
+ * The propagationcontext.
+ *
+ * @return CompositeTransaction The recreated local instance.
+ * @exception SysException
+ * Failure.
+ */
+
+ CompositeTransaction recreateCompositeTransaction(Propagation propagation);
+
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/ExportingTransactionManager.java b/public/transactions-api/src/main/java/com/atomikos/icatch/ExportingTransactionManager.java
new file mode 100644
index 000000000..b2af58c67
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/ExportingTransactionManager.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * An interface for a TM that allows outgoing remote calls to be transactional.
+ */
+
+ public interface ExportingTransactionManager
+ {
+ /**
+ * Gets the propagation info of the transaction for the calling thread.
+ * Should be called before doing the remote call.
+ *
+ * @return Propagation The propagation for the current thread's transaction.
+ *
+ * @throws IllegalStateException If no such transaction exists, e.g. after a prior rollback.
+ *
+ */
+
+ Propagation getPropagation() throws SysException, IllegalStateException;
+
+ /**
+ * Should be called after call returns successfully:
+ * adds the extent of the call to the current transaction.
+ *
+ * If a remote call has failed, this method should NOT be called.
+ *
+ * @param extent The extent of the call.
+ *
+ * @throws IllegalArgumentException If the format of the supplied extent is not recognized.
+ * @throws RollbackException If the current transaction has already rolled back.
+ */
+
+
+ void addExtent(Extent extent) throws SysException, IllegalArgumentException, RollbackException;
+ }
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/Extent.java b/public/transactions-api/src/main/java/com/atomikos/icatch/Extent.java
new file mode 100644
index 000000000..d65364c1f
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/Extent.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ *
+ *
+ * The extent carries the information about the 'size' of a propagation
+ * after it returns: the directly and indirectly invoked servers, and the orphan
+ * detection information for those.
+ *
+ * This interface is a system interface; it should not be handled by application
+ * level code (besides shipping it around via toString()).
+ *
+ * This class (and its parsing) represent the minimum information required for
+ * transactions to work across remoting calls. All values are required.
+ *
+ * In addition, we are liberal in parsing: any additional, unknown values are ignored -
+ * so future releases can add extra, optional properties and still work with
+ * installations of this release.
+ */
+
+public class Extent {
+
+ /**
+ * Major version indicator. Only change this for future releases
+ * that add incompatible changes such as adding/removing required properties
+ * and/or changing the semantics of existing properties. As long as optional properties
+ * are added/removed, the version can stay the same.
+ */
+
+ public static final String VERSION ="2019";
+
+ private final Map participants = new Hashtable();
+ private boolean queried = false;
+ private final Stack directs = new Stack();
+ private String parentTransactionId; //null for root
+
+ public Extent() {
+ }
+
+ public Extent (String parentTransactionId) {
+ this.parentTransactionId = parentTransactionId;
+ }
+
+ /**
+ * Creates a new extent with the supplied extent's participants as indirect participants only.
+ * Intended for use at the importing / server end, when returning an extent to the client.
+ *
+ * The resulting extent must then still be completed with a direct participant representing this JVM for 2-phase commit.
+ *
+ * @param extent
+ */
+ public Extent(Extent extent) {
+ this.parentTransactionId = extent.getParentTransactionId();
+ addRemoteParticipants(extent.participants);
+ }
+
+ public String getParentTransactionId() {
+ return parentTransactionId;
+ }
+
+ public void addRemoteParticipants(Map participants)
+ throws IllegalStateException, SysException {
+ if (participants == null) {
+ return;
+ }
+ Set parts = participants.keySet();
+ for (String participant : parts) {
+ Integer count = this.participants.get(participant);
+ if (count == null) {
+ count = 0;
+ }
+
+ Integer cnt = participants.get(participant);
+ count = count.intValue() + cnt.intValue();
+
+ this.participants.put(participant, count);
+ // NOTE: this will replace the old participant, and if
+ // it is a proxy then the buffered heuristic msgs will
+ // also be replaced. This loses info if multiple PARALLEL calls
+ // went to the same FIRST-ORDER server (i.e., directly invoked).
+ // Never mind, though: it is considered bad practice
+ // to execute parallel calls if they might act on the same
+ // data. This is the case if they go to the same directly
+ // invoked server.
+ }
+ }
+
+ /**
+ * @return Map Mapping URIs of remote participants (directly or indirectly invoked)
+ * to Integer counts that represent the number of invocations detected by each participant.
+ */
+
+ public Map getRemoteParticipants()
+ {
+ queried = true;
+ return new HashMap(participants);
+ }
+
+
+ /**
+ *
+ * @return Stack A stack of direct participants. Direct participants
+ * are those that need to be added to the client TM's two-phase
+ * commit set.
+ *
+ * NOTE: If a participant occurs in the direct participant set,
+ * it will also be part of the remote set.
+ */
+
+
+ @SuppressWarnings("unchecked")
+ public Stack getParticipants ()
+ {
+ queried = true;
+ return (Stack) directs.clone();
+ }
+
+ /**
+ * Adds a participant to the extent.
+ * This method is called at the server side, in order to add the work done
+ * to the two-phase commit set of the calling (client) side, as well as to
+ * make sure that orphan information is propagated through the system.
+ *
+ * @param participant This instance will
+ *be added to the indirect as well as to the direct participant set.
+ *
+ * @param count The number of invocations detected by the adding client.
+ * @throws IllegalStateException If no longer allowed.
+ * @throws SysException
+ */
+
+ public synchronized void add(Participant participant, int count)
+ throws SysException, IllegalStateException {
+ Hashtable table = new Hashtable();
+ table.put(participant.getURI(), count);
+ addRemoteParticipants(table);
+ directs.push(participant);
+ }
+
+ /**
+ * Merges another extent into this one.
+ *
+ *@param extent The extent to add.
+ *
+ *@throws IllegalStateException If no longer allowed.
+ *@throws SysException
+ */
+
+ public synchronized void add ( Extent extent )
+ throws IllegalStateException, SysException
+ {
+ if (queried) throw new IllegalStateException("Adding extent no longer allowed");
+ addRemoteParticipants(extent.getRemoteParticipants());
+ Enumeration enumm = extent.getParticipants().elements();
+ while (enumm.hasMoreElements()) {
+ Participant part = enumm.nextElement();
+ directs.push(part);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer ret = new StringBuffer();
+ ret.append("version=").append(VERSION).append(",");
+ String delimiter = "";
+ if (parentTransactionId != null) { //should not be null but we never know
+ ret.append("parent=").append(parentTransactionId);
+ delimiter = ",";
+ }
+ Set alreadyAdded = new HashSet();
+ for (Participant p : directs) {
+ ret.append(delimiter).append("uri=").append(p.getURI()).append(",").
+ append("responseCount=").append(participants.get(p.getURI())).append(",").
+ append("direct=").append("true");
+ delimiter = ",";
+ alreadyAdded.add(p.getURI());
+ }
+ for (String pUri : participants.keySet()) {
+ if (!alreadyAdded.contains(pUri)) {
+ ret.append(delimiter).
+ append("uri=").append(pUri).append(",").
+ append("responseCount=").append(participants.get(pUri)).append(",").
+ append("direct=").append("false");
+ }
+ }
+ return ret.toString();
+ }
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/HeurCommitException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurCommitException.java
new file mode 100644
index 000000000..28631b04f
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurCommitException.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+/**
+ * Exception signaling heuristic commit.
+ */
+
+public class HeurCommitException extends HeuristicException {
+
+ private static final long serialVersionUID = 1L;
+
+ public HeurCommitException() {
+ super("Heuristic Commit Exception");
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/HeurHazardException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurHazardException.java
new file mode 100644
index 000000000..17d3a05c7
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurHazardException.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+/**
+ * Exception signaling that two-phase commit was not acknowledged by some
+ * participants.
+ */
+
+public class HeurHazardException extends HeuristicException {
+
+ private static final long serialVersionUID = 1L;
+
+ public HeurHazardException() {
+ super("Heuristic Hazard Exception");
+ }
+
+ public HeurHazardException(String msg) {
+ super(msg);
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/HeurMixedException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurMixedException.java
new file mode 100644
index 000000000..abcd6579c
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurMixedException.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+
+//
+//Revision 1.2 2001/03/01 19:26:57 pardon
+//Added more.
+//
+//Revision 1.1 2001/02/21 19:51:23 pardon
+//Redesign!
+//
+
+package com.atomikos.icatch;
+
+
+
+/**
+ * An exception signaling that some participants
+ * have committed whereas others performed a rollback.
+ */
+
+public class HeurMixedException extends HeuristicException
+{
+ private static final long serialVersionUID = 1L;
+
+ public HeurMixedException ()
+ {
+ super("Heuristic Mixed Exception");
+ }
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/HeurRollbackException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurRollbackException.java
new file mode 100644
index 000000000..56fb650ea
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/HeurRollbackException.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+/**
+ * An exception signaling that the transaction's work has been rolled back
+ * heuristically.
+ */
+
+public class HeurRollbackException extends HeuristicException {
+
+ private static final long serialVersionUID = 1L;
+
+ public HeurRollbackException() {
+ super("Heuristic Rollback Exception");
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/HeuristicException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/HeuristicException.java
new file mode 100644
index 000000000..1f5429574
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/HeuristicException.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+ /**
+ * Common superclass for heuristics.
+ */
+
+public class HeuristicException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public HeuristicException(String message) {
+ super(message);
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/ImportingTransactionManager.java b/public/transactions-api/src/main/java/com/atomikos/icatch/ImportingTransactionManager.java
new file mode 100644
index 000000000..fdb80138a
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/ImportingTransactionManager.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * Represents a handle to import a transaction from an incoming request,
+ * so that the work in this VM becomes part of the request's commit or rollback.
+ */
+
+ public interface ImportingTransactionManager
+ {
+ /**
+ * Imports the transaction propagation obtained from an incoming request.
+ *
+ * @param propagation The propagation (transaction context).
+ *
+ * @return CompositeTransaction The locally created transaction instance that takes part in the global commit/rollback.
+ * This instance will also be mapped to the calling thread.
+ *
+ * @throws IllegalArgumentException If the supplied propagation cannot be understood.
+ */
+
+ CompositeTransaction importTransaction(Propagation propagation) throws IllegalArgumentException, SysException;
+
+
+
+ /**
+ * Signals that the incoming request is done processing, in order to
+ * terminate the transaction context for the calling thread.
+ *
+ * @param commit True if the invocation had no errors: commit the local transaction
+ * but make its final outcome subject to the request's commit/rollback.
+ *
+ * @return Extent The extent to return to remote client (i.e., the participant information for two-phase commit)
+ * along with the parent's ID for asynchronous calls.
+ *
+ *
+ * @throws RollbackException If no transaction exists, e.g. if it has been rolled back already.
+ *
+ *
+ *
+ */
+
+ Extent terminated(boolean commit) throws SysException, RollbackException;
+
+
+
+
+
+ }
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/OrderedLifecycleComponent.java b/public/transactions-api/src/main/java/com/atomikos/icatch/OrderedLifecycleComponent.java
new file mode 100644
index 000000000..128901d8d
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/OrderedLifecycleComponent.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+ /**
+ * Marker interface for system components whose order of init
+ * and close is important for correct behavior of the
+ * transaction system. The knowledge of ordering is supposed to
+ * be present elsewhere - in the system configuration.
+ */
+
+public interface OrderedLifecycleComponent {
+
+ /**
+ *
+ * @throws Exception Implementations are free to narrow the exception
+ * or even not throw anything.
+ */
+
+ void init() throws Exception;
+
+ /**
+ *
+ * @throws Exception Implementations are free to narrow the exception
+ * or even not throw anything.
+ */
+
+ void close() throws Exception;
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/Participant.java b/public/transactions-api/src/main/java/com/atomikos/icatch/Participant.java
new file mode 100644
index 000000000..00ac03152
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/Participant.java
@@ -0,0 +1,181 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import java.util.Map;
+
+
+
+
+/**
+ * A participant for (distributed) two-phase commit of composite transactions.
+ *
+ * Implementations can be added as a 2PC participant in the icatch kernel.
+ *
+ * VERY IMPORTANT: implementations should also override the
+ * default equals
+ * and hashCode methods, in order for two-phase commit to work properly!
+ *
+ */
+
+public interface Participant
+{
+ /**
+ * Indicates that no commit/rollback is needed after prepare.
+ */
+
+ static final int READ_ONLY=0x00;
+
+ /**
+ * @return String The unique URI for this remote participant.
+ */
+
+ String getURI();
+
+ /**
+ * For cascading 2PC, this method sets the information needed
+ * to cascade. This method is relevant only for
+ * transaction monitors; leave empty otherwise!
+ *
+ * @param allParticipants The information needed by
+ * the transaction monitor.
+ *
+ * @exception SysException
+ */
+
+ void setCascadeList(Map allParticipants)
+ throws SysException;
+
+ /**
+ * Set by the root coordinator: the total no of siblings detected.
+ * This method is relevant only for
+ * transaction monitors; leave empty otherwise!
+ *
+ *@param count The global count.
+ */
+
+ void setGlobalSiblingCount(int count);
+
+ /**
+ * Prepares the participant.
+ * Any locks for this participant's work should be
+ * recoverable (either saved to an external file OR be part
+ * of the instance's non-transient state so that they can be
+ * flushed into the transaction manager's log).
+ *
+ * @return int READ_ONLY if no second round is needed.
+ * Participants that return this value on prepare will not be
+ * called by commit or rollback afterwards.
+ *
+ * @exception RollbackException For a NO vote.
+ * This indicates that the participant has already rolled back
+ * (or marked for rollback) the work on behalf of this participant.
+ *
+ * @exception HeurHazardException On possible conflicts.
+ * This happens for remote participants instances, in case of
+ * communication failures.
+ *
+ * @exception HeurMixedException If some subordinate
+ * participants voted YES, timed out and committed heuristically
+ * whereas afterwards some NO votes where received.
+ *
+ * @exception SysException
+ */
+
+ int prepare()
+ throws RollbackException,
+ HeurHazardException,
+ HeurMixedException,
+ SysException;
+
+
+ /**
+ * Commits the participant's work.
+ * NOTE: custom participant implementations should
+ * preferably be made for the server's local VM
+ * (e.g., it is better not to do this over RMI).
+ * Also, they should rely on the transaction manager
+ * for heuristic timeout (and NOT decide to terminate
+ * heuristically themselves).
+ * In that case, implementations never need to throw any of the heuristic exceptions
+ * of this method.
+ *
+ *
+ * @param onePhase If true, one-phase commit is being started.
+ * If the participant has received a prepare call earlier,
+ * then it should throw a SysException here.
+ *
+ * @exception HeuristicRollbackException If the participant has rolled back.
+ *
+ * @exception HeuristicMixedException If part of it was rolled back.
+ *
+ * @exception HeurHazardException On possible conflicts.
+ *
+ * @exception RollbackException In case of one-phase commit,
+ * and the transaction has been rolled back at the time
+ * commit is called.
+ *
+ * @exception SysException
+ */
+
+ void commit ( boolean onePhase )
+ throws HeurRollbackException,
+ HeurHazardException,
+ HeurMixedException,
+ RollbackException,
+ SysException;
+
+ /**
+ * Rollback of the participant's work.
+ *
+ * NOTE: custom participant implementations should
+ * preferably be made for the server's local VM
+ * (e.g., it is better not to do this over RMI).
+ * Also, they should rely on the icatch transaction manager
+ * for heuristic timeout (and NOT decide to terminate
+ * heuristically themselves).
+ * In that case, implementations never need to throw any of the heuristic exceptions
+ * of this method.
+ *
+ * @exception HeurCommitException If the participant committed.
+ * @exception HeurHazardException If the participant's final state
+ * is unsure.
+ * @exception HeurMixedException If part of the work was rolled back.
+ *
+ * @exception SysException
+ */
+
+ void rollback()
+ throws HeurCommitException,
+ HeurMixedException,
+ HeurHazardException,
+ SysException;
+
+
+
+
+ /**
+ * Indicates that a heuristic participant can forget about its work.
+ *
+ * If implementations rely on the transaction manager to
+ * decide when to do heuristics (rather then deciding
+ * in the participant implementation), then
+ * leave this method empty.
+ */
+
+ void forget();
+
+
+
+ /**
+ * @return The (unique) name of the recoverable resource as known in the configuration. Null if not relevant.
+ */
+ String getResourceName();
+}
+
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/Propagation.java b/public/transactions-api/src/main/java/com/atomikos/icatch/Propagation.java
new file mode 100644
index 000000000..765bc3635
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/Propagation.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import java.util.Properties;
+import java.util.Stack;
+
+/**
+ * Information about the transaction context that can be
+ * shipped along with a remote request (via toString()), to make the other side
+ * participate in the transaction present for the current thread in this VM.
+ *
+ * This class (and its parsing) represent the minimum information required for
+ * transactions to work across remoting calls. All values are required.
+ *
+ * In addition, we are liberal in parsing: any additional, unknown values are ignored -
+ * so future releases can add extra, optional properties and still work with
+ * installations of this release.
+ *
+ */
+
+public class Propagation {
+
+ /**
+ * Major version indicator. Only change this for future releases
+ * that add incompatible changes such as adding/removing required properties
+ * and/or changing the semantics of existing properties. As long as optional properties
+ * are added/removed, the version can stay the same.
+ */
+ public static final String VERSION ="2019";
+
+ private final Stack lineage;
+ private final boolean serial;
+ private final long timeout;
+ private final String recoveryDomainName;
+ private final String recoveryCoordinatorUri;
+
+ public Propagation (String recoveryDomainName,CompositeTransaction rootTransaction, CompositeTransaction parentTransaction, boolean serial, long timeout) {
+ this(recoveryDomainName, rootTransaction, parentTransaction, serial, timeout, null);
+ }
+
+ public Propagation (String recoveryDomainName,CompositeTransaction rootTransaction, CompositeTransaction parentTransaction, boolean serial, long timeout, String recoveryCoordinatorUri) {
+ if (rootTransaction == null) {
+ throw new IllegalArgumentException("rootTransaction cannot be null");
+ }
+ if (parentTransaction == null) {
+ throw new IllegalArgumentException("parentTransaction cannot be null");
+ }
+ if (recoveryCoordinatorUri == null) {
+ //default to parent coordinator ID
+ recoveryCoordinatorUri = parentTransaction.getCompositeCoordinator().getCoordinatorId();
+ }
+ this.timeout = timeout;
+ this.serial = serial;
+ this.recoveryDomainName = recoveryDomainName;
+ this.lineage = new Stack();
+ this.lineage.push(rootTransaction);
+ this.lineage.push(parentTransaction);
+ this.recoveryCoordinatorUri = recoveryCoordinatorUri;
+ }
+
+ public String getRecoveryDomainName() {
+ return recoveryDomainName;
+ }
+
+ public CompositeTransaction getRootTransaction() {
+ return this.getLineage().firstElement();
+ }
+
+
+ public CompositeTransaction getParentTransaction() {
+ return this.lineage.lastElement();
+ }
+
+ /**
+ * @return A stack of ancestors, bottom one is the root.
+ */
+
+ public Stack getLineage() {
+ return lineage;
+ }
+
+
+ public boolean isSerial() {
+ return serial;
+ }
+
+ public long getTimeout() {
+ return timeout;
+ }
+
+ public String getRecoveryCoordinatorURI() {
+ return recoveryCoordinatorUri;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer ret = new StringBuffer();
+ ret.append("version=").append(VERSION).append(",");
+ ret.append("domain=").append(recoveryDomainName).append(",");
+ ret.append("timeout=").append(getTimeout()).append(",");
+ ret.append("serial=").append(isSerial()).append(",");
+ ret.append("recoveryCoordinatorURI=").append(recoveryCoordinatorUri).append(",");
+ addParent(getRootTransaction(), ret);
+ if (!getRootTransaction().isSameTransaction(getParentTransaction())) {
+ ret.append(",");
+ addParent(getParentTransaction(), ret);
+ }
+ return ret.toString();
+ }
+
+ private void addParent(CompositeTransaction parent, StringBuffer buf) {
+ buf.append("parent=").append(parent.getTid());
+ Properties p = parent.getProperties();
+ for (String key : p.stringPropertyNames()) {
+ buf.append(",").
+ append("property.").append(key). //use prefix to avoid name collisions between property keys and propagation attributes
+ append("=").append(p.getProperty(key));
+ }
+ }
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryCoordinator.java b/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryCoordinator.java
new file mode 100644
index 000000000..265ea63d7
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryCoordinator.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * The coordinator who knows the outcome for recovery purposes.
+ */
+
+public interface RecoveryCoordinator
+{
+
+
+ /**
+ * Gets the URI identifier for this coordinator.
+ * @return String The URI identifier.
+ */
+
+ String getURI();
+
+ /**
+ *
+ * @return The recovery domain of this coordinator; a different one indicates a foreign transaction
+ * (i.e., one whose commit decision is unknown in our logs).
+ */
+ String getRecoveryDomainName();
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryService.java b/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryService.java
new file mode 100644
index 000000000..a8a72be21
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/RecoveryService.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import com.atomikos.recovery.RecoveryLog;
+
+
+/**
+ * A handle to the TM that resources can use to recover.
+ */
+
+public interface RecoveryService
+{
+
+ /**
+ * @return String The unique name of the TM. Resources can use this name to determine what resource
+ * transactions need to be considered for recovery by this
+ * transaction service.
+ */
+
+ String getName();
+
+
+ RecoveryLog getRecoveryLog();
+
+ /**
+ * Instructs the core to do a full recovery cycle.
+ *
+ * @return False if no recovery was done,
+ * for instance if this node is not responsible for recovery
+ * or if there was a concurrent shutdown.
+ */
+ boolean performRecovery();
+
+ /**
+ * Asks the core to do a full recovery cycle.
+ *
+ * @param lax True to allow for lax optimisation
+ * so the actual overhead of recovery can be avoided in
+ * some cases. Depending on your deployment, lax mode
+ * may be accurate (or not).
+ *
+ * @return False if no recovery was done.
+ */
+ boolean performRecovery(boolean lax);
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/RollbackException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/RollbackException.java
new file mode 100644
index 000000000..3ccfa30c5
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/RollbackException.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+
+/**
+ * An exception indicating that a transaction has already been rolled back.
+ */
+
+ public class RollbackException extends Exception
+ {
+
+ private static final long serialVersionUID = 1L;
+
+ public RollbackException(String msg)
+ {
+ super (msg);
+ }
+
+ public RollbackException(String msg, Throwable cause) {
+ super(msg,cause);
+ }
+
+ public RollbackException()
+ {
+ super();
+ }
+ }
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/SubTxAwareParticipant.java b/public/transactions-api/src/main/java/com/atomikos/icatch/SubTxAwareParticipant.java
new file mode 100644
index 000000000..c1b091210
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/SubTxAwareParticipant.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * A participant that wants to be notified of local termination of a node in a
+ * nested transaction tree.
+ */
+
+public interface SubTxAwareParticipant
+{
+ /**
+ * Notification of termination.
+ *
+ * @param transaction The composite transaction that has terminated
+ * locally at its node.
+ */
+
+ void committed ( CompositeTransaction transaction );
+
+ /**
+ * Notification that some transaction has been rolledback.
+ *
+ * @param parent The transaction that has rolled back at its node.
+ */
+
+ void rolledback ( CompositeTransaction transaction );
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/Synchronization.java b/public/transactions-api/src/main/java/com/atomikos/icatch/Synchronization.java
new file mode 100644
index 000000000..e57b38109
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/Synchronization.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import com.atomikos.recovery.TxState;
+
+
+
+ /**
+ * A synchronization inferface for transaction termination callbacks.
+ * Instances are volatile, i.e. not recovered after a crash/restart.
+ */
+
+ public interface Synchronization {
+ /**
+ * Called before prepare decision is made.
+ */
+
+ void beforeCompletion ();
+
+ /**
+ * Called after the overall outcome is known.
+ *
+ * @param txstate The state of the coordinator after preparing.
+ * Equals either null ( readonly ), TxState.COMMITTING or TxState.ABORTING.
+ */
+
+ void afterCompletion ( TxState txstate );
+ }
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/SysException.java b/public/transactions-api/src/main/java/com/atomikos/icatch/SysException.java
new file mode 100644
index 000000000..5e4c44a39
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/SysException.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+/**
+ * An exception for unexpected system errors with nested information.
+ */
+
+
+public class SysException extends RuntimeException
+{
+
+ private static final long serialVersionUID = -9183281406145817016L;
+
+ public SysException (String msg)
+ {
+ super(msg);
+ }
+
+ public SysException(String msg, Throwable cause) {
+ super(msg,cause);
+ }
+
+ public SysException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionService.java b/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionService.java
new file mode 100644
index 000000000..b7d142866
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionService.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+ /**
+ * This internal interface is the base interface for creating transactions.
+ * It also acts as a container for existing transactions.
+ * Each transaction manager has a transaction service to take care
+ * of recovery, and for creating transactions and subtransactions.
+ *
+ * Application code should not use this interface directly.
+ */
+
+public interface TransactionService
+{
+ /**
+ * Adds a listener to the transaction service.
+ * This method should be called before init!
+ * If called afterwards, only the init (false)
+ * callback will happen in the listener.
+ *
+ * @param listener The listener.
+ */
+
+ void addTSListener ( TransactionServicePlugin listener );
+
+ /**
+ * Removes a listener from the transaction service.
+ * @param listener The listener.
+ */
+
+ void removeTSListener ( TransactionServicePlugin listener );
+
+
+ /**
+ * Gets the composite transaction with the given tid.
+ * @param tid The transaction identifier.
+ * @return CompositeTransaction The transaction, or null if none.
+ */
+
+ CompositeTransaction getCompositeTransaction ( String tid );
+
+
+
+ /**
+ * Starts a new transaction.
+ *
+ *
+ * @timeout Timeout (in millis) after which heuristics are done for indoubts.
+ *
+ *
+ * @return CompositeTransaction The new instance.
+ *
+ * @exception SysException
+ *
+ */
+
+ CompositeTransaction createCompositeTransaction (
+ long timeout )
+ throws SysException;
+
+
+ /**
+ * Recreates a composite transaction based on an imported context.
+ * Needed by the application's communication layer.
+ *
+ * @param context The propagation context. Any interposition
+ * actions should already have taken place, so that the propagation
+ * is ready to be used by the local transaction service.
+ *
+ * @param timeout Time in millis after which heur_commit is applied.
+ *
+ * @return CompositeTransaction The recreated local instance.
+ * throws SysException
+ */
+
+
+ CompositeTransaction
+ recreateCompositeTransaction ( Propagation context)
+ throws SysException;
+
+ /**
+ * Shuts down the server in a clean way.
+ *
+ * @param force If true, shutdown will not wait
+ * for possibly indoubt txs to finish.
+ * Calling shutdown with force being true implies that
+ * shutdown will not fail, but there may be remaining timer
+ * threads that stay asleep until there timeouts expire.
+ * Such remaining active transactions will NOT be able to finish,
+ * because the recovery manager will be shutdown by that time.
+ * New transactions will not be allowed.
+ *
+ * @exception IllegalStateException If active transactions exist, and not force.
+ * @exception SysException
+ */
+
+ void shutdown ( boolean force )
+ throws SysException, IllegalStateException;
+
+ /**
+ * Gets a participant handle for the given root.
+ * This method is for subordinated coordinators: in those
+ * cases, a participant role is fulfilled w.r.t. parent
+ * coordinators.
+ * @param root The root identifier.
+ * @return Participant The participant instance.
+ * @exception SysException On failure, or if the given root is not known.
+ */
+
+ Participant getParticipant ( String root )
+ throws SysException;
+
+ /**
+ * Gets a composite coordinator for the given root.
+ * Needed to allow TM to swap out instances after
+ * heuristic termination.
+ * If a commit, abort, forget or replay request comes in,
+ * this method should be called first to revive the instance.
+ *
+ * @param root The root in case.
+ * @return The composite coordinator for this root.
+ * @exception SysException
+ */
+
+ CompositeCoordinator getCompositeCoordinator ( String root )
+ throws SysException;
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionServicePlugin.java b/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionServicePlugin.java
new file mode 100644
index 000000000..1393c5cc4
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/TransactionServicePlugin.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+
+ /**
+ * A plugin interface for transaction service extension modules.
+ * Instances can register themselves via the ServiceLoader mechanism
+ * in order to be notified about startup and shutdown events.
+ */
+
+public interface TransactionServicePlugin
+{
+ /**
+ * Called before initialization of the transaction core.
+ *
+ *
+ * DISCLAIMER: only implementations that register with the ServiceLoader
+ * mechanism are sure of receiving this notification. Other implementations
+ * should be aware that the transaction core may already be running by the
+ * time they register - in which case there will be no callback.
+ *
+ */
+
+ void beforeInit();
+
+ /**
+ * Called after initialization of the transaction core.
+ */
+
+ void afterInit();
+
+ /**
+ * Called after shutdown of the transaction core.
+ */
+
+ void afterShutdown();
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/config/Configuration.java b/public/transactions-api/src/main/java/com/atomikos/icatch/config/Configuration.java
new file mode 100644
index 000000000..da1574f75
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/config/Configuration.java
@@ -0,0 +1,441 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+
+package com.atomikos.icatch.config;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Vector;
+
+import com.atomikos.datasource.RecoverableResource;
+import com.atomikos.datasource.ResourceException;
+import com.atomikos.icatch.CompositeTransactionManager;
+import com.atomikos.icatch.RecoveryService;
+import com.atomikos.icatch.SysException;
+import com.atomikos.icatch.TransactionService;
+import com.atomikos.icatch.TransactionServicePlugin;
+import com.atomikos.icatch.provider.Assembler;
+import com.atomikos.icatch.provider.ConfigProperties;
+import com.atomikos.icatch.provider.TransactionServiceProvider;
+import com.atomikos.recovery.RecoveryLog;
+
+/**
+ * Configuration is a facade for the transaction management core.
+ * Allows the application code to find the transaction manager, even if
+ * the actual implementation varies over time.
+ */
+
+@SuppressWarnings("all")
+public final class Configuration
+{
+
+
+ private static CompositeTransactionManager ctxmgr_ = null;
+ // the tm for the virtual machine instance
+
+ private static Hashtable resources_ = new Hashtable ();
+ // filled on startup, contains all resources managed by the
+ // transaction manager.
+
+ private static Vector resourceList_ = new Vector ();
+ // keep resources in a list, to enable ordered search of XAResource
+ // this way, an AcceptAllXATransactionalResource can be added at the end
+
+
+ private static RecoveryService recoveryService_;
+ // needed for addResource to do recovery
+
+ private static TransactionServiceProvider service_;
+ // the transaction service for this VM.
+
+ private static Vector tsListenersList_ = new Vector ();
+
+ private static List shutdownHooks_ = new ArrayList();
+
+ private static Assembler assembler;
+
+ private static ConfigProperties configProperties;
+
+
+ private static void purgeResources ()
+ {
+ Collection resources = getResources ();
+ for(RecoverableResource res : resources) {
+ if (res.isClosed()) {
+ removeResource(res.getName());
+ }
+ }
+ }
+
+ /**
+ * Construction not allowed.
+ *
+ */
+ private Configuration ()
+ {
+ }
+
+ private static void addAllTransactionServicePluginServicesFromClasspath() {
+ ServiceLoader loader = ServiceLoader.load(TransactionServicePlugin.class,Configuration.class.getClassLoader());
+ for (TransactionServicePlugin l : loader ) {
+ registerTransactionServicePlugin(l);
+ }
+ }
+
+ /**
+ * Adds a shutdown hook to the configuration.
+ * Shutdown hooks are managed here, since regular shutdown
+ * of the transaction core should remove hooks
+ * (cf case 21519).
+ *
+ * @param hook
+ */
+ private static synchronized void addShutdownHook ( Thread hook )
+ {
+ if ( shutdownHooks_.contains ( hook ) ) return;
+
+ shutdownHooks_.add ( hook );
+ try {
+ Runtime.getRuntime().addShutdownHook ( hook );
+ }
+ catch ( IllegalStateException alreadyShuttingDownVm ) {
+ //ignore: this happens when the VM exits and this method
+ //is called as part of one of the shutdown hooks executing
+ }
+ }
+
+
+ /**
+ * Removes all shutdown hooks from the system.
+ * This method should be called on shutdown of the core.
+ */
+
+ private static synchronized void removeShutdownHooks()
+ {
+ Iterator it = shutdownHooks_.iterator();
+
+ //first check if we are not already doing a VM exit;
+ //don't remove the hooks if so
+ boolean vmShutdown = false;
+ while ( it.hasNext() ) {
+ Thread t = ( Thread ) it.next();
+ if ( t.equals ( Thread.currentThread() ) ) vmShutdown = true;
+ }
+
+ it = shutdownHooks_.iterator();
+ while ( !vmShutdown && it.hasNext() ) {
+ Thread hook = ( Thread ) it.next();
+ it.remove();
+ try {
+ Runtime.getRuntime().removeShutdownHook ( hook );
+ }
+ catch ( IllegalStateException alreadyShuttingDownVm ) {
+ //ignore: this happens when the VM exits and this method
+ //is called as part of one of the shutdown hooks executing
+ }
+ }
+ }
+
+ /**
+ * Retrieves the transaction service being used.
+ *
+ * @return TransactionService The transaction service.
+ */
+
+ public static TransactionService getTransactionService ()
+ {
+ return service_;
+ }
+
+ /**
+ * Add a transaction service listener.
+ *
+ * @param l
+ * The listener.
+ */
+ public static synchronized void registerTransactionServicePlugin ( TransactionServicePlugin l )
+ {
+
+ if ( service_ != null ) {
+ service_.addTSListener ( l );
+ }
+ tsListenersList_.add ( l );
+ }
+
+ /**
+ * Remove a transaction service listener.
+ *
+ * @param l
+ * The listener.
+ */
+ public static synchronized void unregisterTransactionServicePlugin ( TransactionServicePlugin l )
+ {
+ if ( service_ != null ) {
+ service_.removeTSListener ( l );
+ }
+ tsListenersList_.remove ( l );
+ }
+
+ /**
+ * Installs a composite transaction manager as a Singleton.
+ *
+ * @param compositeTransactionManager
+ * The instance to install.
+ */
+
+ public static synchronized void installCompositeTransactionManager (
+ CompositeTransactionManager compositeTransactionManager )
+ {
+
+ ctxmgr_ = compositeTransactionManager;
+ }
+
+ /**
+ * Get the composite transaction manager.
+ *
+ * @return CompositeTransactionManager The instance, or null if none.
+ */
+
+ public static CompositeTransactionManager getCompositeTransactionManager ()
+ {
+ return ctxmgr_;
+ }
+
+
+
+ /**
+ * Add a resource to the transaction manager domain. Should be called for
+ * all resources that have to be recovered, BEFORE initializing the
+ * transaction manager! The purpose of registering resources is mainly to be
+ * able the recovery the ResourceTransaction context for each prepared
+ * ResourceTransction. This is needed for those ResourceTransaction
+ * instances that do not encapsulate the full state themselves, as in the
+ * XAResource case.
+ *
+ * @param resource
+ * The resource to add.
+ *
+ * @exception IllegalStateException
+ * If the name of the resource is already in use.
+ */
+
+ public static synchronized void addResource ( RecoverableResource resource )
+ throws IllegalStateException
+ {
+ // ADDED with new recovery: temporary resources:
+ // memory overflow can only happen upon addition of resources
+ // so before each add, first purge closed resources to make room
+ purgeResources ();
+
+ if ( resources_.containsKey ( resource.getName () ) )
+ throw new IllegalStateException ( "Another resource already exists with name " + resource.getName() + " - pick a different name");
+
+
+ //FIRST add init resource, only then add it - cf case 142795
+ resource.setRecoveryService ( recoveryService_ );
+ resources_.put ( resource.getName (), resource );
+ resourceList_.add ( resource );
+ }
+
+ /**
+ * Removes a resource from the config.
+ *
+ * @param name
+ * The resource's name.
+ * @return RecoverableResource The removed object.
+ */
+
+ public static RecoverableResource removeResource ( String name )
+ {
+ RecoverableResource ret = null;
+ if ( name != null ) {
+ ret = (RecoverableResource) resources_.remove ( name );
+ if ( ret != null ) resourceList_.remove ( ret );
+
+ }
+ return ret;
+ }
+
+ /**
+ * Get the resource with the given name.
+ *
+ * @return RecoverableResource The resource.
+ * @param name
+ * The name to find.
+ */
+
+ public static RecoverableResource getResource ( String name )
+ {
+ RecoverableResource res = null;
+ if ( name != null ) res = (RecoverableResource) resources_.get ( name );
+ return res;
+ }
+
+ /**
+ * Get all resources added so far, in the order that they were added.
+ *
+ */
+
+ public static Collection getResources ()
+ {
+ // clone to avoid concurrency problems with
+ // add/removeResource (new recovery makes this possible)
+ Vector ret = (Vector) resourceList_.clone ();
+ return ret;
+ }
+
+ protected static synchronized Assembler getAssembler() {
+ if (assembler == null) loadAssembler();
+ return assembler;
+ }
+
+ private static void loadAssembler() {
+ ServiceLoader loader = ServiceLoader.load(Assembler.class,Configuration.class.getClassLoader());
+ Iterator it = loader.iterator();
+ if (it.hasNext()) {
+ assembler = it.next();
+ } else {
+ throw new SysException("No Assembler service found - please make sure that the right jars are in your classpath");
+ }
+ }
+
+ public static synchronized ConfigProperties getConfigProperties() {
+ if (configProperties == null) {
+ configProperties = getAssembler().initializeProperties();
+ }
+ return configProperties;
+ }
+
+ static synchronized void resetConfigProperties() {
+ configProperties = null;
+ }
+
+ public static synchronized void shutdown(boolean force) {
+ if (service_ != null) {
+ service_.shutdown(force);
+ notifyAfterShutdown();
+ removeShutdownHooks();
+ removeAndCloseResources(force);
+ clearSystemComponents();
+ }
+ }
+
+ private static void clearSystemComponents() {
+ service_ = null;
+ recoveryService_ = null;
+ ctxmgr_ = null;
+ tsListenersList_.clear();
+ resetConfigProperties();
+ assembler = null;
+ }
+
+ private static void notifyAfterShutdown() {
+ for (TransactionServicePlugin p : tsListenersList_) {
+ p.afterShutdown();
+ }
+ }
+
+ private static void removeAndCloseResources(boolean force) {
+ Collection resources = Configuration.getResources();
+ for (RecoverableResource res : resources) {
+ Configuration.removeResource ( res.getName () );
+ try {
+ res.close ();
+ } catch ( ResourceException re ) {
+ //Issue 10038:
+ //Ignore errors in force mode: force is most likely
+ //during VM exit; in that case interleaving of shutdown hooks
+ //means that resource connectors may have closed already
+ //by the time the TM hook runs. We don't want useless
+ //reports in that case.
+ //NOTE: any invalid states will be detected during the next
+ //(re)init so they can be ignored here (if force mode)
+
+ if ( !force ) {
+ re.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * @return False if already running.
+ */
+ public static synchronized boolean init() {
+ boolean startupInitiated = false;
+ if (service_ == null) {
+ startupInitiated = true;
+ addAllTransactionServicePluginServicesFromClasspath();
+ notifyBeforeInit();
+ assembleSystemComponents();
+ initializeSystemComponents();
+ notifyAfterInit();
+ if (configProperties.getForceShutdownOnVmExit()) {
+ addShutdownHook(new ForceShutdownHook());
+ }
+ }
+ return startupInitiated;
+ }
+
+ private static void notifyAfterInit() {
+ for (TransactionServicePlugin p : tsListenersList_) {
+ p.afterInit();
+ }
+ for (RecoverableResource r : resourceList_ ) {
+ r.setRecoveryService(recoveryService_);
+ }
+
+ }
+
+ private static void initializeSystemComponents() {
+ service_.init(getConfigProperties().getCompletedProperties());
+ }
+
+ private static void notifyBeforeInit() {
+ for (TransactionServicePlugin p : tsListenersList_) {
+ p.beforeInit();
+ }
+ }
+
+ private static void assembleSystemComponents() {
+ Assembler assembler = getAssembler();
+ service_ = assembler.assembleTransactionService();
+ recoveryService_ = service_.getRecoveryService();
+ ctxmgr_ = assembler.assembleCompositeTransactionManager();
+ }
+
+ public static RecoveryLog getRecoveryLog() {
+ return recoveryService_.getRecoveryLog();
+ }
+
+ public static RecoveryService getRecoveryService() {
+ return recoveryService_;
+ }
+
+ private static class ForceShutdownHook extends Thread
+ {
+
+
+ private ForceShutdownHook()
+ {
+ super();
+ }
+
+ public void run() {
+ Configuration.shutdown(true);
+ }
+ }
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionService.java b/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionService.java
new file mode 100644
index 000000000..76c03773e
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionService.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.config;
+import java.util.Properties;
+
+import com.atomikos.datasource.RecoverableResource;
+import com.atomikos.icatch.CompositeTransactionManager;
+import com.atomikos.icatch.SysException;
+import com.atomikos.icatch.TransactionServicePlugin;
+
+ /**
+ *
+ * The user's (client program) view of the transaction manager's configuration,
+ * with all the information the client program needs.
+ *
+ */
+
+public interface UserTransactionService
+{
+ void shutdown(boolean force) throws IllegalStateException;
+
+ void registerResource ( RecoverableResource resource );
+
+ void removeResource ( RecoverableResource res );
+
+ void registerTransactionServicePlugin ( TransactionServicePlugin listener );
+
+ void removeTransactionServicePlugin ( TransactionServicePlugin listener );
+
+ void init ( Properties properties ) throws SysException;
+
+ void init() throws SysException;
+
+ CompositeTransactionManager getCompositeTransactionManager();
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionServiceImp.java b/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionServiceImp.java
new file mode 100644
index 000000000..9909245fc
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/config/UserTransactionServiceImp.java
@@ -0,0 +1,207 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+
+
+package com.atomikos.icatch.config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import com.atomikos.datasource.RecoverableResource;
+import com.atomikos.icatch.CompositeTransactionManager;
+import com.atomikos.icatch.OrderedLifecycleComponent;
+import com.atomikos.icatch.SysException;
+import com.atomikos.icatch.TransactionServicePlugin;
+import com.atomikos.icatch.provider.ConfigProperties;
+
+/**
+ * This is the main class for creating a UserTransactionService instance.
+ * This class is the client's main entry point into the transaction engine.
+ *
+ * The creation of a user transaction happens via a UserTransactionServiceFactory
+ * which is looked up by this object behind the scenes.
+ * Instances can be serialized to disk and re-read at a later time.
+ * Note: deserialization will only work in the SAME configuration as the
+ * one that did the streaming out. In particular, if no identical Atomikos
+ * transaction service is present in the target VM then the process
+ * of deserialization will be erroneous.
+ */
+
+public final class UserTransactionServiceImp
+ implements java.io.Serializable , UserTransactionService, OrderedLifecycleComponent
+{
+
+
+ private static final long serialVersionUID = -3374591336514451887L;
+
+ private Properties properties_;
+
+ private List tsListeners_;
+ private List resources_;
+
+ /**
+ * Default constructor.
+ *
+ */
+
+ public UserTransactionServiceImp ()
+ {
+ tsListeners_ = new ArrayList();
+ resources_ = new ArrayList();
+ properties_ = new Properties();
+
+ }
+
+ /**
+ * Constructs a new instance and initializes it with the given properties.
+ * If this constructor is called, then file-based initialization is overridden.
+ * In particular, the given properties will take precedence over the file-based
+ * properties (if found).
+ *
+ * @param properties The properties.
+ */
+
+ public UserTransactionServiceImp ( Properties properties )
+ {
+ this();
+ properties_ = properties;
+ }
+
+ /**
+ *
+ * @see UserTransactionService
+ */
+
+ public void shutdown ( boolean force )
+ throws IllegalStateException
+ {
+ Configuration.shutdown(force);
+ }
+
+
+
+
+ private void initialize() {
+ for (RecoverableResource resource : resources_) {
+ Configuration.addResource ( resource );
+ }
+ for (TransactionServicePlugin nxt : tsListeners_) {
+ Configuration.registerTransactionServicePlugin ( nxt );
+ }
+ ConfigProperties configProps = Configuration.getConfigProperties();
+ configProps.applyUserSpecificProperties(properties_);
+ Configuration.init();
+ }
+
+ /**
+ *@see UserTransactionService
+ */
+
+ public CompositeTransactionManager
+ getCompositeTransactionManager ()
+ {
+ return Configuration.getCompositeTransactionManager();
+ }
+
+
+ /**
+ * @see com.atomikos.icatch.UserTransactionService#registerResource(com.atomikos.datasource.RecoverableResource)
+ */
+ public void registerResource(RecoverableResource res)
+ {
+ Configuration.addResource(res);
+
+ }
+
+ public void removeResource ( RecoverableResource res )
+ {
+ Configuration.removeResource(res.getName());
+
+ }
+
+ public void registerTransactionServicePlugin ( TransactionServicePlugin listener )
+ {
+ Configuration.registerTransactionServicePlugin(listener);
+ }
+
+ public void removeTransactionServicePlugin ( TransactionServicePlugin listener )
+ {
+ Configuration.unregisterTransactionServicePlugin(listener);
+ }
+
+
+ /**
+ * Convenience shutdown method for DI containers like Spring.
+ *
+ */
+
+ public void shutdownForce()
+ {
+ shutdown ( true );
+
+ }
+
+ /**
+ * Convenience shutdown method for DI containers like Spring.
+ *
+ */
+
+ public void shutdownWait()
+ {
+ shutdown ( false );
+
+ }
+
+ /**
+ * Dependency injection of all resources to be added during init.
+ *
+ * @param resources
+ */
+ public void setInitialRecoverableResources ( List resources )
+ {
+ resources_ = resources;
+
+ }
+
+ /**
+ * Dependency injection of extra plugins to be added during init.
+ * @param listeners
+ */
+ public void setInitialTransactionServicePlugins ( List listeners )
+ {
+ tsListeners_ = listeners;
+
+ }
+
+ /**
+ * Convenience init method for DI containers like Spring.
+ *
+ */
+ public void init()
+ {
+ initialize();
+ }
+
+
+ /**
+ * Initializes with given properties.
+ */
+ public void init ( Properties properties ) throws SysException {
+ properties_ = properties;
+ initialize();
+ }
+
+ @Override
+ public void close() throws Exception {
+ shutdownWait();
+ }
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/event/Event.java b/public/transactions-api/src/main/java/com/atomikos/icatch/event/Event.java
new file mode 100644
index 000000000..0c5380152
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/event/Event.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.event;
+
+/**
+ * Significant core events that are communicated to plugged-in listeners.
+ */
+public abstract class Event {
+
+ public final long eventCreationTimestamp;
+
+ protected Event() {
+ this.eventCreationTimestamp = System.currentTimeMillis();
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/event/EventListener.java b/public/transactions-api/src/main/java/com/atomikos/icatch/event/EventListener.java
new file mode 100644
index 000000000..78425ea26
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/event/EventListener.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.event;
+
+
+/**
+ * Observer interface for transaction-related domain events.
+ *
+ * External applications/modules can implement this functionality to
+ * be notified of significant events. Implementations are registered
+ * via the JDK 6+ ServiceLoader mechanism.
+ *
+ * CAUTION: event notification is synchronous, so registering listeners
+ * may impact performance of the core!
+ */
+public interface EventListener {
+
+ void eventOccurred(Event event);
+
+}
\ No newline at end of file
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/ParticipantHeuristicEvent.java b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/ParticipantHeuristicEvent.java
new file mode 100644
index 000000000..a7e90fe18
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/ParticipantHeuristicEvent.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.event.transaction;
+
+import com.atomikos.recovery.TxState;
+
+/**
+ * Signals heuristic outcome on behalf of a participant.
+ */
+public class ParticipantHeuristicEvent extends TransactionEvent {
+
+ public final String participantUri;
+ public final TxState state;
+
+ public ParticipantHeuristicEvent(String transactionId, String participantUri, TxState state) {
+ super(transactionId);
+ this.participantUri = participantUri;
+ this.state = state;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer ret = new StringBuffer();
+ ret.append("Heuristic state detected: ").append(state).
+ append(" for participant ").append(participantUri).
+ append(" in transaction: ").append(transactionId).
+ append(" (HINT: check https://www.atomikos.com/Documentation/HowToHandleHeuristics to learn more on how to handle heuristics...)");
+
+ return ret.toString();
+ }
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionEvent.java b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionEvent.java
new file mode 100644
index 000000000..5c1fb9b7a
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionEvent.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.event.transaction;
+
+import com.atomikos.icatch.event.Event;
+
+
+/**
+ * Domain event raised whenever something significant happens in the transaction life cycle.
+ *
+ */
+
+public abstract class TransactionEvent extends Event {
+
+ public final String transactionId;
+
+ protected TransactionEvent(String transactionId) {
+ this.transactionId = transactionId;
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionHeuristicEvent.java b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionHeuristicEvent.java
new file mode 100644
index 000000000..c11e3ecce
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/event/transaction/TransactionHeuristicEvent.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.event.transaction;
+
+import com.atomikos.recovery.TxState;
+
+ /**
+ * Event to signal a heuristic outcome.
+ *
+ */
+
+public class TransactionHeuristicEvent extends TransactionEvent {
+
+ public final String parentTransactionId;
+ public final TxState state;
+
+ public TransactionHeuristicEvent(String transactionId, String parentTransactionId, TxState state) {
+ super(transactionId);
+ this.parentTransactionId = parentTransactionId;
+ this.state = state;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer ret = new StringBuffer();
+ ret.append("Detected state: ").append(state).
+ append(" for transaction ").append(transactionId);
+ if (parentTransactionId != null) {
+ ret.append(" with parent transaction ").append(parentTransactionId);
+ }
+ if (state.isHeuristic()) {
+ ret.append(" (HINT: check https://www.atomikos.com/Documentation/HowToHandleHeuristics to learn more on how to handle heuristics...)");
+ }
+
+ return ret.toString();
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/provider/Assembler.java b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/Assembler.java
new file mode 100644
index 000000000..9085f00d7
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/Assembler.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.provider;
+
+import com.atomikos.icatch.CompositeTransactionManager;
+
+ /**
+ * Abstraction of how the API is instantiated.
+ * Instances are found by the Configuration class,
+ * via the ServiceLoader mechanism of the JDK.
+ */
+
+public interface Assembler {
+
+ ConfigProperties initializeProperties();
+
+ TransactionServiceProvider assembleTransactionService();
+
+ CompositeTransactionManager assembleCompositeTransactionManager();
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/provider/ConfigProperties.java b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/ConfigProperties.java
new file mode 100644
index 000000000..3a61392d6
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/ConfigProperties.java
@@ -0,0 +1,277 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.provider;
+
+import java.lang.management.ManagementFactory;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.Properties;
+
+public final class ConfigProperties {
+
+
+ public static final String TM_UNIQUE_NAME_PROPERTY_NAME = "com.atomikos.icatch.tm_unique_name";
+ public static final String LOG_BASE_DIR_PROPERTY_NAME = "com.atomikos.icatch.log_base_dir";
+ public static final String LOG_BASE_NAME_PROPERTY_NAME = "com.atomikos.icatch.log_base_name";
+ public static final String ENABLE_LOGGING_PROPERTY_NAME = "com.atomikos.icatch.enable_logging";
+ public static final String MAX_TIMEOUT_PROPERTY_NAME = "com.atomikos.icatch.max_timeout";
+ public static final String MAX_ACTIVES_PROPERTY_NAME = "com.atomikos.icatch.max_actives";
+ public static final String FORCE_SHUTDOWN_ON_VM_EXIT_PROPERTY_NAME = "com.atomikos.icatch.force_shutdown_on_vm_exit";
+ public static final String FILE_PATH_PROPERTY_NAME = "com.atomikos.icatch.file";
+ public static final String CHECKPOINT_INTERVAL_PROPERTY_NAME = "com.atomikos.icatch.checkpoint_interval";
+
+ public static final String FORGET_ORPHANED_LOG_ENTRIES_DELAY_PROPERTY_NAME = "com.atomikos.icatch.forget_orphaned_log_entries_delay";
+ public static final String OLTP_MAX_RETRIES_PROPERTY_NAME = "com.atomikos.icatch.oltp_max_retries";
+ public static final String OLTP_RETRY_INTERVAL_PROPERTY_NAME = "com.atomikos.icatch.oltp_retry_interval";
+ public static final String RECOVERY_DELAY_PROPERTY_NAME = "com.atomikos.icatch.recovery_delay";
+
+ public static final String ALLOW_SUBTRANSACTIONS_PROPERTY_NAME = "com.atomikos.icatch.allow_subtransactions";
+ public static final String THROW_ON_HEURISTIC_PROPERTY_NAME = "com.atomikos.icatch.throw_on_heuristic";
+ public static final String JVM_ID_PROPERTY_NAME = "com.atomikos.icatch.jvm_id";
+
+ public static final String LOG_LOCK_ACQUISITION_MAX_ATTEMPTS = "com.atomikos.icatch.log_lock_acquisition_max_attempts";
+ public static final String LOG_LOCK_ACQUISITION_RETRY_DELAY = "com.atomikos.icatch.log_lock_acquisition_retry_delay";
+
+
+ /**
+ * Replace ${...} sequence with the referenced value from the given properties or
+ * (if not found) the system properties -
+ * contributed through Marian Kelc (marian.kelc@eplus.de)
+ * E-Plus Mobilfunk GmbH & Co. KG, Germany
+ */
+ private static String evaluateReference ( String value , Properties properties )
+ {
+ String result = value;
+ //by default, the value as-is is returned
+
+ int startIndex = value.indexOf ( '$' );
+ if ( startIndex > -1 && value.charAt ( startIndex +1 ) == '{') {
+ //at least one reference is found
+ int endIndex = value.indexOf ( '}' );
+ if ( startIndex + 2 == endIndex )
+ throw new IllegalArgumentException ( "property ref cannot refer to an empty name: ${}" );
+ if ( endIndex == -1 )
+ throw new IllegalArgumentException ( "unclosed property ref: ${" + value.substring ( startIndex + 2 ) );
+
+ //strip-off reference characters -> get the referenced property name
+ String subPropertyKey = value.substring ( startIndex + 2, endIndex );
+ //the properties take precedence -> try them first
+ String subPropertyValue = properties.getProperty ( subPropertyKey );
+ if ( subPropertyValue == null ) {
+ //not found in properties -> try system property
+ subPropertyValue = System.getProperty ( subPropertyKey );
+ }
+
+ if ( subPropertyValue != null ) {
+ //in-line refs supported - result is prefix + value + suffix !!!
+ result = result.substring ( 0, startIndex ) + subPropertyValue + result.substring ( endIndex +1 );
+ //two or more refs supported - evaluate any remaining references in the value
+ result = evaluateReference ( result , properties );
+ }
+ else {
+ //referenced value not found -> ignore any other references and return value as-is
+ //NOTE: trying to resolve further references would lead to infinite recursion
+ }
+
+ }
+
+ return result;
+ }
+
+ private static String getDefaultName ()
+ {
+
+ String ret = "tm";
+ try {
+ ret = java.net.InetAddress.getLocalHost ().getHostAddress ()
+ + ".tm";
+ } catch ( UnknownHostException e ) {
+ // ignore: use short default
+ }
+
+ return ret;
+ }
+
+
+ private Properties properties;
+
+
+ public ConfigProperties(Properties properties) {
+ if (properties == null) throw new IllegalArgumentException("Properties should not be null");
+ this.properties = properties;
+ }
+
+ private void setDefaultTmUniqueName() {
+ if (properties.getProperty(TM_UNIQUE_NAME_PROPERTY_NAME) == null ) {
+ properties.setProperty(TM_UNIQUE_NAME_PROPERTY_NAME, getDefaultName());
+ }
+ }
+
+ private void setDefaultJvmId() {
+ if (properties.getProperty(JVM_ID_PROPERTY_NAME) == null ) {
+ properties.setProperty(JVM_ID_PROPERTY_NAME, getDefaultJvmId());
+ }
+ }
+
+ private String getDefaultJvmId() {
+ return ManagementFactory.getRuntimeMXBean().getName();
+ }
+
+ private void applySystemProperties() {
+ Properties systemProperties = System.getProperties();
+ Enumeration> propertyNames = systemProperties.propertyNames();
+ while (propertyNames.hasMoreElements()) {
+ String name = (String) propertyNames.nextElement();
+ if (name.startsWith("com.atomikos")) {
+ properties.setProperty(name, systemProperties.getProperty(name));
+ }
+ }
+ }
+
+ private void substitutePlaceHolderValues() {
+ //resolve referenced values with ant-like ${...} syntax
+ Enumeration> allProps= properties.propertyNames();
+ while ( allProps.hasMoreElements() ) {
+ String key = ( String ) allProps.nextElement();
+ String raw = properties.getProperty ( key );
+ String value= evaluateReference ( raw , properties );
+ if ( !raw.equals ( value ) ) {
+ properties.setProperty ( key, value );
+ }
+ }
+ }
+
+ public String getProperty(String name) {
+ completeProperties();
+ String ret = properties.getProperty(name);
+ if (ret == null) {
+ throw new IllegalArgumentException("Missing required property: " + name);
+ }
+ ret = ret.trim();
+ return ret;
+ }
+
+ public void setProperty(String name, String value) {
+ properties.setProperty(name, value);
+ }
+
+ public boolean getAsBoolean(String name) {
+ boolean ret = false;
+ String retAsString = getProperty(name);
+ ret = Boolean.valueOf(retAsString);
+ return ret;
+ }
+
+ public int getAsInt(String name) {
+ String retAsString = getProperty(name);
+ return Integer.valueOf(retAsString);
+ }
+
+ public long getAsLong(String name) {
+ String retAsString = getProperty(name);
+ return Long.valueOf(retAsString);
+ }
+
+ public String getTmUniqueName() {
+ return getProperty(TM_UNIQUE_NAME_PROPERTY_NAME);
+ }
+
+ public String getLogBaseDir() {
+ return getProperty(LOG_BASE_DIR_PROPERTY_NAME);
+ }
+
+ public String getLogBaseName() {
+ return getProperty(LOG_BASE_NAME_PROPERTY_NAME);
+ }
+
+ public boolean getEnableLogging() {
+ return getAsBoolean(ENABLE_LOGGING_PROPERTY_NAME);
+ }
+
+ public long getMaxTimeout() {
+ return getAsLong(MAX_TIMEOUT_PROPERTY_NAME);
+ }
+
+ public int getMaxActives() {
+ return getAsInt(MAX_ACTIVES_PROPERTY_NAME);
+ }
+
+ public long getCheckpointInterval(){
+ return getAsLong(CHECKPOINT_INTERVAL_PROPERTY_NAME);
+ }
+
+ public void applyUserSpecificProperties(Properties userSpecificProperties) {
+ Enumeration> names = userSpecificProperties.propertyNames();
+ while (names.hasMoreElements()) {
+ String name = (String) names.nextElement();
+ properties.setProperty(name, userSpecificProperties.getProperty(name));
+ }
+ }
+
+ public Properties getCompletedProperties() {
+ Properties ret = new Properties();
+ completeProperties();
+ Enumeration> propertyNames = properties.propertyNames();
+ while (propertyNames.hasMoreElements()) {
+ String name = (String) propertyNames.nextElement();
+ ret.setProperty(name, getProperty(name));
+ }
+ return ret;
+ }
+
+ private void completeProperties() {
+ applySystemProperties();
+ substitutePlaceHolderValues();
+ setDefaultTmUniqueName();
+ setDefaultJvmId();
+ }
+
+ public boolean getForceShutdownOnVmExit() {
+ return getAsBoolean(FORCE_SHUTDOWN_ON_VM_EXIT_PROPERTY_NAME);
+ }
+
+ public long getForgetOrphanedLogEntriesDelay() {
+ return getAsLong(FORGET_ORPHANED_LOG_ENTRIES_DELAY_PROPERTY_NAME);
+ }
+
+ public int getOltpMaxRetries() {
+ return getAsInt(OLTP_MAX_RETRIES_PROPERTY_NAME);
+ }
+
+ public long getOltpRetryInterval() {
+ return getAsInt(OLTP_RETRY_INTERVAL_PROPERTY_NAME);
+ }
+
+ public long getRecoveryDelay() {
+ return getAsLong(RECOVERY_DELAY_PROPERTY_NAME);
+ }
+
+ public boolean getAllowSubTransactions() {
+ return getAsBoolean(ALLOW_SUBTRANSACTIONS_PROPERTY_NAME);
+ }
+
+ public boolean getThrowOnHeuristic() {
+ return getAsBoolean(THROW_ON_HEURISTIC_PROPERTY_NAME);
+ }
+
+ public int getLockAcquisitionMaxAttempts(){
+ return getAsInt(LOG_LOCK_ACQUISITION_MAX_ATTEMPTS);
+ }
+
+ public long getLockAcquisitionRetryDelay(){
+ return getAsLong(LOG_LOCK_ACQUISITION_RETRY_DELAY);
+ }
+
+ public String getJvmId() {
+ return getProperty(JVM_ID_PROPERTY_NAME);
+
+ }
+
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/icatch/provider/TransactionServiceProvider.java b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/TransactionServiceProvider.java
new file mode 100644
index 000000000..59b1d923c
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/icatch/provider/TransactionServiceProvider.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.provider;
+
+import java.util.Properties;
+
+import com.atomikos.icatch.RecoveryService;
+import com.atomikos.icatch.SysException;
+import com.atomikos.icatch.TransactionService;
+
+public interface TransactionServiceProvider extends TransactionService {
+
+ void init(Properties properties) throws SysException;
+
+ RecoveryService getRecoveryService();
+
+ void shutdown(boolean force);
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/LogException.java b/public/transactions-api/src/main/java/com/atomikos/recovery/LogException.java
new file mode 100644
index 000000000..2835757ea
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/LogException.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+public class LogException extends Exception {
+
+ private static final long serialVersionUID = 3259337218182873867L;
+
+ public LogException() {
+ super();
+ }
+
+ public LogException(String message) {
+ super(message);
+ }
+
+ public LogException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/LogReadException.java b/public/transactions-api/src/main/java/com/atomikos/recovery/LogReadException.java
new file mode 100644
index 000000000..9ecc037bb
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/LogReadException.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+public class LogReadException extends LogException {
+
+ private static final long serialVersionUID = -4835268355879075429L;
+
+ public LogReadException() {
+ super();
+ }
+
+ public LogReadException(Throwable cause) {
+ super(cause);
+ }
+
+ public LogReadException(String message) {
+ super(message);
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/LogWriteException.java b/public/transactions-api/src/main/java/com/atomikos/recovery/LogWriteException.java
new file mode 100644
index 000000000..2cd8ed29c
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/LogWriteException.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+public class LogWriteException extends LogException {
+
+ private static final long serialVersionUID = 5648208124041649641L;
+
+ public LogWriteException() {
+ super();
+ }
+ public LogWriteException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLog.java b/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLog.java
new file mode 100644
index 000000000..a7c553759
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLog.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+
+ /**
+ * Handle to the transaction logs for writing during transaction processing.
+ */
+public interface OltpLog {
+
+ void write(PendingTransactionRecord pendingTransactionRecord) throws LogWriteException;
+
+ void close();
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLogFactory.java b/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLogFactory.java
new file mode 100644
index 000000000..867342586
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/OltpLogFactory.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+
+ /**
+ * Non-default OLTP logging can be enabled by registering an instance
+ * of this interface via the ServiceLoader mechanism of the JDK.
+ * At most one instance is allowed. If none is found, then default logging will be used.
+ */
+
+public interface OltpLogFactory {
+
+ /**
+ * @param properties The init properties picked up .
+ */
+ public OltpLog createOltpLog();
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/PendingTransactionRecord.java b/public/transactions-api/src/main/java/com/atomikos/recovery/PendingTransactionRecord.java
new file mode 100644
index 000000000..80a621441
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/PendingTransactionRecord.java
@@ -0,0 +1,204 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+public class PendingTransactionRecord {
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ private static final String COLUMN_SEPARATOR = "|";
+
+ public final String id;
+
+ public final TxState state;
+
+ public final long expires;
+
+ public final String superiorId;
+
+ /**
+ * For imported transactions, this will be a foreign recovery domain.
+ */
+ public final String recoveryDomainName;
+
+
+
+ public PendingTransactionRecord(String id, TxState state, long expires, String recoveryDomainName) {
+ this(id, state, expires, recoveryDomainName, null);
+ }
+
+ public PendingTransactionRecord(String id, TxState state, long expires, String recoveryDomainName, String superiorId) {
+ super();
+ this.id = id;
+ this.state = state;
+ this.expires = expires;
+ this.superiorId = superiorId;
+ this.recoveryDomainName = recoveryDomainName;
+ }
+
+ public String toRecord() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(id)
+ .append(COLUMN_SEPARATOR)
+ .append(state.name())
+ .append(COLUMN_SEPARATOR)
+ .append(expires)
+ .append(COLUMN_SEPARATOR)
+ .append(recoveryDomainName)
+ .append(COLUMN_SEPARATOR)
+ .append(superiorId==null?"":superiorId)
+ .append(LINE_SEPARATOR);
+ return sb.toString();
+ }
+
+ /**
+ *
+ * @throws IllegalArgumentException If the supplied value cannot be parsed.
+ */
+ public static PendingTransactionRecord fromRecord(String record) {
+ String[] properties = record.split("\\|");
+ if (properties.length < 4) {
+ throw new IllegalArgumentException("Invalid record value supplied: " + record);
+ }
+ String id = properties[0];
+ TxState state = TxState.valueOf(properties[1]);
+ Long expires = Long.valueOf(properties[2]);
+ String recoveryDomainName = String.valueOf(properties[3]);
+ String superiorId = null;
+ if(properties.length > 4) {
+ superiorId = properties[4];
+ }
+
+ return new PendingTransactionRecord(id, state, expires, recoveryDomainName, superiorId);
+ }
+
+ public static Collection findAllDescendants(PendingTransactionRecord entry, Collection collection) {
+ return collectLineages(
+ (PendingTransactionRecord r)-> entry.id.equals(r.superiorId),
+ collection);
+ }
+
+ public static void removeAllDescendants(PendingTransactionRecord entry, Collection allCoordinatorLogEntries) {
+ Collection descendants = findAllDescendants(entry, allCoordinatorLogEntries);
+ for (PendingTransactionRecord descendant : descendants) {
+ allCoordinatorLogEntries.remove(descendant);
+ }
+ }
+
+ /**
+ *
+ * @param predicate
+ * @param collection
+ * @return A collection of all descendants of records that match the given predicate, including the matching records.
+ */
+ public static Collection collectLineages(AncestorPredicate predicate, Collection collection) {
+ Collection results = new HashSet<>();
+ Map map = map(collection);
+ for (PendingTransactionRecord record : collection) {
+ if (!predicate.holdsFor(record)) {
+ if (record.superiorId != null) { //look for ancestor that matches
+ Collection ret = new HashSet<>();
+ collectAncestors(ret, record.superiorId, predicate, map);
+ if(!ret.isEmpty()) {
+ ret.add(record);
+ results.addAll(ret);
+ }
+ }
+ } else { //match found already
+ results.add(record);
+ }
+ }
+ return results;
+ }
+
+ private static Map map(Collection collection) {
+ Map ret = new HashMap<>();
+ for (PendingTransactionRecord record : collection) {
+ ret.put(record.id, record);
+ }
+ return ret;
+ }
+
+ private static void collectAncestors(Collection collector, String superiorId, AncestorPredicate predicate, Map map) {
+ PendingTransactionRecord superior = map.get(superiorId);
+ if (superior != null) {
+ if (predicate.holdsFor(superior)) {
+ collector.add(superior);
+ } else if (superior.superiorId != null) {
+ collectAncestors(collector, superior.superiorId, predicate, map);
+ }
+ }
+ }
+
+ public PendingTransactionRecord markAsTerminated() {
+ return new PendingTransactionRecord(id, TxState.TERMINATED, expires, recoveryDomainName, superiorId);
+ }
+
+ public PendingTransactionRecord markAsCommitting() {
+ return new PendingTransactionRecord(id, TxState.COMMITTING, expires, recoveryDomainName, superiorId);
+ }
+
+ @Override
+ public String toString() {
+ return toRecord();
+ }
+
+ /**
+ *
+ * @param recoveryDomainName
+ * @return True iff this is a foreign record in the given domain.
+ */
+ public boolean isForeignInDomain(String recoveryDomainName) {
+ return !this.recoveryDomainName.equals(recoveryDomainName);
+ }
+
+ public boolean isRecoveredByDomain(String recoveryDomainName) {
+ boolean ret = true;
+ if (isForeignInDomain(recoveryDomainName) && superiorId!= null && superiorId.startsWith("http")) {
+ // foreign record with remote recovery available =>
+ // this record is recovered by remote recovery in the foreign domain
+ ret = false;
+ }
+ return ret;
+ }
+
+ public boolean isLocalRoot(String recoveryDomainName) {
+ return isForeignInDomain(recoveryDomainName) || superiorId == null;
+ }
+
+ public boolean allowsHeuristicTermination(String recoveryDomainName) {
+ boolean ret = false;
+ if (isForeignInDomain(recoveryDomainName) &&
+ isRecoveredByDomain(recoveryDomainName) &&
+ state.equals(TxState.IN_DOUBT)) {
+ ret = true;
+ }
+ return ret;
+ }
+
+ @FunctionalInterface
+ public static interface AncestorPredicate {
+
+ boolean holdsFor(PendingTransactionRecord record);
+ }
+
+ public static Collection extractCoordinatorIds(Collection collection, TxState... statesToFilterOn) {
+ HashSet ret = new HashSet<>();
+ for (PendingTransactionRecord entry : collection) {
+ if (entry.state.isOneOf(statesToFilterOn)) {
+ ret.add(entry.id);
+ }
+ }
+ return ret;
+ }
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/RecoveryLog.java b/public/transactions-api/src/main/java/com/atomikos/recovery/RecoveryLog.java
new file mode 100644
index 000000000..4698d9a89
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/RecoveryLog.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+import java.util.Collection;
+
+ /**
+ * Handle to the transaction logs for recovery purposes.
+ */
+
+public interface RecoveryLog {
+
+ /**
+ * @return False if we don't have to do recovery because another instance is doing it.
+ */
+ boolean isActive();
+
+ /**
+ * Notification of JVM shutdown - allows another instance to take over.
+ */
+ void closing();
+
+ Collection getIndoubtTransactionRecords() throws LogReadException;
+
+ Collection getExpiredPendingCommittingTransactionRecordsAt(long time) throws LogReadException;
+
+ void forgetTransactionRecords(Collection coordinators);
+
+ /**
+ * Mark the given transaction as committing.
+ * @param coordinatorId The transaction, previously logged as IN_DOUBT.
+ * For retries, the IN_DOUBT may no longer exist.
+ * @throws LogException
+ */
+ void recordAsCommitting(String coordinatorId) throws LogException;
+
+ void forget(String coordinatorId);
+
+ PendingTransactionRecord get(String coordinatorId) throws LogReadException;
+
+ Collection getPendingTransactionRecords() throws LogReadException;
+
+ void closed();
+
+}
diff --git a/public/transactions-api/src/main/java/com/atomikos/recovery/TxState.java b/public/transactions-api/src/main/java/com/atomikos/recovery/TxState.java
new file mode 100644
index 000000000..aefaa9090
--- /dev/null
+++ b/public/transactions-api/src/main/java/com/atomikos/recovery/TxState.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+/**
+ * The states for a distributed transaction system.
+ */
+//@formatter:off
+public enum TxState {
+ //OLTP States
+ MARKED_ABORT (false, false),
+ LOCALLY_DONE (false, false),
+ COMMITTED (false, false),
+ ABORTED (false, false),
+ ABANDONED (false, false),
+
+ TERMINATED (true, true),
+ HEUR_HAZARD (false, false, TERMINATED, ABANDONED),
+ //Recoverable States
+ HEUR_COMMITTED (false, false, TERMINATED, ABANDONED),
+ HEUR_ABORTED (false, false, TERMINATED, ABANDONED),
+ HEUR_MIXED (false, false, TERMINATED, ABANDONED),
+ COMMITTING (true, false, HEUR_ABORTED, HEUR_COMMITTED, HEUR_HAZARD, HEUR_MIXED, TERMINATED, ABANDONED),
+ ABORTING (false, false, HEUR_ABORTED, HEUR_COMMITTED, HEUR_HAZARD, HEUR_MIXED, TERMINATED, ABANDONED),
+ IN_DOUBT (true, false, ABORTING, COMMITTING, ABANDONED, TERMINATED),
+ PREPARING (false, false, IN_DOUBT, ABORTING, TERMINATED, ABANDONED),
+ ACTIVE (false, false, ABORTING, COMMITTING, PREPARING);
+
+ private boolean recoverableState;
+
+ private boolean finalState;
+
+ private TxState[] legalNextStates;
+
+ TxState (boolean recoverableState, boolean finalState, TxState... legalNextStates) {
+ this.finalState=finalState;
+ this.recoverableState=recoverableState;
+ this.legalNextStates=legalNextStates;
+ }
+
+
+ public boolean isFinalState() {
+ return finalState;
+ }
+
+
+ public boolean isFinalStateForOltp() {
+ return isFinalState() || this == ABANDONED;
+ }
+
+ public boolean isRecoverableState() {
+ return recoverableState;
+ }
+
+ public boolean transitionAllowedTo(TxState nextState) {
+ //transition to the same state...
+ if(nextState == this) {
+ return true;
+ }
+
+ for (TxState txState : legalNextStates) {
+ if(txState == nextState) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isOneOf(TxState... state) {
+ for (int i = 0; i < state.length; i++) {
+ if(this==state[i])
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isHeuristic() {
+ return isOneOf(HEUR_ABORTED, HEUR_COMMITTED, HEUR_HAZARD, HEUR_MIXED);
+ }
+
+}
diff --git a/public/transactions-api/src/test/java/com/atomikos/icatch/ExtentTestJUnit.java b/public/transactions-api/src/test/java/com/atomikos/icatch/ExtentTestJUnit.java
new file mode 100644
index 000000000..47c3498db
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/icatch/ExtentTestJUnit.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ExtentTestJUnit {
+
+ private Extent extent;
+ private Participant p1;
+ private Map map;
+
+ @Before
+ public void setUp() throws Exception {
+ p1 = Mockito.mock(Participant.class);
+ Mockito.when(p1.getURI()).thenReturn("p1");
+ extent = new Extent();
+ map = new HashMap();
+ map.put("p2", 2);
+
+ }
+
+ @Test
+ public void testToStringWithParticipants() {
+ extent.add(p1, 1);
+ extent.addRemoteParticipants(map);
+ final String expected="version=2019,uri=p1,responseCount=1,direct=true,uri=p2,responseCount=2,direct=false";
+ assertEquals(expected,extent.toString());
+ }
+
+ @Test
+ public void testToStringWithOneDirectParticipant() {
+ extent.add(p1, 1);
+ final String expected="version=2019,uri=p1,responseCount=1,direct=true";
+ assertEquals(expected,extent.toString());
+ }
+
+ @Test
+ public void testToStringWithOneRemoteParticipant() {
+ extent.addRemoteParticipants(map);
+ final String expected="version=2019,uri=p2,responseCount=2,direct=false";
+ assertEquals(expected,extent.toString());
+ }
+
+ @Test
+ public void testToStringWithParentTransactionId() {
+ extent = new Extent("parentId");
+ final String expected = "version=2019,parent=parentId";
+ assertEquals(expected, extent.toString());
+ }
+
+ @Test
+ public void testMergeDoesNotContainDirectParticipants() {
+ extent.add(p1, 1);
+ assertFalse(extent.getParticipants().isEmpty());
+ Extent merged = new Extent(extent);
+ assertTrue(merged.getParticipants().isEmpty());
+ assertTrue(merged.getRemoteParticipants().containsKey(p1.getURI()));
+ }
+
+ @Test
+ public void testMergePreservesParentTransactionId() {
+ extent = new Extent("parentId");
+ extent.add(p1, 1);
+ assertNotNull(extent.getParentTransactionId());
+ Extent merged = new Extent(extent);
+ assertEquals(extent.getParentTransactionId(), merged.getParentTransactionId());
+ }
+
+}
diff --git a/public/transactions-api/src/test/java/com/atomikos/icatch/JDependTestJunit.java b/public/transactions-api/src/test/java/com/atomikos/icatch/JDependTestJunit.java
new file mode 100644
index 000000000..a75c0900d
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/icatch/JDependTestJunit.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import jdepend.framework.JDepend;
+
+public class JDependTestJunit {
+
+ @Test
+ public void test() throws IOException {
+ JDepend jdepend = new JDepend();
+ jdepend.addDirectory("target/classes");
+ jdepend.analyze();
+ Assert.assertFalse(jdepend.containsCycles());
+ }
+
+}
diff --git a/public/transactions-api/src/test/java/com/atomikos/icatch/PropagationTestJUnit.java b/public/transactions-api/src/test/java/com/atomikos/icatch/PropagationTestJUnit.java
new file mode 100644
index 000000000..cfa50dc7d
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/icatch/PropagationTestJUnit.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Properties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class PropagationTestJUnit {
+
+ private static final String PARENT_ID = "parent";
+ private static final String ROOT_ID = "root";
+ private static final long TIMEOUT = 100l;
+ private static final boolean SERIAL = true;
+ private static final String DOMAIN = "domain";
+ private static final String PROPERTY_KEY ="key";
+ private static final String PROPERTY_VALUE ="value";
+ private static final String COORDINATOR_ID = "coordinator";
+
+ private final String EXPECTED_TO_STRING =
+ "version=2019,domain="+DOMAIN+",timeout="+TIMEOUT+",serial="+SERIAL+",recoveryCoordinatorURI="+COORDINATOR_ID+
+ ",parent="+ROOT_ID+
+ ",parent="+PARENT_ID+","+"property."+
+ PROPERTY_KEY+"="+PROPERTY_VALUE;
+
+ private Propagation propagation;
+ private CompositeTransaction parent;
+ private CompositeTransaction root;
+
+ @Before
+ public void setUp() throws Exception {
+ parent = Mockito.mock(CompositeTransaction.class);
+ Mockito.when(parent.getTid()).thenReturn(PARENT_ID);
+ CompositeCoordinator coordinator = Mockito.mock(CompositeCoordinator.class);
+ Mockito.when(coordinator.getCoordinatorId()).thenReturn(COORDINATOR_ID);
+ Mockito.when(parent.getCompositeCoordinator()).thenReturn(coordinator);
+ Properties p = new Properties();
+ p.setProperty(PROPERTY_KEY, PROPERTY_VALUE);
+ Mockito.when(parent.getProperties()).thenReturn(p);
+ root = Mockito.mock(CompositeTransaction.class);
+ Mockito.when(root.getTid()).thenReturn(ROOT_ID);
+ Mockito.when(root.getProperties()).thenReturn(new Properties());
+ propagation = new Propagation(DOMAIN, root, parent, SERIAL, TIMEOUT);
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals(EXPECTED_TO_STRING, propagation.toString());
+ }
+
+}
diff --git a/public/transactions-api/src/test/java/com/atomikos/icatch/TxStateTestJUnit.java b/public/transactions-api/src/test/java/com/atomikos/icatch/TxStateTestJUnit.java
new file mode 100644
index 000000000..365ac66eb
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/icatch/TxStateTestJUnit.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch;
+
+import static com.atomikos.recovery.TxState.ABANDONED;
+import static com.atomikos.recovery.TxState.COMMITTING;
+import static com.atomikos.recovery.TxState.IN_DOUBT;
+import static com.atomikos.recovery.TxState.TERMINATED;
+import static org.junit.Assert.assertEquals;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.atomikos.recovery.TxState;
+
+public class TxStateTestJUnit {
+ @Test
+ public void testCountStates() {
+ TxState[] states = TxState.values();
+ assertEquals(15, states.length);
+ }
+
+ @Test
+ public void testRecoverableStates() throws Exception {
+ Set recoverableStates = EnumSet.of(IN_DOUBT, COMMITTING, TERMINATED);
+ TxState[] states = TxState.values();
+ for (TxState txState : states) {
+ if (recoverableStates.contains(txState)) {
+ Assert.assertTrue(txState.isRecoverableState());
+ } else {
+ Assert.assertFalse(txState.isRecoverableState());
+ }
+ }
+ }
+
+
+ @Test
+ public void testFinalStates() {
+ Set finalStates = EnumSet.of(TERMINATED);
+ TxState[] states = TxState.values();
+ for (TxState txState : states) {
+ if (finalStates.contains(txState)) {
+ Assert.assertTrue(txState.isFinalState());
+ } else {
+ Assert.assertFalse(txState.isFinalState());
+ }
+ }
+
+ }
+
+ @Test
+ public void testFinalStatesForOltp() {
+ Set finalStates = EnumSet.of(TERMINATED, ABANDONED);
+ TxState[] states = TxState.values();
+ for (TxState txState : states) {
+ if (finalStates.contains(txState)) {
+ Assert.assertTrue(txState.isFinalStateForOltp());
+ } else {
+ Assert.assertFalse(txState.isFinalStateForOltp());
+ }
+ }
+ }
+}
diff --git a/public/transactions-api/src/test/java/com/atomikos/icatch/provider/ConfigPropertiesTestJUnit.java b/public/transactions-api/src/test/java/com/atomikos/icatch/provider/ConfigPropertiesTestJUnit.java
new file mode 100644
index 000000000..6eb1aa2e5
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/icatch/provider/ConfigPropertiesTestJUnit.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.provider;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConfigPropertiesTestJUnit {
+
+ private static final String CUSTOM_PROPERTY_NAME = "bla";
+ private ConfigProperties props;
+
+ @Before
+ public void setUp() throws Exception {
+ props = new ConfigProperties(new Properties());
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testGetPropertyThrowsIfNotSet() {
+ props.getProperty("bla");
+ }
+
+ @Test
+ public void testGetAsBoolean() {
+ props.setProperty(CUSTOM_PROPERTY_NAME,"true");
+ Assert.assertTrue(props.getAsBoolean(CUSTOM_PROPERTY_NAME));
+ }
+
+ @Test
+ public void testGetAsInt() {
+ props.setProperty(CUSTOM_PROPERTY_NAME, "10");
+ Assert.assertEquals(10, props.getAsInt(CUSTOM_PROPERTY_NAME));
+ }
+
+ @Test
+ public void testGetAsLong() {
+ props.setProperty(CUSTOM_PROPERTY_NAME, "10");
+ Assert.assertEquals(10, props.getAsLong(CUSTOM_PROPERTY_NAME));
+ }
+
+ @Test
+ public void testGetPropertyTrimsSpaces() {
+ props.setProperty(CUSTOM_PROPERTY_NAME, " bla ");
+ assertEquals("bla", props.getProperty(CUSTOM_PROPERTY_NAME));
+ }
+
+ @Test
+ public void testGetLogBaseDir() {
+ props.setProperty("com.atomikos.icatch.log_base_dir", "bla");
+ assertEquals("bla", props.getLogBaseDir());
+ }
+
+ @Test
+ public void testGetLogBaseName() {
+ props.setProperty("com.atomikos.icatch.log_base_name", "bla");
+ assertEquals("bla", props.getLogBaseName());
+ }
+
+ @Test
+ public void testGetEnableLogging() {
+ props.setProperty("com.atomikos.icatch.enable_logging", "true");
+ assertTrue(props.getEnableLogging());
+ }
+
+ @Test
+ public void testMaxTimeout() {
+ props.setProperty("com.atomikos.icatch.max_timeout", "30000");
+ assertEquals(30000, props.getMaxTimeout());
+ }
+
+ @Test
+ public void testMaxActives() {
+ props.setProperty("com.atomikos.icatch.max_actives", "100");
+ assertEquals(100, props.getMaxActives());
+ }
+
+ @Test
+ public void testMergeProperties() {
+ props.setProperty(CUSTOM_PROPERTY_NAME, "bla");
+ Properties userSpecificProperties = new Properties();
+ userSpecificProperties.setProperty("userSpecific", "userBla");
+ props.applyUserSpecificProperties(userSpecificProperties);
+ assertEquals("bla" , props.getProperty(CUSTOM_PROPERTY_NAME));
+ assertEquals("userBla", props.getProperty("userSpecific"));
+ }
+
+ @Test
+ public void testDefaultTmUniqueName() {
+ assertNotNull(props.getTmUniqueName());
+ }
+
+ @Test
+ public void testDefaultJvmId() {
+ assertNotNull(props.getJvmId());
+ }
+
+ @Test
+ public void testDefaultJvmIdDoesNotOverrideAnyCustomSetting() {
+ final String NAME = "bla";
+ Properties p = createDefaultProperties(ConfigProperties.JVM_ID_PROPERTY_NAME, NAME);
+ props = new ConfigProperties(p);
+ assertEquals(NAME, props.getJvmId());
+ }
+
+ @Test
+ public void testGetPropertiesIncludesSystemProperties() {
+ final String name = "com.atomikos.icatch.testGetPropertiesIncludesSystemProperties";
+ System.setProperty(name, "bla");
+ assertNotNull(props.getCompletedProperties().getProperty(name));
+ }
+
+ @Test
+ public void testForceShutdownOnVmExit() {
+ props.setProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "true");
+ Assert.assertTrue(props.getForceShutdownOnVmExit());
+ props.setProperty("com.atomikos.icatch.force_shutdown_on_vm_exit", "false");
+ Assert.assertFalse(props.getForceShutdownOnVmExit());
+ }
+
+
+ private Properties createDefaultProperties(String name, String value) {
+ Properties ret = new Properties();
+ ret.setProperty(name, value);
+ return ret;
+ }
+
+ @Test
+ public void testDefaultTmUniqueNameDoesNotOverrideAnyCustomSetting() {
+ final String NAME = "bla";
+ Properties p = createDefaultProperties(ConfigProperties.TM_UNIQUE_NAME_PROPERTY_NAME, NAME);
+ props = new ConfigProperties(p);
+ assertEquals(NAME, props.getTmUniqueName());
+ }
+ @Test
+ public void testForgetOrphanedLogEntriesDelay() throws Exception {
+ props.setProperty("com.atomikos.icatch.forget_orphaned_log_entries_delay", "1800000");
+ assertEquals(TimeUnit.MINUTES.toMillis(30), props.getForgetOrphanedLogEntriesDelay());
+ }
+
+ @Test
+ public void testRecoveryDelay() throws Exception {
+ final long VALUE = 12345L;
+ props.setProperty("com.atomikos.icatch.recovery_delay", Long.toString(VALUE));
+ assertEquals(VALUE, props.getRecoveryDelay());
+ }
+
+ @Test
+ public void testOltpMaxRetries() throws Exception {
+ final int VALUE = 123;
+ props.setProperty("com.atomikos.icatch.oltp_max_retries", Integer.toString(VALUE));
+ assertEquals(VALUE, props.getOltpMaxRetries());
+ }
+
+ @Test
+ public void testOltpRetryInterval() throws Exception {
+ final long VALUE = 2345l;
+ props.setProperty("com.atomikos.icatch.oltp_retry_interval", Long.toString(VALUE));
+ assertEquals(VALUE, props.getOltpRetryInterval());
+ }
+
+ @Test
+ public void testAllowSubTransactions() throws Exception {
+ final boolean VALUE = false;
+ props.setProperty("com.atomikos.icatch.allow_subtransactions", "false");
+ assertEquals(VALUE, props.getAllowSubTransactions());
+ }
+}
+
diff --git a/public/transactions-api/src/test/java/com/atomikos/recovery/PendingTransactionRecordTestJUnit.java b/public/transactions-api/src/test/java/com/atomikos/recovery/PendingTransactionRecordTestJUnit.java
new file mode 100644
index 000000000..d5de714dd
--- /dev/null
+++ b/public/transactions-api/src/test/java/com/atomikos/recovery/PendingTransactionRecordTestJUnit.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.recovery;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+
+import org.junit.Test;
+
+public class PendingTransactionRecordTestJUnit {
+
+ PendingTransactionRecord given;
+ @Test
+ public void testToRecord() throws Exception {
+ String id = "id1";
+ TxState state = TxState.COMMITTING;
+ long expires = 100l;
+ given = new PendingTransactionRecord(id, state, expires, "domain");
+ String record = given.toRecord();
+ assertEquals("id1|COMMITTING|100|domain|"+System.lineSeparator(), record);
+ }
+ @Test
+ public void testFromRecord() throws Exception {
+ String givenRecord = "id1|COMMITTING|100|domain|123";
+ PendingTransactionRecord pendingTransactionRecord = PendingTransactionRecord.fromRecord(givenRecord);
+ assertEquals("id1", pendingTransactionRecord.id);
+ assertEquals(TxState.COMMITTING, pendingTransactionRecord.state);
+ assertEquals(100, pendingTransactionRecord.expires);
+ assertEquals("123", pendingTransactionRecord.superiorId);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFromPartialRecordThrows() {
+ String givenRecord = "id1|COMMITTING|";
+ PendingTransactionRecord.fromRecord(givenRecord);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFromInvalidRecordThrows() {
+ String givenRecord = "id1-COMMITTING-";
+ PendingTransactionRecord.fromRecord(givenRecord);
+ }
+
+ @Test
+ public void testRootIsNativeInItsOwnDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain");
+ assertFalse(given.isForeignInDomain("domain"));
+ }
+
+ @Test
+ public void testLocalSubtransactionIsNativeInItsOwnDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "localSuperiorId");
+ assertFalse(given.isForeignInDomain("domain"));
+ }
+
+ @Test
+ public void testImportedTransactionIsForeignInImportingDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "localSuperiorId");
+ assertTrue(given.isForeignInDomain("importingDomain"));
+ }
+
+ @Test
+ public void testRootIsRecoveredByItsOwnDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain");
+ assertTrue(given.isRecoveredByDomain("domain"));
+
+ }
+
+ @Test
+ public void testImportedTransactionWithoutSuperiorUrlIsRecoveredByImportingDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "foreignDomain", "superiorId");
+ assertTrue(given.isRecoveredByDomain("importingDomain"));
+ }
+
+ @Test
+ public void testImportedTransactionWithSuperiorUrlIsNotRecoveredByImportingDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "foreignDomain", "https://superiorId");
+ assertFalse(given.isRecoveredByDomain("importingDomain"));
+ }
+
+
+ @Test
+ public void testLocalSubtransactionIsRecoveredByItsOwnDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "localSuperiorId");
+ assertTrue(given.isRecoveredByDomain("domain"));
+ }
+
+ @Test
+ public void testRootDoesNotAllowHeuristicAbort() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain");
+ assertFalse(given.allowsHeuristicTermination("domain"));
+ }
+
+ @Test
+ public void testLocalSubtransactionDoesNotAllowHeuristicAbort() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "localSuperiorId");
+ assertFalse(given.allowsHeuristicTermination("domain"));
+ }
+
+ @Test
+ public void testImportedTransactionWithoutSuperiorUrlAllowsHeuristicAbortByImportingDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "foreignDomain", "superiorId");
+ assertTrue(given.allowsHeuristicTermination("importingDomain"));
+ }
+
+ @Test
+ public void testImportedTransactionWithSuperiorUrlDoesNotAllowHeuristicAbortByImportingDomain() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "foreignDomain", "http://superiorId");
+ assertFalse(given.allowsHeuristicTermination("importingDomain"));
+ }
+
+ @Test
+ public void testImportedTransactionFromSameDomainDoesNotAllowheuristicAbort() {
+ given = new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "http://superiorId/");
+ assertFalse(given.allowsHeuristicTermination("domain"));
+ }
+
+ @Test
+ public void testFindAllDescendants() {
+ given = new PendingTransactionRecord("rootId", TxState.IN_DOUBT, 0, "domain");
+ Collection set = new HashSet();
+ set.add(new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "superiorId"));
+ set.add(new PendingTransactionRecord("superiorId", TxState.IN_DOUBT, 0, "domain", "rootId"));
+ set.add(given);
+ Collection result = PendingTransactionRecord.findAllDescendants(given, set);
+ assertEquals(2, result.size());
+ }
+
+ @Test
+ public void testRemoveAllDescendants() {
+ given = new PendingTransactionRecord("rootId", TxState.IN_DOUBT, 0, "domain");
+ Collection set = new HashSet();
+ set.add(new PendingTransactionRecord("id", TxState.IN_DOUBT, 0, "domain", "superiorId"));
+ set.add(new PendingTransactionRecord("superiorId", TxState.IN_DOUBT, 0, "domain", "rootId"));
+ set.add(given);
+ PendingTransactionRecord.removeAllDescendants(given, set);
+ assertEquals(1, set.size());
+ }
+
+ @Test
+ public void testExtractCoordinatorIds() {
+ given = new PendingTransactionRecord("rootId", TxState.IN_DOUBT, 0, "domain");
+ Collection result = PendingTransactionRecord.extractCoordinatorIds(Collections.singleton(given), TxState.IN_DOUBT);
+ assertFalse(result.isEmpty());
+ result = PendingTransactionRecord.extractCoordinatorIds(Collections.singleton(given), TxState.COMMITTING);
+ assertTrue(result.isEmpty());
+ }
+}
diff --git a/public/transactions-eclipselink/pom.xml b/public/transactions-eclipselink/pom.xml
new file mode 100644
index 000000000..9d4bb2aa1
--- /dev/null
+++ b/public/transactions-eclipselink/pom.xml
@@ -0,0 +1,28 @@
+
+ 4.0.0
+
+ ate
+ com.atomikos
+ 6.0.1-SNAPSHOT
+
+ transactions-eclipselink
+
+
+ org.eclipse.persistence
+ eclipselink
+ 2.7.12
+ provided
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ org.apache.geronimo.specs
+ geronimo-jta_1.0.1B_spec
+ 1.0
+ provided
+
+
+
\ No newline at end of file
diff --git a/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosPlatform.java b/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosPlatform.java
new file mode 100644
index 000000000..2db20d2fe
--- /dev/null
+++ b/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosPlatform.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.eclipselink.platform;
+
+import org.eclipse.persistence.platform.server.ServerPlatformBase;
+import org.eclipse.persistence.sessions.DatabaseSession;
+
+import com.atomikos.util.Atomikos;
+
+public class AtomikosPlatform extends ServerPlatformBase {
+
+ public AtomikosPlatform(DatabaseSession newDatabaseSession) {
+ super(newDatabaseSession);
+ disableRuntimeServices();
+ }
+
+ @Override
+ public Class> getExternalTransactionControllerClass() {
+
+ return AtomikosTransactionController.class;
+ }
+
+ @Override
+ protected void initializeServerNameAndVersion() {
+ this.serverNameAndVersion="Atomikos: "+Atomikos.VERSION;
+ }
+}
diff --git a/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosTransactionController.java b/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosTransactionController.java
new file mode 100644
index 000000000..5f64821d5
--- /dev/null
+++ b/public/transactions-eclipselink/src/main/java/com/atomikos/eclipselink/platform/AtomikosTransactionController.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.eclipselink.platform;
+
+import javax.transaction.TransactionManager;
+
+import org.eclipse.persistence.transaction.JTATransactionController;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+public class AtomikosTransactionController extends JTATransactionController {
+
+ private UserTransactionManager utm;
+
+ public AtomikosTransactionController() {
+ utm = new UserTransactionManager();
+ }
+ /**
+ * INTERNAL: Obtain and return the JTA TransactionManager on this platform
+ */
+ protected TransactionManager acquireTransactionManager() throws Exception {
+ return utm;
+ }
+
+ @Override
+ public TransactionManager getTransactionManager() {
+
+ return utm;
+ }
+
+}
diff --git a/public/transactions-essentials-jakarta/pom.xml b/public/transactions-essentials-jakarta/pom.xml
new file mode 100644
index 000000000..aa943aa51
--- /dev/null
+++ b/public/transactions-essentials-jakarta/pom.xml
@@ -0,0 +1,94 @@
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ transactions-essentials-jakarta
+ pom
+ transactions-essentials-jakarta
+ Atomikos TransactionsEssentials Jakarta BOM
+ https://www.atomikos.com/Main/TransactionsEssentials
+
+
+ https://www.atomikos.com/Main/WhichLicenseApplies
+
+
+
+
+ Atomikos
+ info@atomikos.com
+ Atomikos
+ https://www.atomikos.com
+
+
+
+
+
+
+ com.atomikos
+ transactions-jdbc
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions-jms
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions-hibernate4
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions-jndi-provider
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-api
+ 6.0.1-SNAPSHOT
+
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions-osgi
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ atomikos-util
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-remoting
+ 6.0.1-SNAPSHOT
+ jakarta
+
+
+ com.atomikos
+ transactions-spring-boot3-starter
+ 6.0.1-SNAPSHOT
+
+
+
+
diff --git a/public/transactions-essentials/pom.xml b/public/transactions-essentials/pom.xml
new file mode 100644
index 000000000..c160d4d39
--- /dev/null
+++ b/public/transactions-essentials/pom.xml
@@ -0,0 +1,101 @@
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ transactions-essentials
+ pom
+ transactions-essentials
+ Atomikos TransactionsEssentials BOM
+ https://www.atomikos.com/Main/TransactionsEssentials
+
+
+ https://www.atomikos.com/Main/WhichLicenseApplies
+
+
+
+
+ Atomikos
+ info@atomikos.com
+ Atomikos
+ https://www.atomikos.com
+
+
+
+
+
+
+ com.atomikos
+ transactions-jdbc
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jms
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-hibernate3
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-hibernate4
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-hibernate2
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-eclipselink
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jndi-provider
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-api
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-osgi
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ atomikos-util
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-remoting
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-spring-boot-starter
+ 6.0.1-SNAPSHOT
+
+
+
+
diff --git a/public/transactions-hibernate2/pom.xml b/public/transactions-hibernate2/pom.xml
new file mode 100644
index 000000000..a3481b968
--- /dev/null
+++ b/public/transactions-hibernate2/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ transactions-hibernate2
+ Transactions Hibernate2
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ net.sf.hibernate
+ hibernate
+ 2.1.8
+ provided
+
+
+ org.apache.geronimo.specs
+ geronimo-jta_1.0.1B_spec
+ 1.0
+ provided
+
+
+
diff --git a/public/transactions-hibernate2/src/main/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookup.java b/public/transactions-hibernate2/src/main/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookup.java
new file mode 100644
index 000000000..0c1c13eda
--- /dev/null
+++ b/public/transactions-hibernate2/src/main/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookup.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.jta.hibernate;
+
+import java.util.Properties;
+
+import javax.transaction.TransactionManager;
+
+import com.atomikos.icatch.jta.UserTransactionManager;
+
+import net.sf.hibernate.HibernateException;
+
+/**
+ *
+ *
+ *
+ * This class is provided for Hibernate integration.
+ * To use Atomikos as the Hibernate JTA transaction manager,
+ * specify this class as the value of the
+ * hibernate.transaction.manager_lookup_class of the
+ * hibernate configuration properties.
+ *
+ */
+public class TransactionManagerLookup
+ implements net.sf.hibernate.transaction.TransactionManagerLookup
+{
+
+ UserTransactionManager utm;
+
+ public TransactionManagerLookup()
+ {
+ utm = new UserTransactionManager();
+ }
+
+
+ /* (non-Javadoc)
+ * @see net.sf.hibernate.transaction.TransactionManagerLookup#getTransactionManager(java.util.Properties)
+ */
+ public TransactionManager getTransactionManager(Properties arg0)
+ throws HibernateException
+ {
+ return utm;
+ }
+
+ /* (non-Javadoc)
+ * @see net.sf.hibernate.transaction.TransactionManagerLookup#getUserTransactionName()
+ */
+ public String getUserTransactionName()
+ {
+
+ return null;
+ }
+
+}
diff --git a/public/transactions-hibernate2/src/test/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookupTestJUnit.java b/public/transactions-hibernate2/src/test/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookupTestJUnit.java
new file mode 100644
index 000000000..077695c98
--- /dev/null
+++ b/public/transactions-hibernate2/src/test/java/com/atomikos/icatch/jta/hibernate/TransactionManagerLookupTestJUnit.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.jta.hibernate;
+
+import junit.framework.TestCase;
+import net.sf.hibernate.HibernateException;
+
+public class TransactionManagerLookupTestJUnit extends TestCase {
+
+ private TransactionManagerLookup lookup;
+
+ protected void setUp() throws Exception {
+ super.setUp();
+ lookup = new TransactionManagerLookup();
+ }
+
+ public void testTransactionManager() throws HibernateException {
+ assertNotNull ( lookup.getTransactionManager( null) );
+ }
+
+ public void testName() throws Exception
+ {
+ assertNull ( lookup.getUserTransactionName() );
+ }
+
+}
diff --git a/public/transactions-hibernate3/pom.xml b/public/transactions-hibernate3/pom.xml
new file mode 100644
index 000000000..66e7f549a
--- /dev/null
+++ b/public/transactions-hibernate3/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+
+ com.atomikos
+ ate
+ 6.0.1-SNAPSHOT
+
+ transactions-hibernate3
+ Transactions Hibernate3
+
+
+ com.atomikos
+ transactions-jta
+ 6.0.1-SNAPSHOT
+
+
+ com.atomikos
+ transactions-jdbc
+ 6.0.1-SNAPSHOT
+
+
+ org.hibernate
+ hibernate
+ 3.2.5.ga
+ provided
+
+
+ javax.transaction
+ jta
+
+
+
+
+ org.apache.geronimo.specs
+ geronimo-jta_1.0.1B_spec
+ 1.0
+ provided
+
+
+ org.slf4j
+ slf4j-api
+ 1.4.3
+ provided
+
+
+ org.slf4j
+ slf4j-simple
+ 1.4.3
+ test
+
+
+
diff --git a/public/transactions-hibernate3/src/main/java/com/atomikos/icatch/jta/hibernate3/AtomikosConnectionProvider.java b/public/transactions-hibernate3/src/main/java/com/atomikos/icatch/jta/hibernate3/AtomikosConnectionProvider.java
new file mode 100644
index 000000000..d68f38a4c
--- /dev/null
+++ b/public/transactions-hibernate3/src/main/java/com/atomikos/icatch/jta/hibernate3/AtomikosConnectionProvider.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (C) 2000-2024 Atomikos
+ *
+ * LICENSE CONDITIONS
+ *
+ * See http://www.atomikos.com/Main/WhichLicenseApplies for details.
+ */
+
+package com.atomikos.icatch.jta.hibernate3;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import org.hibernate.HibernateException;
+import org.hibernate.connection.ConnectionProvider;
+
+import com.atomikos.beans.PropertyException;
+import com.atomikos.beans.PropertyUtils;
+import com.atomikos.jdbc.AtomikosDataSourceBean;
+import com.atomikos.jdbc.AtomikosNonXADataSourceBean;
+import com.atomikos.jdbc.internal.AbstractDataSourceBean;
+import com.atomikos.logging.Logger;
+import com.atomikos.logging.LoggerFactory;
+
+/**
+ * Atomikos-specific ConnectionProvider implementation that can
+ * create Atomikos connection pools directly from the Hibernate
+ * configuration.
+ *
+ *
To use the AtomikosConnectionProvider specify this class as the
+ * value of the hibernate.connection.provider_class of the
+ * hibernate configuration properties.
+ * You then have to configure the {@link AtomikosDataSourceBean} properties
+ * by prefixing them with hibernate.connection.atomikos.
+ * Eg: hibernate.connection.atomikos.uniqueResourceName
+ * hibernate.connection.atomikos.xaDataSourceClassName
+ * hibernate.connection.atomikos.xaProperties.databaseName
+ * ...
+ * Add a hibernate.connection.atomikos.nonxa=true property if you want
+ * to configure a {@link AtomikosNonXADataSourceBean} instead.
+ *
+ *
+ *
+ * NOTE: if you use the Hibernate XML config mechanism,
+ * then the prefix should be connection.atomikos instead (without hibernate prefix).
+ *