diff --git a/CHANGELOG.md b/CHANGELOG.md index 136fcd6e108843bcb079bc15fbf5a4ed529657ab..24af3d81242defb0f33227c5ba9e614f4131217d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Ferma Changelog -## 3.0.4 +## 3.1.0 + +* Added abstraction code for tinkerpop transactions which ease usage of transactions within ferma. + The new code provides functional interfaces for transactions and methods which allow + access to transactions from nested functions without the need to pass the original transaction object along. + Once a transaction has been opened it can be accessed from anywhere within the same thread + via the `Tx.getActive()` method. + + The `TxFactoryTest` class and the [ferma-orientdb extension](https://github.com/syncleus/ferma-orientdb) + contain examples how these classes and methods can be used. + +* Updated the following dependencies + * tinkergraph-gremlin: 3.2.4 -> 3.2.5 + ## 3.0.3 diff --git a/pom.xml b/pom.xml index 7e4092255909c36a176faa193bfe00bd2d2826c2..03d1646bc2bd73b7856f5b1ad15f17495e612992 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ <groupId>com.syncleus.ferma</groupId> <artifactId>ferma</artifactId> <packaging>jar</packaging> - <version>3.0.4-SNAPSHOT</version> + <version>3.1.0-SNAPSHOT</version> <name>Ferma</name> <description>An ORM for the Tinkerpop3 graph stack.</description> @@ -158,12 +158,12 @@ <dependency> <groupId>org.apache.tinkerpop</groupId> <artifactId>gremlin-core</artifactId> - <version>3.2.4</version> + <version>3.2.5</version> </dependency> <dependency> <groupId>org.apache.tinkerpop</groupId> <artifactId>tinkergraph-gremlin</artifactId> - <version>3.2.4</version> + <version>3.2.5</version> <scope>test</scope> </dependency> <dependency> diff --git a/src/main/java/com/syncleus/ferma/AbstractVertexFrame.java b/src/main/java/com/syncleus/ferma/AbstractVertexFrame.java index 3b2c1ca1af53a3a4a2818ad5fcdb881a7df6d87f..f00568b74187633346ea3294ef73082b1a464959 100644 --- a/src/main/java/com/syncleus/ferma/AbstractVertexFrame.java +++ b/src/main/java/com/syncleus/ferma/AbstractVertexFrame.java @@ -28,10 +28,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.structure.*; import org.apache.tinkerpop.gremlin.structure.util.wrapped.WrappedElement; -import javax.annotation.Nullable; import java.util.Iterator; import java.util.function.Consumer; diff --git a/src/main/java/com/syncleus/ferma/DelegatingFramedGraph.java b/src/main/java/com/syncleus/ferma/DelegatingFramedGraph.java index d9a4d5544fc9aea01e1228456a26cef308fd390a..8a12aec59d5a712875897089a9558cedb21e450f 100644 --- a/src/main/java/com/syncleus/ferma/DelegatingFramedGraph.java +++ b/src/main/java/com/syncleus/ferma/DelegatingFramedGraph.java @@ -36,7 +36,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Graph; -import javax.annotation.Nullable; import java.io.IOException; import java.util.Collection; import java.util.Iterator; diff --git a/src/main/java/com/syncleus/ferma/DelegatingTransaction.java b/src/main/java/com/syncleus/ferma/DelegatingTransaction.java index b998c07b58a2d0b20333da8fd6cc41166f2772b0..b1254852613b0f75cb286b6ba5643c56db46fa7e 100644 --- a/src/main/java/com/syncleus/ferma/DelegatingTransaction.java +++ b/src/main/java/com/syncleus/ferma/DelegatingTransaction.java @@ -31,51 +31,62 @@ public class DelegatingTransaction implements WrappedTransaction { @Override public void open() { - this.delegate.open(); + this.getDelegate().open(); } @Override public void commit() { - this.delegate.commit(); + this.getDelegate().commit(); } @Override public void rollback() { - this.delegate.rollback(); + this.getDelegate().rollback(); } @Override public WrappedFramedGraph<? extends Graph> createThreadedTx() { - return new DelegatingFramedGraph<>(this.delegate.createThreadedTx(), this.parentGraph.getBuilder(), this.parentGraph.getTypeResolver()); + return new DelegatingFramedGraph<>(this.getDelegate().createThreadedTx(), this.getGraph().getBuilder(), this.getGraph().getTypeResolver()); } @Override public boolean isOpen() { - return this.delegate.isOpen(); + return this.getDelegate().isOpen(); } @Override public void readWrite() { - this.delegate.readWrite(); + this.getDelegate().readWrite(); } @Override public void close() { - this.delegate.close(); + this.getDelegate().close(); } @Override public void addTransactionListener(final Consumer<Transaction.Status> listener) { - this.delegate.addTransactionListener(listener); + this.getDelegate().addTransactionListener(listener); } @Override public void removeTransactionListener(final Consumer<Transaction.Status> listener) { - this.delegate.removeTransactionListener(listener); + this.getDelegate().removeTransactionListener(listener); } @Override public void clearTransactionListeners() { - this.delegate.clearTransactionListeners(); + this.getDelegate().clearTransactionListeners(); } + + @Override + public Transaction getDelegate() { + return delegate; + } + + @Override + public WrappedFramedGraph<? extends Graph> getGraph() { + return parentGraph; + } + } diff --git a/src/main/java/com/syncleus/ferma/WrappedTransaction.java b/src/main/java/com/syncleus/ferma/WrappedTransaction.java index 58f7087f4f7f5108a72029e3acabe5522616d570..adc7794071abd64d041b77b15939a59cd1def9c1 100644 --- a/src/main/java/com/syncleus/ferma/WrappedTransaction.java +++ b/src/main/java/com/syncleus/ferma/WrappedTransaction.java @@ -15,9 +15,11 @@ */ package com.syncleus.ferma; +import java.util.function.Consumer; + import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; import org.apache.tinkerpop.gremlin.structure.util.AbstractTransaction; -import java.util.function.Consumer; /** * A set of methods that allow for control of transactional behavior of a {@link WrappedFramedGraph} instance. Providers may @@ -95,4 +97,16 @@ public interface WrappedTransaction extends AutoCloseable { * Removes all transaction listeners. */ public void clearTransactionListeners(); + + /** + * Returns the raw wrapped tinkerpop transaction. + * @return + */ + Transaction getDelegate(); + + /** + * Returns the parent graph for the transaction. + * @return + */ + WrappedFramedGraph<? extends Graph> getGraph(); } diff --git a/src/main/java/com/syncleus/ferma/annotations/GraphElement.java b/src/main/java/com/syncleus/ferma/annotations/GraphElement.java new file mode 100644 index 0000000000000000000000000000000000000000..fa9e20a7911c2f2648ae27d338f6b28b05b95bed --- /dev/null +++ b/src/main/java/com/syncleus/ferma/annotations/GraphElement.java @@ -0,0 +1,30 @@ +/** + * Copyright 2004 - 2016 Syncleus, Inc. + * + * 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. + */ +package com.syncleus.ferma.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation which is used to identify classes which represent graph elements. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface GraphElement { + +} diff --git a/src/main/java/com/syncleus/ferma/tx/AbstractTx.java b/src/main/java/com/syncleus/ferma/tx/AbstractTx.java new file mode 100644 index 0000000000000000000000000000000000000000..e58f3e3fdc33c801e9ae4264c9005233bfecfb30 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/AbstractTx.java @@ -0,0 +1,65 @@ +/** + * Copyright 2004 - 2016 Syncleus, Inc. + * + * 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. + */ +package com.syncleus.ferma.tx; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; + +import com.syncleus.ferma.DelegatingTransaction; +import com.syncleus.ferma.WrappedFramedGraph; + +/** + * An abstract class that can be used to implement vendor specific graph database Tx classes. + */ +public abstract class AbstractTx<G extends FramedTxGraph> extends DelegatingTransaction implements Tx { + + private boolean isSuccess = false; + + public AbstractTx(Transaction delegate, WrappedFramedGraph<? extends Graph> parentGraph) { + super(delegate, parentGraph); + } + + @Override + public void success() { + isSuccess = true; + } + + @Override + public void failure() { + isSuccess = false; + } + + /** + * Return the state of the success status flag. + * + * @return + */ + protected boolean isSuccess() { + return isSuccess; + } + + @Override + public void close() { + Tx.setActive(null); + if (isSuccess()) { + commit(); + } else { + rollback(); + } + getDelegate().close(); + } + +} diff --git a/src/main/java/com/syncleus/ferma/tx/FramedTxGraph.java b/src/main/java/com/syncleus/ferma/tx/FramedTxGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..5a327b52f357f9b8c19e1fcf6d6fa82bf85f394a --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/FramedTxGraph.java @@ -0,0 +1,34 @@ +package com.syncleus.ferma.tx; + +import com.syncleus.ferma.FramedGraph; +import com.syncleus.ferma.WrappedTransaction; + +/** + * Adapted flavor of the {@link FramedGraph}. This interface will return a {@link Tx} instead of a {@link WrappedTransaction}. The {@link Tx} interface contains + * some useful methods which makes it easier is some cases to work with transactions. This includes automatic rollback within the autoclosable and transaction + * reference handling. + */ +public interface FramedTxGraph extends FramedGraph { + + /** + * Return an active transaction or create a new transaction if no active could be found. + */ + @Override + default Tx tx() { + if (Tx.getActive() != null) { + return Tx.getActive(); + } else { + Tx tx = createTx(); + Tx.setActive(tx); + return tx; + } + } + + /** + * Create a new transaction. + * + * @return + */ + Tx createTx(); + +} diff --git a/src/main/java/com/syncleus/ferma/tx/Tx.java b/src/main/java/com/syncleus/ferma/tx/Tx.java new file mode 100644 index 0000000000000000000000000000000000000000..acbcff73caac3e069ace84481ecbdb6157835caf --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/Tx.java @@ -0,0 +1,86 @@ +/** + * Copyright 2004 - 2016 Syncleus, Inc. + * + * 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. + */ +package com.syncleus.ferma.tx; + +import java.io.IOException; + +import com.syncleus.ferma.WrappedTransaction; + +/** + * A {@link Tx} is an extended flavor of the {@link WrappedTransaction}. This interface provides methods to store and retrieve transaction references. This is + * useful if you want to access a running transaction without passing along the transaction object in your implementation. Additionally this interface provides + * the {@link #success()} and {@link #failure()} methods which can be used within the {@link #close()} method in order to rollback or commit the transaction + * according to the state of the flag which is set using these methods. The {@link AbstractTx} class contains an abstract implementation of this mechanism. + */ +public interface Tx extends WrappedTransaction { + + /** + * Thread local that is used to store references to the used graph. + */ + public static ThreadLocal<Tx> threadLocalGraph = new ThreadLocal<>(); + + /** + * Set the nested active transaction for the current thread. + * + * @param tx + * Transaction + */ + public static void setActive(Tx tx) { + Tx.threadLocalGraph.set(tx); + } + + /** + * Return the current active graph. A transaction should be the only place where this threadlocal is updated. + * + * @return Currently active transaction + */ + public static Tx getActive() { + return Tx.threadLocalGraph.get(); + } + + /** + * Mark the transaction as succeeded. The autoclosable will invoke a commit when completing. + */ + void success(); + + /** + * Mark the transaction as failed. The autoclosable will invoke a rollback when completing. + */ + void failure(); + + /** + * Invoke rollback or commit when closing the autoclosable. By default a rollback will be invoked. + * + * @throws IOException + */ + @Override + void close(); + + /** + * Add new isolated vertex to the graph. + * + * @param <T> + * The type used to frame the element. + * @param kind + * The kind of the vertex + * @return The framed vertex + * + */ + default <T> T addVertex(Class<T> kind) { + return getGraph().addFramedVertex(kind); + } + +} diff --git a/src/main/java/com/syncleus/ferma/tx/TxAction.java b/src/main/java/com/syncleus/ferma/tx/TxAction.java new file mode 100644 index 0000000000000000000000000000000000000000..f946e58e422dab1003fcf12b86dc26ec0ead875e --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/TxAction.java @@ -0,0 +1,8 @@ +package com.syncleus.ferma.tx; + +@FunctionalInterface +public interface TxAction<T> { + + T handle(Tx tx) throws Exception; + +} diff --git a/src/main/java/com/syncleus/ferma/tx/TxAction0.java b/src/main/java/com/syncleus/ferma/tx/TxAction0.java new file mode 100644 index 0000000000000000000000000000000000000000..e354e99043e4159d34b1ada34ce2cbe6b07d4f82 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/TxAction0.java @@ -0,0 +1,8 @@ +package com.syncleus.ferma.tx; + +@FunctionalInterface +public interface TxAction0 { + + void handle() throws Exception; + +} diff --git a/src/main/java/com/syncleus/ferma/tx/TxAction1.java b/src/main/java/com/syncleus/ferma/tx/TxAction1.java new file mode 100644 index 0000000000000000000000000000000000000000..75efc341f931487c4a29f71ea244ca387c11e760 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/TxAction1.java @@ -0,0 +1,8 @@ +package com.syncleus.ferma.tx; + +@FunctionalInterface +public interface TxAction1<T> { + + T handle() throws Exception; + +} diff --git a/src/main/java/com/syncleus/ferma/tx/TxAction2.java b/src/main/java/com/syncleus/ferma/tx/TxAction2.java new file mode 100644 index 0000000000000000000000000000000000000000..c2afdf41031d0115fd4146eada3acff7505f47a5 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/TxAction2.java @@ -0,0 +1,8 @@ +package com.syncleus.ferma.tx; + +@FunctionalInterface +public interface TxAction2 { + + void handle(Tx tx) throws Exception; + +} diff --git a/src/main/java/com/syncleus/ferma/tx/TxFactory.java b/src/main/java/com/syncleus/ferma/tx/TxFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b93809d027508312d27c6b0b0c9fc19a1bd604f6 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/TxFactory.java @@ -0,0 +1,104 @@ +/** + * Copyright 2004 - 2016 Syncleus, Inc. + * + * 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. + */ +package com.syncleus.ferma.tx; + +/** + * Interface which can be used for custom transaction factories in + * order to provide various ways of executing transaction handlers. + */ +public interface TxFactory { + + /** + * Return a new autoclosable transaction handler. This object should be used within a try-with-resource block. + * Return an active transaction or create a new transaction if no active could be found. + * + * <pre> + * { + * @code + * try(Tx tx = db.tx()) { + * // interact with graph db here + * } + * } + * </pre> + * + * @return Created transaction + */ + default Tx tx() { + if (Tx.getActive() != null) { + return Tx.getActive(); + } else { + Tx tx = createTx(); + Tx.setActive(tx); + return tx; + } + } + + /** + * Create a new transaction. + * + * @return + */ + Tx createTx(); + + /** + * Execute the txHandler within the scope of a transaction and call + * the result handler once the transaction handler code has finished. + * + * @param txHandler + * Handler that will be executed within the scope of the transaction. + * @return Object which was returned by the handler + */ + <T> T tx(TxAction<T> txHandler); + + /** + * Execute the txHandler within the scope of a transaction. + * + * @param txHandler + * Handler that will be executed within the scope of the transaction. + */ + default void tx(TxAction0 txHandler) { + tx((tx) -> { + txHandler.handle(); + }); + } + + /** + * Execute the txHandler within the scope of a transaction. + * + * @param txHandler + * Handler that will be executed within the scope of the transaction. + * @return Result of the handler + */ + default <T> T tx(TxAction1<T> txHandler) { + return tx((tx) -> { + return txHandler.handle(); + }); + } + + /** + * Execute the txHandler within the scope of a transaction. + * + * @param txHandler + * Handler that will be executed within the scope of the transaction. + */ + default void tx(TxAction2 txHandler) { + tx((tx) -> { + txHandler.handle(tx); + return null; + }); + } + +} diff --git a/src/main/java/com/syncleus/ferma/tx/WrappedFramedTxGraph.java b/src/main/java/com/syncleus/ferma/tx/WrappedFramedTxGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..384d01e91842296392b2613d78aaba1593ff59b0 --- /dev/null +++ b/src/main/java/com/syncleus/ferma/tx/WrappedFramedTxGraph.java @@ -0,0 +1,9 @@ +package com.syncleus.ferma.tx; + +import org.apache.tinkerpop.gremlin.structure.Graph; + +import com.syncleus.ferma.WrappedFramedGraph; + +public interface WrappedFramedTxGraph<G extends Graph> extends WrappedFramedGraph<G>, FramedTxGraph { + +} diff --git a/src/test/java/com/syncleus/ferma/tx/DummyGraph.java b/src/test/java/com/syncleus/ferma/tx/DummyGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..a14e3ea6bd688cc1e543c7748f854c366cd7cbfc --- /dev/null +++ b/src/test/java/com/syncleus/ferma/tx/DummyGraph.java @@ -0,0 +1,22 @@ +package com.syncleus.ferma.tx; + +import org.apache.tinkerpop.gremlin.structure.Graph; + +import com.syncleus.ferma.DelegatingFramedGraph; + +public class DummyGraph extends DelegatingFramedGraph<Graph> implements FramedTxGraph { + + public DummyGraph(Graph delegate) { + super(delegate); + } + + @Override + public Tx tx() { + return FramedTxGraph.super.tx(); + } + + @Override + public Tx createTx() { + return new DummyTransaction(null, this); + } +} diff --git a/src/test/java/com/syncleus/ferma/tx/DummyTransaction.java b/src/test/java/com/syncleus/ferma/tx/DummyTransaction.java new file mode 100644 index 0000000000000000000000000000000000000000..4924d0be4a8d70f2ef54c427d814caabb4a98a00 --- /dev/null +++ b/src/test/java/com/syncleus/ferma/tx/DummyTransaction.java @@ -0,0 +1,14 @@ +package com.syncleus.ferma.tx; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; + +import com.syncleus.ferma.WrappedFramedGraph; + +public class DummyTransaction extends AbstractTx<FramedTxGraph>{ + + public DummyTransaction(Transaction delegate, WrappedFramedGraph<? extends Graph> parentGraph) { + super(delegate, parentGraph); + } + +} diff --git a/src/test/java/com/syncleus/ferma/tx/TxFactoryTest.java b/src/test/java/com/syncleus/ferma/tx/TxFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d79f46114c3bec3446005017ab46f290b4517588 --- /dev/null +++ b/src/test/java/com/syncleus/ferma/tx/TxFactoryTest.java @@ -0,0 +1,127 @@ +package com.syncleus.ferma.tx; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.verify; + +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class TxFactoryTest implements TxFactory { + + private DummyTransaction mock = Mockito.mock(DummyTransaction.class, Mockito.CALLS_REAL_METHODS); + + @Before + public void setupMocks() { + Transaction rawTx = Mockito.mock(Transaction.class); + Mockito.when(mock.getDelegate()).thenReturn(rawTx); + } + + @Test + public void testTx0() { + try (Tx tx = tx()) { + + } + verify(mock).close(); + } + + @Test + public void testTx1() { + tx(() -> { + + }); + verify(mock).close(); + } + + @Test + public void testTx2() { + assertEquals("test", tx(() -> { + return "test"; + })); + verify(mock).close(); + } + + @Test + public void testTx3() { + assertEquals("test", tx((tx) -> { + tx.failure(); + tx.success(); + return "test"; + })); + verify(mock).close(); + } + + @Test + public void testAbstractTxSucceeding() { + DummyTransaction tx = Mockito.mock(DummyTransaction.class, Mockito.CALLS_REAL_METHODS); + Transaction rawTx = Mockito.mock(Transaction.class); + Mockito.when(tx.getDelegate()).thenReturn(rawTx); + DummyGraph graphMock = Mockito.mock(DummyGraph.class, Mockito.CALLS_REAL_METHODS); + Mockito.when(graphMock.createTx()).thenReturn(tx); + + try (Tx tx2 = graphMock.tx()) { + assertNotNull(Tx.getActive()); + tx2.success(); + } + assertNull(Tx.getActive()); + verify(tx).commit(); + verify(tx).close(); + verify(tx, Mockito.never()).rollback(); + } + + @Test + public void testAbstractTxDefault() { + DummyTransaction tx = Mockito.mock(DummyTransaction.class, Mockito.CALLS_REAL_METHODS); + Transaction rawTx = Mockito.mock(Transaction.class); + Mockito.when(tx.getDelegate()).thenReturn(rawTx); + DummyGraph graphMock = Mockito.mock(DummyGraph.class, Mockito.CALLS_REAL_METHODS); + Mockito.when(graphMock.tx()).thenReturn(tx); + try (Tx tx2 = tx) { + assertNotNull(Tx.getActive()); + // Don't call tx2.success() or tx2.failure() + } + assertNull(Tx.getActive()); + verify(tx).close(); + verify(graphMock.tx()).rollback(); + verify(graphMock.tx()).close(); + verify(graphMock.tx(), Mockito.never()).commit(); + } + + @Test + public void testAbstractTxFailing() { + DummyTransaction tx = Mockito.mock(DummyTransaction.class, Mockito.CALLS_REAL_METHODS); + Transaction rawTx = Mockito.mock(Transaction.class); + Mockito.when(tx.getDelegate()).thenReturn(rawTx); + DummyGraph graphMock = Mockito.mock(DummyGraph.class, Mockito.CALLS_REAL_METHODS); + Mockito.when(graphMock.tx()).thenReturn(tx); + try (Tx tx2 = tx) { + assertNotNull(Tx.getActive()); + tx2.failure(); + } + assertNull(Tx.getActive()); + verify(tx).close(); + verify(graphMock.tx()).rollback(); + verify(graphMock.tx()).close(); + verify(graphMock.tx(), Mockito.never()).commit(); + } + + @Override + public Tx createTx() { + return mock; + } + + @Override + public <T> T tx(TxAction<T> txHandler) { + try (Tx tx = tx()) { + try { + return txHandler.handle(mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + +}