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