diff --git a/spring-tx/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java new file mode 100644 index 00000000000..78a655ddebd --- /dev/null +++ b/spring-tx/src/main/java/org/springframework/transaction/jta/WebLogicJtaTransactionManager.java @@ -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, beyond standard JTA: transaction names, + * per-transaction isolation levels, and proper resuming of transactions in all cases. + * + *

Uses WebLogic's special {@code begin(name)} method to start a JTA transaction, + * in order to make Spring-driven transactions visible in WebLogic's transaction + * monitor. In case of Spring's declarative transactions, the exposed name will + * (by default) be the fully-qualified class name + "." + method name. + * + *

Supports a per-transaction isolation level 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. + * + *

Invokes WebLogic's special {@code forceResume} method if standard JTA resume + * failed, to also resume if the target transaction was marked rollback-only. + * If you're not relying on this feature of transaction suspension in the first + * place, Spring's standard JtaTransactionManager will behave properly too. + * + *

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. + * + *

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); + } + } + +}