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 <pmgheap....@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 <plehanov.a...@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>