001/* 002 * Copyright 2002-2020 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.util.List; 020import javax.naming.NamingException; 021 022import com.ibm.websphere.uow.UOWSynchronizationRegistry; 023import com.ibm.wsspi.uow.UOWAction; 024import com.ibm.wsspi.uow.UOWActionException; 025import com.ibm.wsspi.uow.UOWException; 026import com.ibm.wsspi.uow.UOWManager; 027import com.ibm.wsspi.uow.UOWManagerFactory; 028 029import org.springframework.transaction.IllegalTransactionStateException; 030import org.springframework.transaction.InvalidTimeoutException; 031import org.springframework.transaction.NestedTransactionNotSupportedException; 032import org.springframework.transaction.TransactionDefinition; 033import org.springframework.transaction.TransactionException; 034import org.springframework.transaction.TransactionSystemException; 035import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager; 036import org.springframework.transaction.support.DefaultTransactionDefinition; 037import org.springframework.transaction.support.DefaultTransactionStatus; 038import org.springframework.transaction.support.SmartTransactionObject; 039import org.springframework.transaction.support.TransactionCallback; 040import org.springframework.transaction.support.TransactionSynchronization; 041import org.springframework.transaction.support.TransactionSynchronizationManager; 042import org.springframework.transaction.support.TransactionSynchronizationUtils; 043import org.springframework.util.ReflectionUtils; 044 045/** 046 * WebSphere-specific PlatformTransactionManager implementation that delegates 047 * to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's 048 * JNDI environment. This allows Spring to leverage the full power of the WebSphere 049 * transaction coordinator, including transaction suspension, in a manner that is 050 * perfectly compliant with officially supported WebSphere API. 051 * 052 * <p>The {@link CallbackPreferringPlatformTransactionManager} interface 053 * implemented by this class indicates that callers should preferably pass in 054 * a {@link TransactionCallback} through the {@link #execute} method, which 055 * will be handled through the callback-based WebSphere UOWManager API instead 056 * of through standard JTA API (UserTransaction / TransactionManager). This avoids 057 * the use of the non-public {@code javax.transaction.TransactionManager} 058 * API on WebSphere, staying within supported WebSphere API boundaries. 059 * 060 * <p>This transaction manager implementation derives from Spring's standard 061 * {@link JtaTransactionManager}, inheriting the capability to support programmatic 062 * transaction demarcation via {@code getTransaction} / {@code commit} / 063 * {@code rollback} calls through a JTA UserTransaction handle, for callers 064 * that do not use the TransactionCallback-based {@link #execute} method. However, 065 * transaction suspension is <i>not</i> supported in this {@code getTransaction} 066 * style (unless you explicitly specify a {@link #setTransactionManager} reference, 067 * despite the official WebSphere recommendations). Use the {@link #execute} style 068 * for any code that might require transaction suspension. 069 * 070 * <p>This transaction manager is compatible with WebSphere 6.1.0.9 and above. 071 * The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager". 072 * If the location happens to differ according to your WebSphere documentation, 073 * simply specify the actual location through this transaction manager's 074 * "uowManagerName" bean property. 075 * 076 * <p><b>NOTE: This JtaTransactionManager is intended to refine specific transaction 077 * demarcation behavior on Spring's side. It will happily co-exist with independently 078 * configured WebSphere transaction strategies in your persistence provider, with no 079 * need to specifically connect those setups in any way.</b> 080 * 081 * @author Juergen Hoeller 082 * @since 2.5 083 * @see #setUowManager 084 * @see #setUowManagerName 085 * @see com.ibm.wsspi.uow.UOWManager 086 */ 087@SuppressWarnings("serial") 088public class WebSphereUowTransactionManager extends JtaTransactionManager 089 implements CallbackPreferringPlatformTransactionManager { 090 091 /** 092 * Default JNDI location for the WebSphere UOWManager. 093 * @see #setUowManagerName 094 */ 095 public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager"; 096 097 098 private UOWManager uowManager; 099 100 private String uowManagerName; 101 102 103 /** 104 * Create a new WebSphereUowTransactionManager. 105 */ 106 public WebSphereUowTransactionManager() { 107 setAutodetectTransactionManager(false); 108 } 109 110 /** 111 * Create a new WebSphereUowTransactionManager for the given UOWManager. 112 * @param uowManager the WebSphere UOWManager to use as direct reference 113 */ 114 public WebSphereUowTransactionManager(UOWManager uowManager) { 115 this(); 116 this.uowManager = uowManager; 117 } 118 119 120 /** 121 * Set the WebSphere UOWManager to use as direct reference. 122 * <p>Typically just used for test setups; in a Java EE environment, 123 * the UOWManager will always be fetched from JNDI. 124 * @see #setUserTransactionName 125 */ 126 public void setUowManager(UOWManager uowManager) { 127 this.uowManager = uowManager; 128 } 129 130 /** 131 * Set the JNDI name of the WebSphere UOWManager. 132 * The default "java:comp/websphere/UOWManager" is used if not set. 133 * @see #DEFAULT_USER_TRANSACTION_NAME 134 * @see #setUowManager 135 */ 136 public void setUowManagerName(String uowManagerName) { 137 this.uowManagerName = uowManagerName; 138 } 139 140 141 @Override 142 public void afterPropertiesSet() throws TransactionSystemException { 143 initUserTransactionAndTransactionManager(); 144 145 // Fetch UOWManager handle from JNDI, if necessary. 146 if (this.uowManager == null) { 147 if (this.uowManagerName != null) { 148 this.uowManager = lookupUowManager(this.uowManagerName); 149 } 150 else { 151 this.uowManager = lookupDefaultUowManager(); 152 } 153 } 154 } 155 156 /** 157 * Look up the WebSphere UOWManager in JNDI via the configured name. 158 * @param uowManagerName the JNDI name of the UOWManager 159 * @return the UOWManager object 160 * @throws TransactionSystemException if the JNDI lookup failed 161 * @see #setJndiTemplate 162 * @see #setUowManagerName 163 */ 164 protected UOWManager lookupUowManager(String uowManagerName) throws TransactionSystemException { 165 try { 166 if (logger.isDebugEnabled()) { 167 logger.debug("Retrieving WebSphere UOWManager from JNDI location [" + uowManagerName + "]"); 168 } 169 return getJndiTemplate().lookup(uowManagerName, UOWManager.class); 170 } 171 catch (NamingException ex) { 172 throw new TransactionSystemException( 173 "WebSphere UOWManager is not available at JNDI location [" + uowManagerName + "]", ex); 174 } 175 } 176 177 /** 178 * Obtain the WebSphere UOWManager from the default JNDI location 179 * "java:comp/websphere/UOWManager". 180 * @return the UOWManager object 181 * @throws TransactionSystemException if the JNDI lookup failed 182 * @see #setJndiTemplate 183 */ 184 protected UOWManager lookupDefaultUowManager() throws TransactionSystemException { 185 try { 186 logger.debug("Retrieving WebSphere UOWManager from default JNDI location [" + DEFAULT_UOW_MANAGER_NAME + "]"); 187 return getJndiTemplate().lookup(DEFAULT_UOW_MANAGER_NAME, UOWManager.class); 188 } 189 catch (NamingException ex) { 190 logger.debug("WebSphere UOWManager is not available at default JNDI location [" + 191 DEFAULT_UOW_MANAGER_NAME + "] - falling back to UOWManagerFactory lookup"); 192 return UOWManagerFactory.getUOWManager(); 193 } 194 } 195 196 /** 197 * Registers the synchronizations as interposed JTA Synchronization on the UOWManager. 198 */ 199 @Override 200 protected void doRegisterAfterCompletionWithJtaTransaction( 201 JtaTransactionObject txObject, List<TransactionSynchronization> synchronizations) { 202 203 this.uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); 204 } 205 206 /** 207 * Returns {@code true} since WebSphere ResourceAdapters (as exposed in JNDI) 208 * implicitly perform transaction enlistment if the MessageEndpointFactory's 209 * {@code isDeliveryTransacted} method returns {@code true}. 210 * In that case we'll simply skip the {@link #createTransaction} call. 211 * @see javax.resource.spi.endpoint.MessageEndpointFactory#isDeliveryTransacted 212 * @see org.springframework.jca.endpoint.AbstractMessageEndpointFactory 213 * @see TransactionFactory#createTransaction 214 */ 215 @Override 216 public boolean supportsResourceAdapterManagedTransactions() { 217 return true; 218 } 219 220 221 @Override 222 public <T> T execute(TransactionDefinition definition, TransactionCallback<T> callback) throws TransactionException { 223 if (definition == null) { 224 // Use defaults if no transaction definition given. 225 definition = new DefaultTransactionDefinition(); 226 } 227 228 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { 229 throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); 230 } 231 int pb = definition.getPropagationBehavior(); 232 boolean existingTx = (this.uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE && 233 this.uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION); 234 235 int uowType = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION; 236 boolean joinTx = false; 237 boolean newSynch = false; 238 239 if (existingTx) { 240 if (pb == TransactionDefinition.PROPAGATION_NEVER) { 241 throw new IllegalTransactionStateException( 242 "Transaction propagation 'never' but existing transaction found"); 243 } 244 if (pb == TransactionDefinition.PROPAGATION_NESTED) { 245 throw new NestedTransactionNotSupportedException( 246 "Transaction propagation 'nested' not supported for WebSphere UOW transactions"); 247 } 248 if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || 249 pb == TransactionDefinition.PROPAGATION_REQUIRED || 250 pb == TransactionDefinition.PROPAGATION_MANDATORY) { 251 joinTx = true; 252 newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 253 } 254 else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { 255 uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; 256 newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); 257 } 258 else { 259 newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 260 } 261 } 262 else { 263 if (pb == TransactionDefinition.PROPAGATION_MANDATORY) { 264 throw new IllegalTransactionStateException( 265 "Transaction propagation 'mandatory' but no existing transaction found"); 266 } 267 if (pb == TransactionDefinition.PROPAGATION_SUPPORTS || 268 pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED || 269 pb == TransactionDefinition.PROPAGATION_NEVER) { 270 uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION; 271 newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); 272 } 273 else { 274 newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); 275 } 276 } 277 278 boolean debug = logger.isDebugEnabled(); 279 if (debug) { 280 logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); 281 } 282 SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null); 283 UOWActionAdapter<T> action = null; 284 try { 285 boolean actualTransaction = (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION); 286 if (actualTransaction && definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) { 287 this.uowManager.setUOWTimeout(uowType, definition.getTimeout()); 288 } 289 if (debug) { 290 logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx); 291 } action = new UOWActionAdapter<T>(definition, callback, actualTransaction, !joinTx, newSynch, debug); 293 this.uowManager.runUnderUOW(uowType, joinTx, action); 294 if (debug) { 295 logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx); 296 } 297 return action.getResult(); 298 } 299 catch (UOWException ex) { 300 TransactionSystemException tse = 301 new TransactionSystemException("UOWManager transaction processing failed", ex); 302 Throwable appEx = action.getException(); 303 if (appEx != null) { 304 logger.error("Application exception overridden by rollback exception", appEx); 305 tse.initApplicationException(appEx); 306 } 307 throw tse; 308 } 309 catch (UOWActionException ex) { 310 TransactionSystemException tse = 311 new TransactionSystemException("UOWManager transaction processing failed", ex); 312 Throwable appEx = action.getException(); 313 if (appEx != null) { 314 logger.error("Application exception overridden by rollback exception", appEx); 315 tse.initApplicationException(appEx); 316 } 317 throw tse; 318 } 319 finally { 320 if (suspendedResources != null) { 321 resume(null, suspendedResources); 322 } 323 } 324 } 325 326 327 /** 328 * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape. 329 */ 330 private class UOWActionAdapter<T> implements UOWAction, SmartTransactionObject { 331 332 private final TransactionDefinition definition; 333 334 private final TransactionCallback<T> callback; 335 336 private final boolean actualTransaction; 337 338 private final boolean newTransaction; 339 340 private final boolean newSynchronization; 341 342 private boolean debug; 343 344 private T result; 345 346 private Throwable exception; 347 348 public UOWActionAdapter(TransactionDefinition definition, TransactionCallback<T> callback, 349 boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) { 350 351 this.definition = definition; 352 this.callback = callback; 353 this.actualTransaction = actualTransaction; 354 this.newTransaction = newTransaction; 355 this.newSynchronization = newSynchronization; 356 this.debug = debug; 357 } 358 359 @Override 360 public void run() { 361 DefaultTransactionStatus status = prepareTransactionStatus( 362 this.definition, (this.actualTransaction ? this : null), 363 this.newTransaction, this.newSynchronization, this.debug, null); 364 try { 365 this.result = this.callback.doInTransaction(status); 366 triggerBeforeCommit(status); 367 } 368 catch (Throwable ex) { 369 this.exception = ex; 370 if (status.isDebug()) { 371 logger.debug("Rolling back on application exception from transaction callback", ex); 372 } 373 uowManager.setRollbackOnly(); 374 } 375 finally { 376 if (status.isLocalRollbackOnly()) { 377 if (status.isDebug()) { 378 logger.debug("Transaction callback has explicitly requested rollback"); 379 } 380 uowManager.setRollbackOnly(); 381 } 382 triggerBeforeCompletion(status); 383 if (status.isNewSynchronization()) { 384 List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations(); 385 TransactionSynchronizationManager.clear(); 386 if (!synchronizations.isEmpty()) { 387 uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations)); 388 } 389 } 390 } 391 } 392 393 public T getResult() { 394 if (this.exception != null) { 395 ReflectionUtils.rethrowRuntimeException(this.exception); 396 } 397 return this.result; 398 } 399 400 public Throwable getException() { 401 return this.exception; 402 } 403 404 @Override 405 public boolean isRollbackOnly() { 406 return uowManager.getRollbackOnly(); 407 } 408 409 @Override 410 public void flush() { 411 TransactionSynchronizationUtils.triggerFlush(); 412 } 413 } 414 415}