forked from spring-projects/spring-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* SPR-5079: Introduce programmatic tx mgmt in the TCF
- Loading branch information
Showing
8 changed files
with
663 additions
and
108 deletions.
There are no files selected for viewing
146 changes: 146 additions & 0 deletions
146
spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* Copyright 2002-2014 the original author or authors. | ||
* | ||
* 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 org.springframework.test.context.transaction; | ||
|
||
import org.springframework.test.context.TestExecutionListeners; | ||
import org.springframework.transaction.TransactionStatus; | ||
|
||
/** | ||
* {@code TestTransaction} provides a collection of static utility methods for | ||
* programmatic interaction with <em>test-managed transactions</em>. | ||
* | ||
* <p>Test-managed transactions are transactions that are managed by the <em>Spring TestContext Framework</em>. | ||
* | ||
* <p>Support for {@code TestTransaction} is automatically available whenever | ||
* the {@link TransactionalTestExecutionListener} is enabled. Note that the | ||
* {@code TransactionalTestExecutionListener} is typically enabled by default, | ||
* but it can also be manually enabled via the | ||
* {@link TestExecutionListeners @TestExecutionListeners} annotation. | ||
* | ||
* @author Sam Brannen | ||
* @since 4.1 | ||
* @see TransactionalTestExecutionListener | ||
*/ | ||
public class TestTransaction { | ||
|
||
/** | ||
* Determine whether a test-managed transaction is currently <em>active</em>. | ||
* @return {@code true} if a test-managed transaction is currently active | ||
* @see #start() | ||
* @see #end() | ||
*/ | ||
public static boolean isActive() { | ||
TransactionContext transactionContext = TransactionContextHolder.getCurrentTransactionContext(); | ||
if (transactionContext != null) { | ||
TransactionStatus transactionStatus = transactionContext.getTransactionStatus(); | ||
return (transactionStatus != null) && (!transactionStatus.isCompleted()); | ||
} | ||
|
||
// else | ||
return false; | ||
} | ||
|
||
/** | ||
* Determine whether the current test-managed transaction has been | ||
* {@linkplain #flagForRollback() flagged for rollback} or | ||
* {@linkplain #flagForCommit() flagged for commit}. | ||
* @return {@code true} if the current test-managed transaction is flagged | ||
* to be rolled back; {@code false} if the current test-managed transaction | ||
* is flagged to be committed | ||
* @throws IllegalStateException if a transaction is not active for the | ||
* current test | ||
* @see #isActive() | ||
* @see #flagForRollback() | ||
* @see #flagForCommit() | ||
*/ | ||
public static boolean isFlaggedForRollback() { | ||
return requireCurrentTransactionContext().isFlaggedForRollback(); | ||
} | ||
|
||
/** | ||
* Flag the current test-managed transaction for <em>rollback</em>. | ||
* <p>Invoking this method will <em>not</em> end the current transaction. | ||
* Rather, the value of this flag will be used to determine whether or not | ||
* the current test-managed transaction should be rolled back or committed | ||
* once it is {@linkplain #end ended}. | ||
* @throws IllegalStateException if a transaction is not active for the | ||
* current test | ||
* @see #isActive() | ||
* @see #isFlaggedForRollback() | ||
* @see #start() | ||
* @see #end() | ||
*/ | ||
public static void flagForRollback() { | ||
setFlaggedForRollback(true); | ||
} | ||
|
||
/** | ||
* Flag the current test-managed transaction for <em>commit</em>. | ||
* <p>Invoking this method will <em>not</em> end the current transaction. | ||
* Rather, the value of this flag will be used to determine whether or not | ||
* the current test-managed transaction should be rolled back or committed | ||
* once it is {@linkplain #end ended}. | ||
* @throws IllegalStateException if a transaction is not active for the | ||
* current test | ||
* @see #isActive() | ||
* @see #isFlaggedForRollback() | ||
* @see #start() | ||
* @see #end() | ||
*/ | ||
public static void flagForCommit() { | ||
setFlaggedForRollback(false); | ||
} | ||
|
||
/** | ||
* Start a new test-managed transaction. | ||
* <p>Only call this method if {@link #end} has been called or if no | ||
* transaction has been previously started. | ||
* @throws IllegalStateException if the transaction context could not be | ||
* retrieved or if a transaction is already active for the current test | ||
* @see #isActive() | ||
* @see #end() | ||
*/ | ||
public static void start() { | ||
requireCurrentTransactionContext().startTransaction(); | ||
} | ||
|
||
/** | ||
* Immediately force a <em>commit</em> or <em>rollback</em> of the current | ||
* test-managed transaction, according to the {@linkplain #isFlaggedForRollback | ||
* rollback flag}. | ||
* @throws IllegalStateException if the transaction context could not be | ||
* retrieved or if a transaction is not active for the current test | ||
* @see #isActive() | ||
* @see #start() | ||
*/ | ||
public static void end() { | ||
requireCurrentTransactionContext().endTransaction(); | ||
} | ||
|
||
private static TransactionContext requireCurrentTransactionContext() { | ||
TransactionContext txContext = TransactionContextHolder.getCurrentTransactionContext(); | ||
if (txContext == null) { | ||
throw new IllegalStateException("TransactionContext is not active"); | ||
} | ||
return txContext; | ||
} | ||
|
||
private static void setFlaggedForRollback(boolean flag) { | ||
requireCurrentTransactionContext().setFlaggedForRollback(flag); | ||
} | ||
|
||
} |
140 changes: 140 additions & 0 deletions
140
...g-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* Copyright 2002-2014 the original author or authors. | ||
* | ||
* 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 org.springframework.test.context.transaction; | ||
|
||
import org.apache.commons.logging.Log; | ||
import org.apache.commons.logging.LogFactory; | ||
import org.springframework.test.context.TestContext; | ||
import org.springframework.transaction.PlatformTransactionManager; | ||
import org.springframework.transaction.TransactionDefinition; | ||
import org.springframework.transaction.TransactionException; | ||
import org.springframework.transaction.TransactionStatus; | ||
|
||
/** | ||
* Transaction context for a specific {@link TestContext}. | ||
* | ||
* @author Sam Brannen | ||
* @author Juergen Hoeller | ||
* @since 4.1 | ||
* @see org.springframework.transaction.annotation.Transactional | ||
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener | ||
*/ | ||
class TransactionContext { | ||
|
||
private static final Log logger = LogFactory.getLog(TransactionContext.class); | ||
|
||
private final TestContext testContext; | ||
|
||
private final TransactionDefinition transactionDefinition; | ||
|
||
private final PlatformTransactionManager transactionManager; | ||
|
||
private final boolean defaultRollback; | ||
|
||
private boolean flaggedForRollback; | ||
|
||
private TransactionStatus transactionStatus; | ||
|
||
private volatile int transactionsStarted = 0; | ||
|
||
|
||
TransactionContext(TestContext testContext, PlatformTransactionManager transactionManager, | ||
TransactionDefinition transactionDefinition, boolean defaultRollback) { | ||
this.testContext = testContext; | ||
this.transactionManager = transactionManager; | ||
this.transactionDefinition = transactionDefinition; | ||
this.defaultRollback = defaultRollback; | ||
this.flaggedForRollback = defaultRollback; | ||
} | ||
|
||
TransactionStatus getTransactionStatus() { | ||
return this.transactionStatus; | ||
} | ||
|
||
/** | ||
* Has the current transaction been flagged for rollback? | ||
* <p>In other words, should we roll back or commit the current transaction | ||
* upon completion of the current test? | ||
*/ | ||
boolean isFlaggedForRollback() { | ||
return this.flaggedForRollback; | ||
} | ||
|
||
void setFlaggedForRollback(boolean flaggedForRollback) { | ||
if (this.transactionStatus == null) { | ||
throw new IllegalStateException(String.format( | ||
"Failed to set rollback flag for test context %s: transaction does not exist.", this.testContext)); | ||
} | ||
this.flaggedForRollback = flaggedForRollback; | ||
} | ||
|
||
/** | ||
* Start a new transaction for the configured {@linkplain #getTestContext test context}. | ||
* <p>Only call this method if {@link #endTransaction} has been called or if no | ||
* transaction has been previously started. | ||
* @throws TransactionException if starting the transaction fails | ||
*/ | ||
void startTransaction() { | ||
if (this.transactionStatus != null) { | ||
throw new IllegalStateException( | ||
"Cannot start a new transaction without ending the existing transaction first."); | ||
} | ||
this.flaggedForRollback = this.defaultRollback; | ||
this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition); | ||
++this.transactionsStarted; | ||
if (logger.isInfoEnabled()) { | ||
logger.info(String.format( | ||
"Began transaction (%s) for test context %s; transaction manager [%s]; rollback [%s]", | ||
this.transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback)); | ||
} | ||
} | ||
|
||
/** | ||
* Immediately force a <em>commit</em> or <em>rollback</em> of the transaction | ||
* for the configured {@linkplain #getTestContext test context}, according to | ||
* the {@linkplain #isFlaggedForRollback rollback flag}. | ||
*/ | ||
void endTransaction() { | ||
if (logger.isTraceEnabled()) { | ||
logger.trace(String.format( | ||
"Ending transaction for test context %s; transaction status [%s]; rollback [%s]", this.testContext, | ||
this.transactionStatus, flaggedForRollback)); | ||
} | ||
if (this.transactionStatus == null) { | ||
throw new IllegalStateException(String.format( | ||
"Failed to end transaction for test context %s: transaction does not exist.", this.testContext)); | ||
} | ||
|
||
try { | ||
if (flaggedForRollback) { | ||
this.transactionManager.rollback(this.transactionStatus); | ||
} | ||
else { | ||
this.transactionManager.commit(this.transactionStatus); | ||
} | ||
} | ||
finally { | ||
this.transactionStatus = null; | ||
} | ||
|
||
if (logger.isInfoEnabled()) { | ||
logger.info(String.format("%s transaction after test execution for test context %s.", | ||
(flaggedForRollback ? "Rolled back" : "Committed"), this.testContext)); | ||
} | ||
} | ||
|
||
} |
49 changes: 49 additions & 0 deletions
49
.../src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/* | ||
* Copyright 2002-2014 the original author or authors. | ||
* | ||
* 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 org.springframework.test.context.transaction; | ||
|
||
import org.springframework.core.NamedInheritableThreadLocal; | ||
|
||
/** | ||
* {@link InheritableThreadLocal}-based holder for the current {@link TransactionContext}. | ||
* | ||
* @author Sam Brannen | ||
* @since 4.1 | ||
*/ | ||
class TransactionContextHolder { | ||
|
||
private static final ThreadLocal<TransactionContext> currentTransactionContext = new NamedInheritableThreadLocal<TransactionContext>( | ||
"Test Transaction Context"); | ||
|
||
|
||
static TransactionContext getCurrentTransactionContext() { | ||
return currentTransactionContext.get(); | ||
} | ||
|
||
static void setCurrentTransactionContext(TransactionContext transactionContext) { | ||
currentTransactionContext.set(transactionContext); | ||
} | ||
|
||
static TransactionContext removeCurrentTransactionContext() { | ||
synchronized (currentTransactionContext) { | ||
TransactionContext transactionContext = currentTransactionContext.get(); | ||
currentTransactionContext.remove(); | ||
return transactionContext; | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.