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