001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.transaction.jta; 018 019import java.io.Serializable; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022 023import javax.transaction.InvalidTransactionException; 024import javax.transaction.NotSupportedException; 025import javax.transaction.SystemException; 026import javax.transaction.Transaction; 027import javax.transaction.TransactionManager; 028import javax.transaction.UserTransaction; 029 030import org.springframework.lang.Nullable; 031import org.springframework.transaction.TransactionDefinition; 032import org.springframework.transaction.TransactionSystemException; 033import org.springframework.util.Assert; 034 035/** 036 * Special {@link JtaTransactionManager} variant for BEA WebLogic (9.0 and higher). 037 * Supports the full power of Spring's transaction definitions on WebLogic's 038 * transaction coordinator, <i>beyond standard JTA</i>: transaction names, 039 * per-transaction isolation levels, and proper resuming of transactions in all cases. 040 * 041 * <p>Uses WebLogic's special {@code begin(name)} method to start a JTA transaction, 042 * in order to make <b>Spring-driven transactions visible in WebLogic's transaction 043 * monitor</b>. In case of Spring's declarative transactions, the exposed name will 044 * (by default) be the fully-qualified class name + "." + method name. 045 * 046 * <p>Supports a <b>per-transaction isolation level</b> through WebLogic's corresponding 047 * JTA transaction property "ISOLATION LEVEL". This will apply the specified isolation 048 * level (e.g. ISOLATION_SERIALIZABLE) to all JDBC Connections that participate in the 049 * given transaction. 050 * 051 * <p>Invokes WebLogic's special {@code forceResume} method if standard JTA resume 052 * failed, to <b>also resume if the target transaction was marked rollback-only</b>. 053 * If you're not relying on this feature of transaction suspension in the first 054 * place, Spring's standard JtaTransactionManager will behave properly too. 055 * 056 * <p>By default, the JTA UserTransaction and TransactionManager handles are 057 * fetched directly from WebLogic's {@code TransactionHelper}. This can be 058 * overridden by specifying "userTransaction"/"userTransactionName" and 059 * "transactionManager"/"transactionManagerName", passing in existing handles 060 * or specifying corresponding JNDI locations to look up. 061 * 062 * <p><b>NOTE: This JtaTransactionManager is intended to refine specific transaction 063 * demarcation behavior on Spring's side. It will happily co-exist with independently 064 * configured WebLogic transaction strategies in your persistence provider, with no 065 * need to specifically connect those setups in any way.</b> 066 * 067 * @author Juergen Hoeller 068 * @since 1.1 069 * @see org.springframework.transaction.TransactionDefinition#getName 070 * @see org.springframework.transaction.TransactionDefinition#getIsolationLevel 071 * @see weblogic.transaction.UserTransaction#begin(String) 072 * @see weblogic.transaction.Transaction#setProperty 073 * @see weblogic.transaction.TransactionManager#forceResume 074 * @see weblogic.transaction.TransactionHelper 075 */ 076@SuppressWarnings("serial") 077public class WebLogicJtaTransactionManager extends JtaTransactionManager { 078 079 private static final String USER_TRANSACTION_CLASS_NAME = "weblogic.transaction.UserTransaction"; 080 081 private static final String CLIENT_TRANSACTION_MANAGER_CLASS_NAME = "weblogic.transaction.ClientTransactionManager"; 082 083 private static final String TRANSACTION_CLASS_NAME = "weblogic.transaction.Transaction"; 084 085 private static final String TRANSACTION_HELPER_CLASS_NAME = "weblogic.transaction.TransactionHelper"; 086 087 private static final String ISOLATION_LEVEL_KEY = "ISOLATION LEVEL"; 088 089 090 private boolean weblogicUserTransactionAvailable; 091 092 @Nullable 093 private Method beginWithNameMethod; 094 095 @Nullable 096 private Method beginWithNameAndTimeoutMethod; 097 098 private boolean weblogicTransactionManagerAvailable; 099 100 @Nullable 101 private Method forceResumeMethod; 102 103 @Nullable 104 private Method setPropertyMethod; 105 106 @Nullable 107 private Object transactionHelper; 108 109 110 @Override 111 public void afterPropertiesSet() throws TransactionSystemException { 112 super.afterPropertiesSet(); 113 loadWebLogicTransactionClasses(); 114 } 115 116 @Override 117 @Nullable 118 protected UserTransaction retrieveUserTransaction() throws TransactionSystemException { 119 Object helper = loadWebLogicTransactionHelper(); 120 try { 121 logger.trace("Retrieving JTA UserTransaction from WebLogic TransactionHelper"); 122 Method getUserTransactionMethod = helper.getClass().getMethod("getUserTransaction"); 123 return (UserTransaction) getUserTransactionMethod.invoke(this.transactionHelper); 124 } 125 catch (InvocationTargetException ex) { 126 throw new TransactionSystemException( 127 "WebLogic's TransactionHelper.getUserTransaction() method failed", ex.getTargetException()); 128 } 129 catch (Exception ex) { 130 throw new TransactionSystemException( 131 "Could not invoke WebLogic's TransactionHelper.getUserTransaction() method", ex); 132 } 133 } 134 135 @Override 136 @Nullable 137 protected TransactionManager retrieveTransactionManager() throws TransactionSystemException { 138 Object helper = loadWebLogicTransactionHelper(); 139 try { 140 logger.trace("Retrieving JTA TransactionManager from WebLogic TransactionHelper"); 141 Method getTransactionManagerMethod = helper.getClass().getMethod("getTransactionManager"); 142 return (TransactionManager) getTransactionManagerMethod.invoke(this.transactionHelper); 143 } 144 catch (InvocationTargetException ex) { 145 throw new TransactionSystemException( 146 "WebLogic's TransactionHelper.getTransactionManager() method failed", ex.getTargetException()); 147 } 148 catch (Exception ex) { 149 throw new TransactionSystemException( 150 "Could not invoke WebLogic's TransactionHelper.getTransactionManager() method", ex); 151 } 152 } 153 154 private Object loadWebLogicTransactionHelper() throws TransactionSystemException { 155 Object helper = this.transactionHelper; 156 if (helper == null) { 157 try { 158 Class<?> transactionHelperClass = getClass().getClassLoader().loadClass(TRANSACTION_HELPER_CLASS_NAME); 159 Method getTransactionHelperMethod = transactionHelperClass.getMethod("getTransactionHelper"); 160 helper = getTransactionHelperMethod.invoke(null); 161 this.transactionHelper = helper; 162 logger.trace("WebLogic TransactionHelper found"); 163 } 164 catch (InvocationTargetException ex) { 165 throw new TransactionSystemException( 166 "WebLogic's TransactionHelper.getTransactionHelper() method failed", ex.getTargetException()); 167 } 168 catch (Exception ex) { 169 throw new TransactionSystemException( 170 "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", 171 ex); 172 } 173 } 174 return helper; 175 } 176 177 private void loadWebLogicTransactionClasses() throws TransactionSystemException { 178 try { 179 Class<?> userTransactionClass = getClass().getClassLoader().loadClass(USER_TRANSACTION_CLASS_NAME); 180 this.weblogicUserTransactionAvailable = userTransactionClass.isInstance(getUserTransaction()); 181 if (this.weblogicUserTransactionAvailable) { 182 this.beginWithNameMethod = userTransactionClass.getMethod("begin", String.class); 183 this.beginWithNameAndTimeoutMethod = userTransactionClass.getMethod("begin", String.class, int.class); 184 logger.debug("Support for WebLogic transaction names available"); 185 } 186 else { 187 logger.debug("Support for WebLogic transaction names not available"); 188 } 189 190 // Obtain WebLogic ClientTransactionManager interface. 191 Class<?> transactionManagerClass = 192 getClass().getClassLoader().loadClass(CLIENT_TRANSACTION_MANAGER_CLASS_NAME); 193 logger.trace("WebLogic ClientTransactionManager found"); 194 195 this.weblogicTransactionManagerAvailable = transactionManagerClass.isInstance(getTransactionManager()); 196 if (this.weblogicTransactionManagerAvailable) { 197 Class<?> transactionClass = getClass().getClassLoader().loadClass(TRANSACTION_CLASS_NAME); 198 this.forceResumeMethod = transactionManagerClass.getMethod("forceResume", Transaction.class); 199 this.setPropertyMethod = transactionClass.getMethod("setProperty", String.class, Serializable.class); 200 logger.debug("Support for WebLogic forceResume available"); 201 } 202 else { 203 logger.debug("Support for WebLogic forceResume not available"); 204 } 205 } 206 catch (Exception ex) { 207 throw new TransactionSystemException( 208 "Could not initialize WebLogicJtaTransactionManager because WebLogic API classes are not available", 209 ex); 210 } 211 } 212 213 private TransactionManager obtainTransactionManager() { 214 TransactionManager tm = getTransactionManager(); 215 Assert.state(tm != null, "No TransactionManager set"); 216 return tm; 217 } 218 219 220 @Override 221 protected void doJtaBegin(JtaTransactionObject txObject, TransactionDefinition definition) 222 throws NotSupportedException, SystemException { 223 224 int timeout = determineTimeout(definition); 225 226 // Apply transaction name (if any) to WebLogic transaction. 227 if (this.weblogicUserTransactionAvailable && definition.getName() != null) { 228 try { 229 if (timeout > TransactionDefinition.TIMEOUT_DEFAULT) { 230 /* 231 weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; 232 wut.begin(definition.getName(), timeout); 233 */ 234 Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized"); 235 this.beginWithNameAndTimeoutMethod.invoke(txObject.getUserTransaction(), definition.getName(), timeout); 236 } 237 else { 238 /* 239 weblogic.transaction.UserTransaction wut = (weblogic.transaction.UserTransaction) ut; 240 wut.begin(definition.getName()); 241 */ 242 Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized"); 243 this.beginWithNameMethod.invoke(txObject.getUserTransaction(), definition.getName()); 244 } 245 } 246 catch (InvocationTargetException ex) { 247 throw new TransactionSystemException( 248 "WebLogic's UserTransaction.begin() method failed", ex.getTargetException()); 249 } 250 catch (Exception ex) { 251 throw new TransactionSystemException( 252 "Could not invoke WebLogic's UserTransaction.begin() method", ex); 253 } 254 } 255 else { 256 // No WebLogic UserTransaction available or no transaction name specified 257 // -> standard JTA begin call. 258 applyTimeout(txObject, timeout); 259 txObject.getUserTransaction().begin(); 260 } 261 262 // Specify isolation level, if any, through corresponding WebLogic transaction property. 263 if (this.weblogicTransactionManagerAvailable) { 264 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 265 try { 266 Transaction tx = obtainTransactionManager().getTransaction(); 267 Integer isolationLevel = definition.getIsolationLevel(); 268 /* 269 weblogic.transaction.Transaction wtx = (weblogic.transaction.Transaction) tx; 270 wtx.setProperty(ISOLATION_LEVEL_KEY, isolationLevel); 271 */ 272 Assert.state(this.setPropertyMethod != null, "WebLogic JTA API not initialized"); 273 this.setPropertyMethod.invoke(tx, ISOLATION_LEVEL_KEY, isolationLevel); 274 } 275 catch (InvocationTargetException ex) { 276 throw new TransactionSystemException( 277 "WebLogic's Transaction.setProperty(String, Serializable) method failed", ex.getTargetException()); 278 } 279 catch (Exception ex) { 280 throw new TransactionSystemException( 281 "Could not invoke WebLogic's Transaction.setProperty(String, Serializable) method", ex); 282 } 283 } 284 } 285 else { 286 applyIsolationLevel(txObject, definition.getIsolationLevel()); 287 } 288 } 289 290 @Override 291 protected void doJtaResume(@Nullable JtaTransactionObject txObject, Object suspendedTransaction) 292 throws InvalidTransactionException, SystemException { 293 294 try { 295 obtainTransactionManager().resume((Transaction) suspendedTransaction); 296 } 297 catch (InvalidTransactionException ex) { 298 if (!this.weblogicTransactionManagerAvailable) { 299 throw ex; 300 } 301 302 if (logger.isDebugEnabled()) { 303 logger.debug("Standard JTA resume threw InvalidTransactionException: " + ex.getMessage() + 304 " - trying WebLogic JTA forceResume"); 305 } 306 /* 307 weblogic.transaction.TransactionManager wtm = 308 (weblogic.transaction.TransactionManager) getTransactionManager(); 309 wtm.forceResume(suspendedTransaction); 310 */ 311 try { 312 Assert.state(this.forceResumeMethod != null, "WebLogic JTA API not initialized"); 313 this.forceResumeMethod.invoke(getTransactionManager(), suspendedTransaction); 314 } 315 catch (InvocationTargetException ex2) { 316 throw new TransactionSystemException( 317 "WebLogic's TransactionManager.forceResume(Transaction) method failed", ex2.getTargetException()); 318 } 319 catch (Exception ex2) { 320 throw new TransactionSystemException( 321 "Could not access WebLogic's TransactionManager.forceResume(Transaction) method", ex2); 322 } 323 } 324 } 325 326 @Override 327 public Transaction createTransaction(@Nullable String name, int timeout) throws NotSupportedException, SystemException { 328 if (this.weblogicUserTransactionAvailable && name != null) { 329 try { 330 if (timeout >= 0) { 331 Assert.state(this.beginWithNameAndTimeoutMethod != null, "WebLogic JTA API not initialized"); 332 this.beginWithNameAndTimeoutMethod.invoke(getUserTransaction(), name, timeout); 333 } 334 else { 335 Assert.state(this.beginWithNameMethod != null, "WebLogic JTA API not initialized"); 336 this.beginWithNameMethod.invoke(getUserTransaction(), name); 337 } 338 } 339 catch (InvocationTargetException ex) { 340 if (ex.getTargetException() instanceof NotSupportedException) { 341 throw (NotSupportedException) ex.getTargetException(); 342 } 343 else if (ex.getTargetException() instanceof SystemException) { 344 throw (SystemException) ex.getTargetException(); 345 } 346 else if (ex.getTargetException() instanceof RuntimeException) { 347 throw (RuntimeException) ex.getTargetException(); 348 } 349 else { 350 throw new SystemException( 351 "WebLogic's begin() method failed with an unexpected error: " + ex.getTargetException()); 352 } 353 } 354 catch (Exception ex) { 355 throw new SystemException("Could not invoke WebLogic's UserTransaction.begin() method: " + ex); 356 } 357 return new ManagedTransactionAdapter(obtainTransactionManager()); 358 } 359 360 else { 361 // No name specified - standard JTA is sufficient. 362 return super.createTransaction(name, timeout); 363 } 364 } 365 366}