You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2021/02/09 07:44:28 UTC
[ignite-extensions] branch master updated: IGNITE-13992 Migrate
Spring Transactions integration to ignite-extensions - Fixes #33.
This is an automated email from the ASF dual-hosted git repository.
alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite-extensions.git
The following commit(s) were added to refs/heads/master by this push:
new 63c6e0b IGNITE-13992 Migrate Spring Transactions integration to ignite-extensions - Fixes #33.
63c6e0b is described below
commit 63c6e0b4f945dd445d1b704efece27ff1da5583b
Author: Mikhail Petrov <pm...@gmail.com>
AuthorDate: Tue Feb 9 10:40:54 2021 +0300
IGNITE-13992 Migrate Spring Transactions integration to ignite-extensions - Fixes #33.
Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
modules/spring-tx-ext/README.txt | 52 +++
modules/spring-tx-ext/licenses/apache-2.0.txt | 202 ++++++++++++
.../modules/core/src/test/config/log4j-test.xml | 97 ++++++
.../modules/core/src/test/config/tests.properties | 22 ++
modules/spring-tx-ext/pom.xml | 91 ++++++
.../transactions/proxy/ClientTransactionProxy.java | 60 ++++
.../proxy/ClientTransactionProxyFactory.java | 61 ++++
.../transactions/proxy/IgniteTransactionProxy.java | 60 ++++
.../proxy/IgniteTransactionProxyFactory.java | 62 ++++
.../transactions/proxy/TransactionProxy.java | 41 +++
.../proxy/TransactionProxyFactory.java | 27 ++
.../spring/AbstractSpringTransactionManager.java | 309 ++++++++++++++++++
.../IgniteClientSpringTransactionManager.java | 117 +++++++
.../spring/IgniteTransactionHolder.java | 98 ++++++
.../spring/SpringTransactionManager.java | 359 +++++++++++++++++++++
.../ignite/transactions/spring/package-info.java | 22 ++
.../spring-transactions-ignite-spring-bean.xml | 67 ++++
.../src/test/java/config/spring-transactions.xml | 35 ++
.../apache/ignite/TestInjectionLifecycleBean.java | 42 +++
.../org/apache/ignite/spring-injection-test.xml | 43 +++
.../IgniteSpringTransactionsTestSuite.java | 38 +++
.../GridSpringTransactionManagerAbstractTest.java | 142 ++++++++
.../GridSpringTransactionManagerSelfTest.java | 67 ++++
...SpringTransactionManagerSpringBeanSelfTest.java | 59 ++++
.../spring/GridSpringTransactionService.java | 149 +++++++++
.../IgniteClientSpringTransactionManagerTest.java | 118 +++++++
...ringTransactionManagerContextInjectionTest.java | 128 ++++++++
parent/pom.xml | 4 +
pom.xml | 1 +
29 files changed, 2573 insertions(+)
diff --git a/modules/spring-tx-ext/README.txt b/modules/spring-tx-ext/README.txt
new file mode 100644
index 0000000..5c1e72f
--- /dev/null
+++ b/modules/spring-tx-ext/README.txt
@@ -0,0 +1,52 @@
+Apache Ignite Spring Transactions Module
+---------------------------
+
+Apache Ignite Spring Transactions extension provides an integration with Spring Transactions framework.
+
+To get started with Apache Ignite Spring Transactions, create instance of Apache Ignite Spring Transactions Manager as bean in your Spring application.
+
+There are two implementations of Apache Ignite Spring Transactions Manager - org.apache.ignite.transactions.spring.SpringTransactionManager and org.apache.ignite.transactions.spring.IgniteClientSpringTransactionManager, that provide ability to use the Ignite thick or thin client to connect to the Ignite cluster and manage Ignite transactions, respectively.
+
+Importing Spring Transactions extension In Maven Project
+----------------------------------------
+
+If you are using Maven to manage dependencies of your project, you can add Spring Transactions extension dependency like this (replace '${ignite-spring-tx-ext.version}', '${ignite-spring.version}' and '${ignite.version}' with actual version of Ignite Spring Transactions extension, Spring Transactions and Ignite you are interested in, respectively):
+
+<!-- Please note that for Ignite versions earlier than 2.11, the ignite-spring-tx-ext dependency must be added to classpath before ignite-spring, due to duplication of Spring Transactions integration classes. If you are using Maven to manage dependencies, it just needs to place ignite-spring-tx-ext before ignite-spring dependency in your pom file. --!>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+ http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ ...
+ <dependencies>
+ ...
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-spring-tx-ext</artifactId>
+ <version>${ignite-spring-transactions.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-spring</artifactId>
+ <version>${ignite.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-core</artifactId>
+ <version>${ignite.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+
+ ...
+ </dependencies>
+ ...
+</project>
diff --git a/modules/spring-tx-ext/licenses/apache-2.0.txt b/modules/spring-tx-ext/licenses/apache-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/modules/spring-tx-ext/licenses/apache-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/modules/spring-tx-ext/modules/core/src/test/config/log4j-test.xml b/modules/spring-tx-ext/modules/core/src/test/config/log4j-test.xml
new file mode 100755
index 0000000..3061bd4
--- /dev/null
+++ b/modules/spring-tx-ext/modules/core/src/test/config/log4j-test.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN"
+ "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
+<!--
+ Log4j configuration.
+-->
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
+ <!--
+ Logs System.out messages to console.
+ -->
+ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+ <!-- Log to STDOUT. -->
+ <param name="Target" value="System.out"/>
+
+ <!-- Log from DEBUG and higher. -->
+ <param name="Threshold" value="DEBUG"/>
+
+ <!-- The default pattern: Date Priority [Category] Message\n -->
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"/>
+ </layout>
+
+ <!-- Do not log beyond INFO level. -->
+ <filter class="org.apache.log4j.varia.LevelRangeFilter">
+ <param name="levelMin" value="DEBUG"/>
+ <param name="levelMax" value="INFO"/>
+ </filter>
+ </appender>
+
+ <!--
+ Logs all System.err messages to console.
+ -->
+ <appender name="CONSOLE_ERR" class="org.apache.log4j.ConsoleAppender">
+ <!-- Log to STDERR. -->
+ <param name="Target" value="System.err"/>
+
+ <!-- Log from WARN and higher. -->
+ <param name="Threshold" value="WARN"/>
+
+ <!-- The default pattern: Date Priority [Category] Message\n -->
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"/>
+ </layout>
+ </appender>
+
+ <!--
+ Logs all output to specified file.
+ -->
+ <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
+ <param name="Threshold" value="DEBUG"/>
+ <param name="File" value="ignite/work/log/ignite.log"/>
+ <param name="Append" value="true"/>
+ <param name="MaxFileSize" value="10MB"/>
+ <param name="MaxBackupIndex" value="10"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="[%d{ISO8601}][%-5p][%t][%c{1}] %m%n"/>
+ </layout>
+ </appender>
+
+ <!-- Disable all open source debugging. -->
+ <category name="org">
+ <level value="INFO"/>
+ </category>
+
+ <category name="org.eclipse.jetty">
+ <level value="INFO"/>
+ </category>
+
+ <!-- Default settings. -->
+ <root>
+ <!-- Print at info by default. -->
+ <level value="INFO"/>
+
+ <!-- Append to file and console. -->
+ <appender-ref ref="FILE"/>
+ <appender-ref ref="CONSOLE"/>
+ <appender-ref ref="CONSOLE_ERR"/>
+ </root>
+</log4j:configuration>
diff --git a/modules/spring-tx-ext/modules/core/src/test/config/tests.properties b/modules/spring-tx-ext/modules/core/src/test/config/tests.properties
new file mode 100644
index 0000000..0faf5b8
--- /dev/null
+++ b/modules/spring-tx-ext/modules/core/src/test/config/tests.properties
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Local address to bind to.
+local.ip=127.0.0.1
+
+# TCP communication port
+comm.tcp.port=30010
diff --git a/modules/spring-tx-ext/pom.xml b/modules/spring-tx-ext/pom.xml
new file mode 100644
index 0000000..adfe9ea
--- /dev/null
+++ b/modules/spring-tx-ext/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-extensions-parent</artifactId>
+ <version>1</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <artifactId>ignite-spring-tx-ext</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <url>http://ignite.apache.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-core</artifactId>
+ <version>${ignite.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-spring</artifactId>
+ <version>${ignite.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ </exclusion>
+ </exclusions>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ <version>${spring.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-log4j</artifactId>
+ <version>${ignite.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-core</artifactId>
+ <version>${ignite.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <testResources>
+ <testResource>
+ <directory>src/test/java</directory>
+ <excludes>
+ <exclude>**/*.java</exclude>
+ </excludes>
+ </testResource>
+ </testResources>
+ </build>
+</project>
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxy.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxy.java
new file mode 100644
index 0000000..eca575b
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxy.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+import org.apache.ignite.client.ClientTransaction;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Represents {@link TransactionProxy} implementation that uses {@link ClientTransaction} to perform transaction
+ * operations.
+ */
+public class ClientTransactionProxy implements TransactionProxy {
+ /** */
+ private final ClientTransaction tx;
+
+ /** */
+ public ClientTransactionProxy(ClientTransaction tx) {
+ this.tx = tx;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void commit() {
+ tx.commit();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void rollback() {
+ tx.rollback();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void close() {
+ tx.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean setRollbackOnly() {
+ throw new UnsupportedOperationException("Operation is not supported by thin client.");
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(ClientTransactionProxy.class, this);
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxyFactory.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxyFactory.java
new file mode 100644
index 0000000..440867d
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/ClientTransactionProxyFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+import org.apache.ignite.client.ClientTransactions;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+
+/**
+ * Represents {@link TransactionProxyFactory} implementation that uses Ignite thin client transaction facade to start
+ * new transaction.
+ */
+public class ClientTransactionProxyFactory implements TransactionProxyFactory {
+ /** */
+ private final ClientTransactions txs;
+
+ /** */
+ public ClientTransactionProxyFactory(ClientTransactions txs) {
+ this.txs = txs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public TransactionProxy txStart(
+ TransactionConcurrency concurrency,
+ TransactionIsolation isolation,
+ long timeout
+ ) {
+ return new ClientTransactionProxy(txs.txStart(concurrency, isolation, timeout));
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object other) {
+ if (this == other)
+ return true;
+
+ if (other == null || getClass() != other.getClass())
+ return false;
+
+ return txs.equals(((ClientTransactionProxyFactory)other).txs);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return txs.hashCode();
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxy.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxy.java
new file mode 100644
index 0000000..15eaaae
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxy.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.transactions.Transaction;
+
+/**
+ * Represents {@link TransactionProxy} implementation that uses {@link Transaction} to perform transaction
+ * operations.
+ */
+public class IgniteTransactionProxy implements TransactionProxy {
+ /** */
+ private final Transaction tx;
+
+ /** */
+ public IgniteTransactionProxy(Transaction tx) {
+ this.tx = tx;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void commit() {
+ tx.commit();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void rollback() {
+ tx.rollback();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void close() {
+ tx.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean setRollbackOnly() {
+ return tx.setRollbackOnly();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(IgniteTransactionProxy.class, this);
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxyFactory.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxyFactory.java
new file mode 100644
index 0000000..efc0c51
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/IgniteTransactionProxyFactory.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+import java.util.Objects;
+import org.apache.ignite.IgniteTransactions;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+
+/**
+ * Represents {@link TransactionProxyFactory} implementation that uses Ignite node transaction facade to start new
+ * transaction.
+ */
+public class IgniteTransactionProxyFactory implements TransactionProxyFactory {
+ /** */
+ private final IgniteTransactions txs;
+
+ /** */
+ public IgniteTransactionProxyFactory(IgniteTransactions txs) {
+ this.txs = txs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public TransactionProxy txStart(
+ TransactionConcurrency concurrency,
+ TransactionIsolation isolation,
+ long timeout
+ ) {
+ return new IgniteTransactionProxy(txs.txStart(concurrency, isolation, timeout, 0));
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object other) {
+ if (this == other)
+ return true;
+
+ if (other == null || getClass() != other.getClass())
+ return false;
+
+ return txs.equals(((IgniteTransactionProxyFactory)other).txs);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(txs);
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxy.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxy.java
new file mode 100644
index 0000000..a40a5bd
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxy.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+/** Represents Ignite client-independent transaction operations. */
+public interface TransactionProxy extends AutoCloseable {
+ /** Commits this transaction. */
+ public void commit();
+
+ /** Rolls back this transaction. */
+ public void rollback();
+
+ /** Ends the transaction. Transaction will be rolled back if it has not been committed. */
+ @Override public void close();
+
+ /**
+ * Modify the transaction associated with the current thread such that the
+ * only possible outcome of the transaction is to roll back the
+ * transaction.
+ *
+ * @return {@code True} if rollback-only flag was set as a result of this operation,
+ * {@code false} if it was already set prior to this call or could not be set
+ * because transaction is already finishing up committing or rolling back.
+ */
+ public boolean setRollbackOnly();
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxyFactory.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxyFactory.java
new file mode 100644
index 0000000..5484723
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/internal/transactions/proxy/TransactionProxyFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.transactions.proxy;
+
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+
+/** Represents Ignite client-independent transaction factory. */
+public interface TransactionProxyFactory {
+ /** Starts transaction with specified concurrency, isolation and timeout. */
+ public TransactionProxy txStart(TransactionConcurrency concurrency, TransactionIsolation isolation, long timeout);
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/AbstractSpringTransactionManager.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/AbstractSpringTransactionManager.java
new file mode 100644
index 0000000..717baeb
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/AbstractSpringTransactionManager.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.transactions.proxy.TransactionProxy;
+import org.apache.ignite.internal.transactions.proxy.TransactionProxyFactory;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.transaction.CannotCreateTransactionException;
+import org.springframework.transaction.InvalidIsolationLevelException;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.TransactionSystemException;
+import org.springframework.transaction.support.AbstractPlatformTransactionManager;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+import org.springframework.transaction.support.ResourceTransactionManager;
+import org.springframework.transaction.support.SmartTransactionObject;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.transaction.support.TransactionSynchronizationUtils;
+
+/** Abstract implementation of Spring Transaction manager with omitted Ignite cluster access logic. */
+public abstract class AbstractSpringTransactionManager extends AbstractPlatformTransactionManager
+ implements ResourceTransactionManager, ApplicationListener<ContextRefreshedEvent>
+{
+ /** Transaction factory.*/
+ private TransactionProxyFactory txFactory;
+
+ /** Ignite logger. */
+ private IgniteLogger log;
+
+ /** Transaction concurrency level. */
+ private TransactionConcurrency txConcurrency;
+
+ /** Default transaction isolation. */
+ private TransactionIsolation dfltTxIsolation;
+
+ /** Default transaction timeout. */
+ private long dfltTxTimeout;
+
+ /**
+ * Gets transaction concurrency level.
+ *
+ * @return Transaction concurrency level.
+ */
+ public TransactionConcurrency getTransactionConcurrency() {
+ return txConcurrency;
+ }
+
+ /**
+ * Sets transaction concurrency level.
+ *
+ * @param txConcurrency transaction concurrency level.
+ */
+ public void setTransactionConcurrency(TransactionConcurrency txConcurrency) {
+ this.txConcurrency = txConcurrency;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onApplicationEvent(ContextRefreshedEvent evt) {
+ if (txConcurrency == null)
+ txConcurrency = defaultTransactionConcurrency();
+
+ dfltTxIsolation = defaultTransactionIsolation();
+
+ dfltTxTimeout = defaultTransactionTimeout();
+
+ log = log();
+
+ txFactory = createTransactionFactory();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected Object doGetTransaction() throws TransactionException {
+ IgniteTransactionObject txObj = new IgniteTransactionObject();
+
+ txObj.setTransactionHolder(
+ (IgniteTransactionHolder)TransactionSynchronizationManager.getResource(txFactory), false);
+
+ return txObj;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
+ if (definition.getIsolationLevel() == TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
+ throw new InvalidIsolationLevelException("Ignite does not support READ_UNCOMMITTED isolation level.");
+
+ IgniteTransactionObject txObj = (IgniteTransactionObject)transaction;
+ TransactionProxy tx = null;
+
+ try {
+ if (txObj.getTransactionHolder() == null || txObj.getTransactionHolder().isSynchronizedWithTransaction()) {
+ long timeout = dfltTxTimeout;
+
+ if (definition.getTimeout() > 0)
+ timeout = TimeUnit.SECONDS.toMillis(definition.getTimeout());
+
+ TransactionProxy newTx = txFactory.txStart(txConcurrency,
+ convertToIgniteIsolationLevel(definition.getIsolationLevel()), timeout);
+
+ if (log.isDebugEnabled())
+ log.debug("Started Ignite transaction: " + newTx);
+
+ txObj.setTransactionHolder(new IgniteTransactionHolder(newTx), true);
+ }
+
+ txObj.getTransactionHolder().setSynchronizedWithTransaction(true);
+ txObj.getTransactionHolder().setTransactionActive(true);
+
+ tx = txObj.getTransactionHolder().getTransaction();
+
+ // Bind the session holder to the thread.
+ if (txObj.isNewTransactionHolder())
+ TransactionSynchronizationManager.bindResource(txFactory, txObj.getTransactionHolder());
+ }
+ catch (Exception ex) {
+ if (tx != null)
+ tx.close();
+
+ throw new CannotCreateTransactionException("Could not create Ignite transaction", ex);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
+ IgniteTransactionObject txObj = (IgniteTransactionObject)status.getTransaction();
+ TransactionProxy tx = txObj.getTransactionHolder().getTransaction();
+
+ if (status.isDebug() && log.isDebugEnabled())
+ log.debug("Committing Ignite transaction: " + tx);
+
+ try {
+ tx.commit();
+ }
+ catch (Exception e) {
+ throw new TransactionSystemException("Could not commit Ignite transaction", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
+ IgniteTransactionObject txObj = (IgniteTransactionObject)status.getTransaction();
+ TransactionProxy tx = txObj.getTransactionHolder().getTransaction();
+
+ if (status.isDebug() && log.isDebugEnabled())
+ log.debug("Rolling back Ignite transaction: " + tx);
+
+ try {
+ tx.rollback();
+ }
+ catch (Exception e) {
+ throw new TransactionSystemException("Could not rollback Ignite transaction", e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
+ IgniteTransactionObject txObj = (IgniteTransactionObject)status.getTransaction();
+ TransactionProxy tx = txObj.getTransactionHolder().getTransaction();
+
+ assert tx != null;
+
+ if (status.isDebug() && log.isDebugEnabled())
+ log.debug("Setting Ignite transaction rollback-only: " + tx);
+
+ tx.setRollbackOnly();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doCleanupAfterCompletion(Object transaction) {
+ IgniteTransactionObject txObj = (IgniteTransactionObject)transaction;
+
+ // Remove the transaction holder from the thread, if exposed.
+ if (txObj.isNewTransactionHolder()) {
+ TransactionProxy tx = txObj.getTransactionHolder().getTransaction();
+ TransactionSynchronizationManager.unbindResource(txFactory);
+
+ if (log.isDebugEnabled())
+ log.debug("Releasing Ignite transaction: " + tx);
+ }
+
+ txObj.getTransactionHolder().clear();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected boolean isExistingTransaction(Object transaction) throws TransactionException {
+ IgniteTransactionObject txObj = (IgniteTransactionObject)transaction;
+
+ return (txObj.getTransactionHolder() != null && txObj.getTransactionHolder().isTransactionActive());
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object getResourceFactory() {
+ return txFactory;
+ }
+
+ /**
+ * @param isolationLevel Spring isolation level.
+ * @return Ignite isolation level.
+ */
+ private TransactionIsolation convertToIgniteIsolationLevel(int isolationLevel) {
+ TransactionIsolation isolation = dfltTxIsolation;
+
+ switch (isolationLevel) {
+ case TransactionDefinition.ISOLATION_READ_COMMITTED:
+ isolation = TransactionIsolation.READ_COMMITTED;
+
+ break;
+
+ case TransactionDefinition.ISOLATION_REPEATABLE_READ:
+ isolation = TransactionIsolation.REPEATABLE_READ;
+
+ break;
+
+ case TransactionDefinition.ISOLATION_SERIALIZABLE:
+ isolation = TransactionIsolation.SERIALIZABLE;
+ }
+
+ return isolation;
+ }
+
+ /** @return Default transaction isolation. */
+ protected abstract TransactionIsolation defaultTransactionIsolation();
+
+ /** @return Default transaction timeout. */
+ protected abstract long defaultTransactionTimeout();
+
+ /** @return Default transaction concurrency. */
+ protected abstract TransactionConcurrency defaultTransactionConcurrency();
+
+ /** Creates instance of {@link TransactionProxyFactory} that will be used to start new Ignite transactions. */
+ protected abstract TransactionProxyFactory createTransactionFactory();
+
+ /** @return Ignite logger. */
+ protected abstract IgniteLogger log();
+
+ /**
+ * An object representing a managed Ignite transaction.
+ */
+ protected static class IgniteTransactionObject implements SmartTransactionObject {
+ /** */
+ private IgniteTransactionHolder txHolder;
+
+ /** */
+ private boolean newTxHolder;
+
+ /**
+ * Sets the resource holder being used to hold Ignite resources in the
+ * transaction.
+ *
+ * @param txHolder the transaction resource holder
+ * @param newTxHolder true if the holder was created for this transaction,
+ * false if it already existed
+ */
+ private void setTransactionHolder(IgniteTransactionHolder txHolder, boolean newTxHolder) {
+ this.txHolder = txHolder;
+ this.newTxHolder = newTxHolder;
+ }
+
+ /**
+ * Returns the resource holder being used to hold Ignite resources in the
+ * transaction.
+ *
+ * @return the transaction resource holder
+ */
+ protected IgniteTransactionHolder getTransactionHolder() {
+ return txHolder;
+ }
+
+ /**
+ * Returns true if the transaction holder was created for the current
+ * transaction and false if it existed prior to the transaction.
+ *
+ * @return true if the holder was created for this transaction, false if it
+ * already existed
+ */
+ private boolean isNewTransactionHolder() {
+ return newTxHolder;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isRollbackOnly() {
+ return txHolder.isRollbackOnly();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void flush() {
+ TransactionSynchronizationUtils.triggerFlush();
+ }
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManager.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManager.java
new file mode 100644
index 0000000..25eff87
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManager.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.ClientTransactionConfiguration;
+import org.apache.ignite.internal.transactions.proxy.ClientTransactionProxyFactory;
+import org.apache.ignite.internal.transactions.proxy.TransactionProxyFactory;
+import org.apache.ignite.logger.NullLogger;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.transaction.TransactionException;
+import org.springframework.transaction.support.DefaultTransactionStatus;
+
+/**
+ * Represents {@link AbstractSpringTransactionManager} implementation that uses thin client to access the cluster and
+ * manage transactions. It requires thin client instance to be set before manager use
+ * (see {@link #setClientInstance(IgniteClient)}).
+ *
+ * You can provide ignite client instance to a Spring configuration XML file, like below:
+ *
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <-- Provide Ignite client instance. -->
+ * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.IgniteClientSpringTransactionManager">
+ * <property name="clientInstance" ref="igniteClientBean"/>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ *
+ * Note that the same thin client instance must be used to both initialize the transaction manager and perform
+ * transactional operations.
+ *
+ * @see SpringTransactionManager to configure Transaction Manager access to the cluster through the Ignite client node.
+ */
+public class IgniteClientSpringTransactionManager extends AbstractSpringTransactionManager {
+ /** No-op Ignite logger. */
+ private static final IgniteLogger NOOP_LOG = new NullLogger();
+
+ /** Thin client instance. */
+ private IgniteClient cli;
+
+ /** @return Thin client instance that is used for accessing the Ignite cluster. */
+ public IgniteClient getClientInstance() {
+ return cli;
+ }
+
+ /** Sets thin client instance that is used for accessing the Ignite cluster. */
+ public void setClientInstance(IgniteClient cli) {
+ this.cli = cli;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onApplicationEvent(ContextRefreshedEvent evt) {
+ if (cli == null) {
+ throw new IllegalArgumentException("Failed to obtain thin client instance for accessing the Ignite" +
+ " cluster. Check that 'clientInstance' property is set.");
+ }
+
+ super.onApplicationEvent(evt);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionIsolation defaultTransactionIsolation() {
+ return ClientTransactionConfiguration.DFLT_TX_ISOLATION;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected long defaultTransactionTimeout() {
+ return ClientTransactionConfiguration.DFLT_TRANSACTION_TIMEOUT;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionConcurrency defaultTransactionConcurrency() {
+ return ClientTransactionConfiguration.DFLT_TX_CONCURRENCY;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionProxyFactory createTransactionFactory() {
+ return new ClientTransactionProxyFactory(cli.transactions());
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteLogger log() {
+ return NOOP_LOG;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
+ ((IgniteTransactionObject)status.getTransaction()).getTransactionHolder().setRollbackOnly();
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java
new file mode 100644
index 0000000..d0363a1
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/IgniteTransactionHolder.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.internal.transactions.proxy.TransactionProxy;
+import org.apache.ignite.transactions.Transaction;
+import org.springframework.transaction.support.ResourceHolderSupport;
+
+/**
+ * A {@link org.springframework.transaction.support.ResourceHolder} for the Ignite {@link Transaction} to
+ * associate the transaction with a Spring transaction manager.
+ */
+class IgniteTransactionHolder extends ResourceHolderSupport {
+ /** */
+ private TransactionProxy transaction;
+
+ /** */
+ private boolean transactionActive;
+
+ /**
+ * Constructs the transaction holder.
+ *
+ * @param transaction the transaction to hold
+ */
+ IgniteTransactionHolder(TransactionProxy transaction) {
+ this.transaction = transaction;
+ }
+
+ /**
+ * Returns true if the holder is holding a transaction.
+ *
+ * @return true if holding a transaction
+ */
+ public boolean hasTransaction() {
+ return this.transaction != null;
+ }
+
+ /**
+ * Sets the transaction to be held in the resource holder.
+ *
+ * @param transaction the transaction
+ */
+ void setTransaction(TransactionProxy transaction) {
+ this.transaction = transaction;
+ }
+
+ /**
+ * Returns the transaction in the holder or null if none has been set.
+ *
+ * @return the transaction or null
+ */
+ TransactionProxy getTransaction() {
+ return this.transaction;
+ }
+
+ /**
+ * Return whether this holder represents an active, Ignite-managed
+ * transaction.
+ *
+ * @return true if a transaction is active
+ */
+ protected boolean isTransactionActive() {
+ return this.transactionActive;
+ }
+
+ /**
+ * Set whether this holder represents an active, Ignite-managed
+ * transaction.
+ *
+ * @param transactionActive true if a transaction is active
+ */
+ protected void setTransactionActive(boolean transactionActive) {
+ this.transactionActive = transactionActive;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void clear() {
+ super.clear();
+
+ transactionActive = false;
+ transaction.close();
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java
new file mode 100644
index 0000000..8736c6f
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/SpringTransactionManager.java
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteSpring;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.TransactionConfiguration;
+import org.apache.ignite.internal.transactions.proxy.IgniteTransactionProxyFactory;
+import org.apache.ignite.internal.transactions.proxy.TransactionProxyFactory;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.transactions.TransactionConcurrency;
+import org.apache.ignite.transactions.TransactionIsolation;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.event.ContextRefreshedEvent;
+
+/**
+ * Implementation of Spring transaction abstraction based on Ignite transaction.
+ * <h1 class="header">Overview</h1>
+ * Spring transaction abstraction allows to enable declarative transaction management
+ * and concentrate on business logic rather than transaction life-cycle.
+ * For more information, refer to
+ * <a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html">
+ * Spring Transaction Abstraction documentation</a>.
+ * <h1 class="header">How To Enable Transaction support</h1>
+ * To enable declarative transaction management on Ignite cache in your Spring application,
+ * you will need to do the following:
+ * <ul>
+ * <li>
+ * Start an Ignite node with proper configuration in embedded mode
+ * (i.e., in the same JVM where the application is running). It can
+ * already have predefined caches, but it's not required - caches
+ * will be created automatically on first access if needed.
+ * </li>
+ * <li>
+ * Configure {@code SpringTransactionManager} as a transaction manager
+ * in the Spring application context.
+ * </li>
+ * </ul>
+ * {@code SpringTransactionManager} can start a node itself on its startup
+ * based on provided Ignite configuration. You can provide path to a
+ * Spring configuration XML file, like below (path can be absolute or
+ * relative to {@code IGNITE_HOME}):
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <-- Provide configuration file path. -->
+ * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="configurationPath" value="examples/config/spring-transaction.xml"/>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ * Or you can provide a {@link IgniteConfiguration} bean, like below:
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <-- Provide configuration bean. -->
+ * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="configuration">
+ * <bean id="gridCfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+ * ...
+ * </bean>
+ * </property>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ * Note that providing both configuration path and configuration bean is illegal
+ * and results in {@link IllegalArgumentException}.
+ *
+ * If you already have Ignite node running within your application,
+ * simply provide correct Ignite instance name, like below (if there is no Grid
+ * instance with such name, exception will be thrown):
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <-- Provide Ignite instance name. -->
+ * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="igniteInstanceName" value="myGrid"/>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ * This can be used, for example, when you are running your application
+ * in a J2EE Web container and use {@ignitelink org.apache.ignite.startup.servlet.ServletContextListenerStartup}
+ * for node startup.
+ *
+ * If neither {@link #setConfigurationPath(String) configurationPath},
+ * {@link #setConfiguration(IgniteConfiguration) configuration}, nor
+ * {@link #setIgniteInstanceName(String) igniteInstanceName} are provided, transaction manager
+ * will try to use default Grid instance (the one with the {@code null}
+ * name). If it doesn't exist, exception will be thrown.
+ *
+ * {@code SpringTransactionManager} can be configured to support Ignite transaction concurrency.
+ * For this you need to provide {@code SpringTransactionManager} with transactionConcurrency property.
+ * If this property is not set then default transaction concurrency will be used
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <-- Provide Ignite instance name. -->
+ * <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="igniteInstanceName" value="myGrid"/>
+ * <property name="transactionConcurrency" value="OPTIMISTIC"/>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ *
+ * In case you need to support both "OPTIMISTIC" and "PESSIMISTIC" transaction concurrency in you application,
+ * you need to create two transaction managers with different transaction concurrency
+ * <pre name="code" class="xml">
+ * <beans xmlns="http://www.springframework.org/schema/beans"
+ * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ * xmlns:tx="http://www.springframework.org/schema/tx"
+ * xsi:schemaLocation="
+ * http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ * http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+ * <bean id="optimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="igniteInstanceName" value="myGrid"/>
+ * <property name="transactionConcurrency" value="OPTIMISTIC"/>
+ * </bean>
+ *
+ * <bean id="pessimisticTransactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ * <property name="igniteInstanceName" value="myGrid"/>
+ * <property name="transactionConcurrency" value="PESSIMISTIC"/>
+ * </bean>
+ *
+ * <-- Use annotation-driven transaction configuration. -->
+ * <tx:annotation-driven/>
+ * </beans>
+ * </pre>
+ * Then use them with qualifiers in your application:
+ * <pre name="code" class="xml">
+ * public class TransactionalService {
+ * {@literal @}Transactional("optimisticTransactionManager")
+ * public void doOptimistically() {
+ * ...
+ * }
+ *
+ * {@literal @}Transactional("pessimisticTransactionManager")
+ * public void doPessimistically() {
+ * ...
+ * }
+ * }
+ * </pre>
+ */
+public class SpringTransactionManager extends AbstractSpringTransactionManager implements ApplicationContextAware,
+ DisposableBean
+{
+ /** Grid configuration file path. */
+ private String cfgPath;
+
+ /** Ignite configuration. */
+ private IgniteConfiguration cfg;
+
+ /** Ignite instance name. */
+ private String igniteInstanceName;
+
+ /** Ignite instance. */
+ private Ignite ignite;
+
+ /** Flag indicating that Ignite instance was not created inside current transaction manager. */
+ private boolean externalIgniteInstance;
+
+ /** Ignite transactions configuration. */
+ private TransactionConfiguration txCfg;
+
+ /** Spring context */
+ private ApplicationContext springCtx;
+
+ /**
+ * Gets configuration file path.
+ *
+ * @return Grid configuration file path.
+ */
+ public String getConfigurationPath() {
+ return cfgPath;
+ }
+
+ /**
+ * Sets configuration file path.
+ *
+ * @param cfgPath Grid configuration file path.
+ */
+ public void setConfigurationPath(String cfgPath) {
+ this.cfgPath = cfgPath;
+ }
+
+ /**
+ * Gets configuration bean.
+ *
+ * @return Grid configuration bean.
+ */
+ public IgniteConfiguration getConfiguration() {
+ return cfg;
+ }
+
+ /**
+ * Sets configuration bean.
+ *
+ * @param cfg Grid configuration bean.
+ */
+ public void setConfiguration(IgniteConfiguration cfg) {
+ this.cfg = cfg;
+ }
+
+ /**
+ * Gets grid name.
+ *
+ * @return Grid name.
+ * @deprecated Use {@link #getIgniteInstanceName()}.
+ */
+ @Deprecated
+ public String getGridName() {
+ return getIgniteInstanceName();
+ }
+
+ /**
+ * Sets grid name.
+ *
+ * @param gridName Grid name.
+ * @deprecated Use {@link #setIgniteInstanceName(String)}.
+ */
+ @Deprecated
+ public void setGridName(String gridName) {
+ setIgniteInstanceName(gridName);
+ }
+
+ /**
+ * Gets Ignite instance name.
+ *
+ * @return Ignite instance name.
+ */
+ public String getIgniteInstanceName() {
+ return igniteInstanceName;
+ }
+
+ /**
+ * Sets Ignite instance name.
+ *
+ * @param igniteInstanceName Ignite instance name.
+ */
+ public void setIgniteInstanceName(String igniteInstanceName) {
+ this.igniteInstanceName = igniteInstanceName;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onApplicationEvent(ContextRefreshedEvent evt) {
+ if (ignite == null) {
+ if (cfgPath != null && cfg != null) {
+ throw new IllegalArgumentException("Both 'configurationPath' and 'configuration' are " +
+ "provided. Set only one of these properties if you need to start a Ignite node inside of " +
+ "SpringTransactionManager. If you already have a node running, omit both of them and set" +
+ "'igniteInstanceName' property.");
+ }
+
+ try {
+ if (cfgPath != null)
+ ignite = IgniteSpring.start(cfgPath, springCtx);
+ else if (cfg != null)
+ ignite = IgniteSpring.start(cfg, springCtx);
+ else {
+ ignite = Ignition.ignite(igniteInstanceName);
+
+ externalIgniteInstance = true;
+ }
+ }
+ catch (IgniteCheckedException e) {
+ throw U.convertException(e);
+ }
+
+ txCfg = ignite.configuration().getTransactionConfiguration();
+ }
+
+ super.onApplicationEvent(evt);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void setApplicationContext(ApplicationContext ctx) throws BeansException {
+ this.springCtx = ctx;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionIsolation defaultTransactionIsolation() {
+ return txCfg.getDefaultTxIsolation();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected long defaultTransactionTimeout() {
+ return txCfg.getDefaultTxTimeout();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteLogger log() {
+ return ignite.log();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionConcurrency defaultTransactionConcurrency() {
+ return txCfg.getDefaultTxConcurrency();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected TransactionProxyFactory createTransactionFactory() {
+ return new IgniteTransactionProxyFactory(ignite.transactions());
+ }
+
+ /** {@inheritDoc} */
+ @Override public void destroy() {
+ if (!externalIgniteInstance)
+ ignite.close();
+ }
+}
diff --git a/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/package-info.java b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/package-info.java
new file mode 100644
index 0000000..66b9942
--- /dev/null
+++ b/modules/spring-tx-ext/src/main/java/org/apache/ignite/transactions/spring/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * Contains implementation of Spring transaction manager.
+ */
+package org.apache.ignite.transactions.spring;
diff --git a/modules/spring-tx-ext/src/test/java/config/spring-transactions-ignite-spring-bean.xml b/modules/spring-tx-ext/src/test/java/config/spring-transactions-ignite-spring-bean.xml
new file mode 100644
index 0000000..57a0ced
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/config/spring-transactions-ignite-spring-bean.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+
+ <tx:annotation-driven/>
+
+ <bean id="mySpringBean" class="org.apache.ignite.IgniteSpringBean">
+ <property name="configuration">
+ <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+ <property name="peerClassLoadingEnabled" value="true"/>
+ <property name="igniteInstanceName" value="testGrid"/>
+
+ <property name="cacheConfiguration">
+ <list>
+ <bean class="org.apache.ignite.configuration.CacheConfiguration">
+ <property name="name" value="testCache"/>
+ <property name="atomicityMode" value="TRANSACTIONAL"/>
+ </bean>
+ </list>
+ </property>
+
+ <property name="discoverySpi">
+ <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
+ <property name="ipFinder">
+ <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
+ <property name="addresses">
+ <list>
+ <value>127.0.0.1:47500..47509</value>
+ </list>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="gridSpringTransactionService" class="org.apache.ignite.transactions.spring.GridSpringTransactionService"/>
+
+ <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ <property name="transactionConcurrency" value="OPTIMISTIC"/>
+ <property name="igniteInstanceName" value="testGrid"/>
+ </bean>
+</beans>
diff --git a/modules/spring-tx-ext/src/test/java/config/spring-transactions.xml b/modules/spring-tx-ext/src/test/java/config/spring-transactions.xml
new file mode 100644
index 0000000..f1a29ad
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/config/spring-transactions.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
+
+ <tx:annotation-driven/>
+
+ <bean id="gridSpringTransactionService" class="org.apache.ignite.transactions.spring.GridSpringTransactionService"/>
+
+ <bean id="transactionManager" class="org.apache.ignite.transactions.spring.SpringTransactionManager">
+ <property name="transactionConcurrency" value="OPTIMISTIC"/>
+ <property name="igniteInstanceName" value="testGrid"/>
+ </bean>
+</beans>
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/TestInjectionLifecycleBean.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/TestInjectionLifecycleBean.java
new file mode 100644
index 0000000..2b8c932
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/TestInjectionLifecycleBean.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite;
+
+import org.apache.ignite.lifecycle.LifecycleBean;
+import org.apache.ignite.lifecycle.LifecycleEventType;
+import org.apache.ignite.resources.SpringApplicationContextResource;
+import org.springframework.context.ApplicationContext;
+
+import static org.junit.Assert.assertNotNull;
+
+/** Lifecycle bean for testing. */
+public class TestInjectionLifecycleBean implements LifecycleBean {
+ /** */
+ @SpringApplicationContextResource
+ private ApplicationContext appCtx;
+
+ /** Checks that context was injected. */
+ public void checkState() {
+ assertNotNull(appCtx);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void onLifecycleEvent(LifecycleEventType evt) {
+ checkState();
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/spring-injection-test.xml b/modules/spring-tx-ext/src/test/java/org/apache/ignite/spring-injection-test.xml
new file mode 100644
index 0000000..14072ff
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/spring-injection-test.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ Ignite Spring configuration file to startup grid cache.
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:util="http://www.springframework.org/schema/util"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/util
+ http://www.springframework.org/schema/util/spring-util.xsd">
+ <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
+ <property name="lifecycleBeans">
+ <array>
+ <bean id="bean1" class="org.apache.ignite.TestInjectionLifecycleBean"/>
+ <bean id="bean2" class="org.apache.ignite.TestInjectionLifecycleBean"/>
+ </array>
+ </property>
+
+ <property name="localHost" value="127.0.0.1"/>
+
+ <property name="igniteInstanceName" value="springInjectionTest"/>
+ </bean>
+</beans>
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringTransactionsTestSuite.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringTransactionsTestSuite.java
new file mode 100644
index 0000000..025ca99
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/testsuites/IgniteSpringTransactionsTestSuite.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.testsuites;
+
+import org.apache.ignite.transactions.spring.GridSpringTransactionManagerSelfTest;
+import org.apache.ignite.transactions.spring.GridSpringTransactionManagerSpringBeanSelfTest;
+import org.apache.ignite.transactions.spring.IgniteClientSpringTransactionManagerTest;
+import org.apache.ignite.transactions.spring.SpringTransactionManagerContextInjectionTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Ignite Spring Transactions tests.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ GridSpringTransactionManagerSelfTest.class,
+ GridSpringTransactionManagerSpringBeanSelfTest.class,
+ IgniteClientSpringTransactionManagerTest.class,
+ SpringTransactionManagerContextInjectionTest.class,
+})
+public class IgniteSpringTransactionsTestSuite {
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerAbstractTest.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerAbstractTest.java
new file mode 100644
index 0000000..eb7cd78
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerAbstractTest.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.transactions.Transaction;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.CacheProxy;
+import org.junit.Test;
+import org.springframework.transaction.IllegalTransactionStateException;
+import org.springframework.transaction.InvalidIsolationLevelException;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+public abstract class GridSpringTransactionManagerAbstractTest extends GridCommonAbstractTest {
+
+ /** */
+ protected static final String CACHE_NAME = "testCache";
+
+ /** */
+ public abstract CacheProxy<Integer, String> cache();
+
+ /** */
+ public abstract GridSpringTransactionService service();
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ cache().removeAll();
+ }
+
+ /** {@inheritDoc} */
+ @Override public String getTestIgniteInstanceName() {
+ return "testGrid";
+ }
+
+ /** */
+ @Test
+ public void testSuccessPut() {
+ int entryCnt = 1_000;
+
+ service().put(cache(), entryCnt);
+
+ assertEquals(entryCnt, cache().size());
+ }
+
+ /** */
+ @Test
+ public void testFailPut() {
+ int entryCnt = 1_000;
+
+ try {
+ service().putWithError(cache(), entryCnt);
+ }
+ catch (NumberFormatException ignored) {
+ System.out.println();
+ // No-op.
+ }
+
+ assertEquals(0, cache().size());
+ }
+
+ /** */
+ @Test
+ public void testMandatoryPropagation() {
+ try {
+ service().putWithMandatoryPropagation(cache());
+ }
+ catch (IllegalTransactionStateException e) {
+ assertEquals("No existing transaction found for transaction marked with propagation 'mandatory'", e.getMessage());
+ }
+
+ assertEquals(0, cache().size());
+ }
+
+ /** */
+ @Test
+ public void testUnsupportedIsolationLevel() {
+ try {
+ service().putWithUnsupportedIsolationLevel(cache());
+ }
+ catch (InvalidIsolationLevelException e) {
+ assertEquals("Ignite does not support READ_UNCOMMITTED isolation level.", e.getMessage());
+ }
+
+ assertEquals(0, cache().size());
+ }
+
+ /**
+ * @throws Exception If test failed.
+ */
+ @Test
+ public void testDoSetRollbackOnlyInExistingTransaction() throws Exception {
+ SpringTransactionManager mngr = new SpringTransactionManager();
+ mngr.setIgniteInstanceName(grid().name());
+ mngr.onApplicationEvent(null);
+
+ TransactionTemplate txTmpl = new TransactionTemplate(mngr);
+
+ try {
+ txTmpl.execute(new TransactionCallback<Object>() {
+ @Override public Object doInTransaction(TransactionStatus status) {
+ cache().put(1, "1");
+
+ Transaction tx = grid().transactions().tx();
+
+ assertFalse(tx.isRollbackOnly());
+
+ try {
+ service().putWithError(cache(), 1_000);
+ }
+ catch (Exception ignored) {
+ // No-op.
+ }
+
+ assertTrue(tx.isRollbackOnly());
+
+ return null;
+ }
+ });
+ }
+ catch (Exception ignored) {
+ // No-op.
+ }
+
+ assertEquals(0, cache().size());
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java
new file mode 100644
index 0000000..d5e2a8e
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSelfTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.CacheProxy;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.IgniteCacheProxy;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericXmlApplicationContext;
+
+/**
+ * Spring transaction test.
+ */
+public class GridSpringTransactionManagerSelfTest extends GridSpringTransactionManagerAbstractTest {
+ /** */
+ private GridSpringTransactionService service;
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ CacheConfiguration cache = new CacheConfiguration(DEFAULT_CACHE_NAME);
+
+ cache.setName(CACHE_NAME);
+ cache.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL);
+
+ cfg.setCacheConfiguration(cache);
+
+ return cfg;
+ }
+
+ @Override public CacheProxy<Integer, String> cache() {
+ return new IgniteCacheProxy<>(grid().cache(CACHE_NAME));
+ }
+
+ @Override public GridSpringTransactionService service() {
+ return service;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ startGrid();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ ApplicationContext appCtx = new GenericXmlApplicationContext("config/spring-transactions.xml");
+ service = (GridSpringTransactionService)appCtx.getBean("gridSpringTransactionService");
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSpringBeanSelfTest.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSpringBeanSelfTest.java
new file mode 100644
index 0000000..19774a6
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionManagerSpringBeanSelfTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.CacheProxy;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.IgniteCacheProxy;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericXmlApplicationContext;
+
+public class GridSpringTransactionManagerSpringBeanSelfTest extends GridSpringTransactionManagerAbstractTest {
+
+ /** */
+ private Ignite ignite;
+
+ /** */
+ private GridSpringTransactionService service;
+
+ @Override public CacheProxy<Integer, String> cache() {
+ return new IgniteCacheProxy<>(ignite.cache(CACHE_NAME));
+ }
+
+ @Override public GridSpringTransactionService service() {
+ return service;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTest() throws Exception {
+ ApplicationContext appCtx = new GenericXmlApplicationContext("config/spring-transactions-ignite-spring-bean.xml");
+
+ // To produce multiple calls of ApplicationListener::onApplicationEvent
+ GenericXmlApplicationContext child = new GenericXmlApplicationContext();
+ child.setParent(appCtx);
+ child.refresh();
+
+ ignite = (Ignite)appCtx.getBean("mySpringBean");
+ service = (GridSpringTransactionService)appCtx.getBean("gridSpringTransactionService");
+ }
+
+ @Override protected void afterTest() throws Exception {
+ super.afterTest();
+ stopAllGrids();
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java
new file mode 100644
index 0000000..4a0d02f
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/GridSpringTransactionService.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.client.ClientCache;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Service.
+ */
+public class GridSpringTransactionService {
+ /** */
+ @Autowired
+ private GridSpringTransactionService self;
+
+ /**
+ * @param cache Cache.
+ * @param entryCnt Entries count.
+ */
+ @Transactional
+ public void put(CacheProxy<Integer, String> cache, int entryCnt) {
+ for (int i = 0; i < entryCnt; i++)
+ cache.put(i, String.valueOf(i));
+ }
+
+ /**
+ * @param cache Cache.
+ * @param entryCnt Entries count.
+ */
+ @Transactional
+ public void putWithError(CacheProxy<Integer, String> cache, int entryCnt) {
+ for (int i = 0; i < entryCnt; i++)
+ cache.put(i, String.valueOf(i));
+
+ cache.put(Integer.valueOf("one"), "one");
+ }
+
+ /**
+ * @param cache Cache.
+ */
+ @Transactional(propagation = Propagation.MANDATORY)
+ public void putWithMandatoryPropagation(CacheProxy<Integer, String> cache) {
+ cache.put(1, "1");
+ }
+
+ /**
+ * @param cache Cache.
+ */
+ @Transactional(isolation = Isolation.READ_UNCOMMITTED)
+ public void putWithUnsupportedIsolationLevel(CacheProxy<Integer, String> cache) {
+ cache.put(1, "1");
+ }
+
+ /** */
+ @Transactional
+ public void putWithNestedError(CacheProxy<Integer, String> cache, int entryCnt) {
+ self.put(cache, entryCnt);
+
+ try {
+ self.putWithError(cache, entryCnt);
+ }
+ catch (Exception ignored) {
+ // No-op.
+ }
+ }
+
+ /** */
+ public static class ClientCacheProxy<K, V> implements CacheProxy<K, V> {
+ /** */
+ private final ClientCache<K, V> cliCache;
+
+ /** */
+ public ClientCacheProxy(ClientCache<K, V> cliCache) {
+ this.cliCache = cliCache;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void put(K key, V val) {
+ cliCache.put(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return cliCache.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void removeAll() {
+ cliCache.removeAll();
+ }
+ }
+
+ /** */
+ public static class IgniteCacheProxy<K, V> implements CacheProxy<K, V> {
+ /** */
+ private final IgniteCache<K, V> cache;
+
+ /** */
+ public IgniteCacheProxy(IgniteCache<K, V> cache) {
+ this.cache = cache;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void put(K key, V val) {
+ cache.put(key, val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return cache.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void removeAll() {
+ cache.removeAll();
+ }
+ }
+
+ /** */
+ public static interface CacheProxy<K, V> {
+ /** */
+ public void put(K key, V val);
+
+ /** */
+ public int size();
+
+ /** */
+ public void removeAll();
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManagerTest.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManagerTest.java
new file mode 100644
index 0000000..0d333ea
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/IgniteClientSpringTransactionManagerTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.CacheProxy;
+import org.apache.ignite.transactions.spring.GridSpringTransactionService.ClientCacheProxy;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.UnexpectedRollbackException;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static org.apache.ignite.configuration.ClientConnectorConfiguration.DFLT_PORT;
+
+/** Tests Spring Transactions manager implementation that uses thin client to access the Ignite cluster. */
+public class IgniteClientSpringTransactionManagerTest extends GridSpringTransactionManagerAbstractTest {
+ /** Spring application context. */
+ private static AnnotationConfigApplicationContext ctx;
+
+ /** Ignite thin client instance. */
+ private static IgniteClient cli;
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setCacheConfiguration(new CacheConfiguration<>(CACHE_NAME)
+ .setAtomicityMode(TRANSACTIONAL));
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ startGrid();
+
+ ctx = new AnnotationConfigApplicationContext(IgniteClientSpringTransactionManagerApplicationContext.class);
+ cli = ctx.getBean(IgniteClient.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTestsStopped() throws Exception {
+ super.afterTestsStopped();
+
+ ctx.close();
+ }
+
+ /** {@inheritDoc} */
+ @Override public CacheProxy<Integer, String> cache() {
+ return new ClientCacheProxy<>(cli.cache(CACHE_NAME));
+ }
+
+ /** {@inheritDoc} */
+ @Override public GridSpringTransactionService service() {
+ return ctx.getBean(GridSpringTransactionService.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void testDoSetRollbackOnlyInExistingTransaction() {
+ GridTestUtils.assertThrowsAnyCause(
+ log,
+ () -> {
+ service().putWithNestedError(cache(), 1_000);
+
+ return null;
+ },
+ UnexpectedRollbackException.class,
+ "Transaction rolled back because it has been marked as rollback-only");
+
+ assertEquals(0, cache().size());
+ }
+
+ /** */
+ @Configuration
+ @EnableTransactionManagement
+ public static class IgniteClientSpringTransactionManagerApplicationContext {
+ /** */
+ @Bean
+ public GridSpringTransactionService transactionService() {
+ return new GridSpringTransactionService();
+ }
+
+ /** */
+ @Bean
+ public IgniteClient igniteClient() {
+ return Ignition.startClient(new ClientConfiguration().setAddresses("127.0.0.1:" + DFLT_PORT));
+ }
+
+ /** */
+ @Bean
+ public AbstractSpringTransactionManager transactionManager(IgniteClient cli) {
+ IgniteClientSpringTransactionManager mgr = new IgniteClientSpringTransactionManager();
+
+ mgr.setClientInstance(cli);
+
+ return mgr;
+ }
+ }
+}
diff --git a/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/SpringTransactionManagerContextInjectionTest.java b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/SpringTransactionManagerContextInjectionTest.java
new file mode 100644
index 0000000..6acbe36
--- /dev/null
+++ b/modules/spring-tx-ext/src/test/java/org/apache/ignite/transactions/spring/SpringTransactionManagerContextInjectionTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.transactions.spring;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.TestInjectionLifecycleBean;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgnitionEx;
+import org.apache.ignite.lifecycle.LifecycleBean;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ *
+ */
+public class SpringTransactionManagerContextInjectionTest extends GridCommonAbstractTest {
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testBeanInjectionUsingConfigPath() throws Exception {
+ BeanFactory factory = new AnnotationConfigApplicationContext(TestPathConfiguration.class);
+
+ Ignite grid = IgnitionEx.grid("springInjectionTest");
+
+ IgniteConfiguration cfg = grid.configuration();
+
+ LifecycleBean[] beans = cfg.getLifecycleBeans();
+
+ assertEquals(2, beans.length);
+
+ TestInjectionLifecycleBean bean1 = (TestInjectionLifecycleBean)beans[0];
+ TestInjectionLifecycleBean bean2 = (TestInjectionLifecycleBean)beans[1];
+
+ bean1.checkState();
+ bean2.checkState();
+ }
+
+ /**
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testBeanInjectionUsingConfig() throws Exception {
+ BeanFactory factory = new AnnotationConfigApplicationContext(TestCfgConfiguration.class);
+
+ TestInjectionLifecycleBean bean1 = (TestInjectionLifecycleBean)factory.getBean("bean1");
+ TestInjectionLifecycleBean bean2 = (TestInjectionLifecycleBean)factory.getBean("bean2");
+
+ bean1.checkState();
+ bean2.checkState();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ stopAllGrids();
+
+ super.afterTest();
+ }
+
+ /** */
+ @SuppressWarnings("WeakerAccess")
+ @Configuration
+ static class TestPathConfiguration {
+ /** */
+ @Bean(name = "mgr")
+ public SpringTransactionManager springTransactionManager() {
+ SpringTransactionManager mgr = new SpringTransactionManager();
+
+ mgr.setConfigurationPath("org/apache/ignite/spring-injection-test.xml");
+
+ return mgr;
+ }
+ }
+
+ /** */
+ @SuppressWarnings("WeakerAccess")
+ @Configuration
+ static class TestCfgConfiguration {
+ /** */
+ @Bean(name = "mgr")
+ public SpringTransactionManager springTransactionManager() {
+ IgniteConfiguration cfg = new IgniteConfiguration();
+
+ cfg.setLocalHost("127.0.0.1");
+
+ cfg.setIgniteInstanceName("stmcit");
+
+ cfg.setLifecycleBeans(bean1(), bean2());
+
+ SpringTransactionManager mgr = new SpringTransactionManager();
+
+ mgr.setConfiguration(cfg);
+
+ return mgr;
+ }
+
+ /** */
+ @Bean(name = "bean1")
+ LifecycleBean bean1() {
+ return new TestInjectionLifecycleBean();
+ }
+
+ /** */
+ @Bean(name = "bean2")
+ LifecycleBean bean2() {
+ return new TestInjectionLifecycleBean();
+ }
+ }
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index cc0208d..c5d7525 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -367,6 +367,10 @@
<title>Ignite Development Utils</title>
<packages>org.apache.ignite.development.utils*</packages>
</group>
+ <group>
+ <title>Spring Transactions Integration</title>
+ <packages>org.apache.ignite.transactions.spring*</packages>
+ </group>
</groups>
<bottom>
<![CDATA[
diff --git a/pom.xml b/pom.xml
index 96df4eb..4985347 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,7 @@
<module>modules/spring-data-2.2-ext</module>
<module>modules/spring-data-commons</module>
<module>modules/performance-statistics-ext</module>
+ <module>modules/spring-tx-ext</module>
</modules>
<profiles>