1 changed files with 362 additions and 0 deletions
@ -0,0 +1,362 @@
@@ -0,0 +1,362 @@
|
||||
/* |
||||
* Copyright 2002-present 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 |
||||
* |
||||
* https://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.transaction.jta; |
||||
|
||||
import java.io.Serializable; |
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.lang.reflect.Method; |
||||
|
||||
import jakarta.transaction.InvalidTransactionException; |
||||
import jakarta.transaction.NotSupportedException; |
||||
import jakarta.transaction.SystemException; |
||||
import jakarta.transaction.Transaction; |
||||
import jakarta.transaction.TransactionManager; |
||||
import jakarta.transaction.UserTransaction; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.transaction.TransactionDefinition; |
||||
import org.springframework.transaction.TransactionSystemException; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Special {@link JtaTransactionManager} variant for Oracle WebLogic 15.1.1 and higher. |
||||
* Supports the full power of Spring's transaction definitions on WebLogic's |
||||
* transaction coordinator, <i>beyond standard JTA</i>: transaction names, |
||||
* per-transaction isolation levels, and proper resuming of transactions in all cases. |
||||
* |
||||
* <p>Uses WebLogic's special {@code begin(name)} method to start a JTA transaction, |
||||
* in order to make <b>Spring-driven transactions visible in WebLogic's transaction |
||||
* monitor</b>. In case of Spring's declarative transactions, the exposed name will |
||||
* (by default) be the fully-qualified class name + "." + method name. |
||||
* |
||||
* <p>Supports a <b>per-transaction isolation level</b> through WebLogic's corresponding |
||||
* JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation |
||||
* level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the |
||||
* given transaction. |
||||
* |
||||
* <p>Invokes WebLogic's special {@code forceResume} method if standard JTA resume |
||||
* failed, to <b>also resume if the target transaction was marked rollback-only</b>. |
||||
* If you're not relying on this feature of transaction suspension in the first |
||||
* place, Spring's standard JtaTransactionManager will behave properly too. |
||||
* |
||||
* <p>By default, the JTA UserTransaction and TransactionManager handles are |
||||
* fetched directly from WebLogic's {@code TransactionHelper}. This can be |
||||
* overridden by specifying "userTransaction"/"userTransactionName" and |
||||
* "transactionManager"/"transactionManagerName", passing in existing handles |
||||
* or specifying corresponding JNDI locations to look up. |
||||
* |
||||
* <p>Note: This class was initially removed as of Spring Framework 6.0 but then |
||||
* brought back after the WebLogic 15.1.1 release which finally delivers Jakarta EE 9 |
||||
* compatibility. As of Spring Framework 6.2.16, it is available again for manual |
||||
* configuration - as a replacement for the standard {@link JtaTransactionManager}. |
||||
* |
||||
* @author Juergen Hoeller |
||||
* @since 6.2.16 |
||||
* @see org.springframework.transaction.TransactionDefinition#getName() |
||||
* @see org.springframework.transaction.TransactionDefinition#getIsolationLevel() |
||||
*/ |
||||
@SuppressWarnings("serial") |
||||
public class WebLogicJtaTransactionManager extends JtaTransactionManager { |
||||
|
||||
private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction"; |
||||
|
||||
private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager"; |
||||
|
||||
private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction"; |
||||
|
||||
private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper"; |
||||
|
||||
private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL"; |
||||
|
||||
|
||||
private boolean weblogicUserTransactionAvailable; |
||||
|
||||
@Nullable |
||||
private Method beginWithNameMethod; |
||||
|
||||
@Nullable |
||||
private Method beginWithNameAndTimeoutMethod; |
||||
|
||||
private boolean weblogicTransactionManagerAvailable; |
||||
|
||||
@Nullable |
||||
private Method forceResumeMethod; |
||||
|
||||
@Nullable |
||||
private Method setPropertyMethod; |
||||
|
||||
@Nullable |
||||
private Object transactionHelper; |
||||
|
||||
|
||||
@Override |
||||
public void afterPropertiesSet() throws TransactionSystemException { |
||||
super.afterPropertiesSet(); |
||||
loadWebLogicTransactionClasses(); |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { |
||||
Object helper = loadWebLogicTransactionHelper(); |
||||
try { |
||||
logger.trace("Retrieving JTA UserTransaction from WebLogic TransactionHelper"); |
||||
Method getUserTransactionMethod = helper.getClass().getMethod("getUserTransaction"); |
||||
return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper); |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's TransactionHelper.getUserTransaction() method failed", ex.getTargetException()); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not invoke WebLogic's TransactionHelper.getUserTransaction() method", ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
@Nullable |
||||
protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { |
||||
Object helper = loadWebLogicTransactionHelper(); |
||||
try { |
||||
logger.trace("Retrieving JTA TransactionManager from WebLogic TransactionHelper"); |
||||
Method getTransactionManagerMethod = helper.getClass().getMethod("getTransactionManager"); |
||||
return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper); |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's TransactionHelper.getTransactionManager() method failed", ex.getTargetException()); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not invoke WebLogic's TransactionHelper.getTransactionManager() method", ex); |
||||
} |
||||
} |
||||
|
||||
private Object loadWebLogicTransactionHelper() throws TransactionSystemException { |
||||
Object helper = this.transactionHelper; |
||||
if (helper == null) { |
||||
try { |
||||
Class<?> transactionHelperClass = getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME); |
||||
Method getTransactionHelperMethod = transactionHelperClass.getMethod("getTransactionHelper"); |
||||
helper = getTransactionHelperMethod.invoke(null); |
||||
this.transactionHelper = helper; |
||||
logger.trace("WebLogic TransactionHelper found"); |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException()); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", |
||||
ex); |
||||
} |
||||
} |
||||
return helper; |
||||
} |
||||
|
||||
private void loadWebLogicTransactionClasses() throws TransactionSystemException { |
||||
try { |
||||
Class<?> userTransactionClass = getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME); |
||||
this.weblogicUserTransactionAvailable = userTransactionClass.isInstance(getUserTransaction()); |
||||
if (this.weblogicUserTransactionAvailable) { |
||||
this.beginWithNameMethod = userTransactionClass.getMethod("begin", String.class); |
||||
this.beginWithNameAndTimeoutMethod = userTransactionClass.getMethod("begin", String.class, int.class); |
||||
logger.debug("Support for WebLogic transaction names available"); |
||||
} |
||||
else { |
||||
logger.debug("Support for WebLogic transaction names not available"); |
||||
} |
||||
|
||||
// Obtain WebLogic ClientTransactionManager interface.
|
||||
Class<?> transactionManagerClass = |
||||
getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME); |
||||
logger.trace("WebLogic ClientTransactionManager found"); |
||||
|
||||
this.weblogicTransactionManagerAvailable = transactionManagerClass.isInstance(getTransactionManager()); |
||||
if (this.weblogicTransactionManagerAvailable) { |
||||
Class<?> transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME); |
||||
this.forceResumeMethod = transactionManagerClass.getMethod("forceResume", Transaction.class); |
||||
this.setPropertyMethod = transactionClass.getMethod("setProperty", String.class, Serializable.class); |
||||
logger.debug("Support for WebLogic forceResume available"); |
||||
} |
||||
else { |
||||
logger.debug("Support for WebLogic forceResume not available"); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", |
||||
ex); |
||||
} |
||||
} |
||||
|
||||
private TransactionManager obtainTransactionManager() { |
||||
TransactionManager tm = getTransactionManager(); |
||||
Assert.state(tm != null, "No TransactionManager set"); |
||||
return tm; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) |
||||
throws NotSupportedException, SystemException { |
||||
|
||||
int timeout = determineTimeout(definition); |
||||
|
||||
// Apply transaction name (if any) to WebLogic transaction.
|
||||
if (this.weblogicUserTransactionAvailable && definition.getName() != null) { |
||||
try { |
||||
if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { |
||||
/* |
||||
weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; |
||||
wut.begin(definition.getName(), timeout); |
||||
*/ |
||||
Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized"); |
||||
this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), definition.getName(), timeout); |
||||
} |
||||
else { |
||||
/* |
||||
weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; |
||||
wut.begin(definition.getName()); |
||||
*/ |
||||
Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized"); |
||||
this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName()); |
||||
} |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's UserTransaction.begin() method failed", ex.getTargetException()); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not invoke WebLogic's UserTransaction.begin() method", ex); |
||||
} |
||||
} |
||||
else { |
||||
// No WebLogic UserTransaction available or no transaction name specified
|
||||
// -> standard JTA begin call.
|
||||
applyTimeout(txObject, timeout); |
||||
txObject.getUserTransaction().begin(); |
||||
} |
||||
|
||||
// Specify isolation level, if any, through corresponding WebLogic transaction property.
|
||||
if (this.weblogicTransactionManagerAvailable) { |
||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { |
||||
try { |
||||
Transaction tx = obtainTransactionManager().getTransaction(); |
||||
Integer isolationLevel = definition.getIsolationLevel(); |
||||
/* |
||||
weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx; |
||||
wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel); |
||||
*/ |
||||
Assert.state(this.setPropertyMethod != null, "WebLogic JTA API not initialized"); |
||||
this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel); |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException()); |
||||
} |
||||
catch (Exception ex) { |
||||
throw new TransactionSystemException( |
||||
"Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex); |
||||
} |
||||
} |
||||
} |
||||
else { |
||||
applyIsolationLevel(txObject, definition.getIsolationLevel()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void doJtaResume(@Nullable JtaTransactionObject txObject, Object suspendedTransaction) |
||||
throws InvalidTransactionException, SystemException { |
||||
|
||||
try { |
||||
obtainTransactionManager().resume((Transaction) suspendedTransaction); |
||||
} |
||||
catch (InvalidTransactionException ex) { |
||||
if (!this.weblogicTransactionManagerAvailable) { |
||||
throw ex; |
||||
} |
||||
|
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() + |
||||
" - trying WebLogic JTA forceResume"); |
||||
} |
||||
/* |
||||
weblogic.transaction.TransactionManager wtm = |
||||
(weblogic.transaction.TransactionManager) getTransactionManager(); |
||||
wtm.forceResume(suspendedTransaction); |
||||
*/ |
||||
try { |
||||
Assert.state(this.forceResumeMethod != null, "WebLogic JTA API not initialized"); |
||||
this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction); |
||||
} |
||||
catch (InvocationTargetException ex2) { |
||||
throw new TransactionSystemException( |
||||
"WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException()); |
||||
} |
||||
catch (Exception ex2) { |
||||
throw new TransactionSystemException( |
||||
"Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Transaction createTransaction(@Nullable String name, int timeout) throws NotSupportedException, SystemException { |
||||
if (this.weblogicUserTransactionAvailable && name != null) { |
||||
try { |
||||
if (timeout >= 0) { |
||||
Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized"); |
||||
this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout); |
||||
} |
||||
else { |
||||
Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized"); |
||||
this.beginWithNameMethod.invoke(getUserTransaction(), name); |
||||
} |
||||
} |
||||
catch (InvocationTargetException ex) { |
||||
if (ex.getTargetException() instanceof NotSupportedException) { |
||||
throw (NotSupportedException) ex.getTargetException(); |
||||
} |
||||
else if (ex.getTargetException() instanceof SystemException) { |
||||
throw (SystemException) ex.getTargetException(); |
||||
} |
||||
else if (ex.getTargetException() instanceof RuntimeException) { |
||||
throw (RuntimeException) ex.getTargetException(); |
||||
} |
||||
else { |
||||
throw new SystemException( |
||||
"WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException()); |
||||
} |
||||
} |
||||
catch (Exception ex) { |
||||
throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex); |
||||
} |
||||
return new ManagedTransactionAdapter(obtainTransactionManager()); |
||||
} |
||||
|
||||
else { |
||||
// No name specified - standard JTA is sufficient.
|
||||
return super.createTransaction(name, timeout); |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue