org.springframework.transaction.jta.WebSphereUowTransactionManager Maven / Gradle / Ivy
/*
* Copyright 2002-2020 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.util.List;
import javax.naming.NamingException;
import com.ibm.websphere.uow.UOWSynchronizationRegistry;
import com.ibm.wsspi.uow.UOWAction;
import com.ibm.wsspi.uow.UOWActionException;
import com.ibm.wsspi.uow.UOWException;
import com.ibm.wsspi.uow.UOWManager;
import com.ibm.wsspi.uow.UOWManagerFactory;
import org.springframework.lang.Nullable;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.InvalidTimeoutException;
import org.springframework.transaction.NestedTransactionNotSupportedException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.support.CallbackPreferringPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.SmartTransactionObject;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* WebSphere-specific PlatformTransactionManager implementation that delegates
* to a {@link com.ibm.wsspi.uow.UOWManager} instance, obtained from WebSphere's
* JNDI environment. This allows Spring to leverage the full power of the WebSphere
* transaction coordinator, including transaction suspension, in a manner that is
* perfectly compliant with officially supported WebSphere API.
*
* The {@link CallbackPreferringPlatformTransactionManager} interface
* implemented by this class indicates that callers should preferably pass in
* a {@link TransactionCallback} through the {@link #execute} method, which
* will be handled through the callback-based WebSphere UOWManager API instead
* of through standard JTA API (UserTransaction / TransactionManager). This avoids
* the use of the non-public {@code javax.transaction.TransactionManager}
* API on WebSphere, staying within supported WebSphere API boundaries.
*
*
This transaction manager implementation derives from Spring's standard
* {@link JtaTransactionManager}, inheriting the capability to support programmatic
* transaction demarcation via {@code getTransaction} / {@code commit} /
* {@code rollback} calls through a JTA UserTransaction handle, for callers
* that do not use the TransactionCallback-based {@link #execute} method. However,
* transaction suspension is not supported in this {@code getTransaction}
* style (unless you explicitly specify a {@link #setTransactionManager} reference,
* despite the official WebSphere recommendations). Use the {@link #execute} style
* for any code that might require transaction suspension.
*
*
This transaction manager is compatible with WebSphere 6.1.0.9 and above.
* The default JNDI location for the UOWManager is "java:comp/websphere/UOWManager".
* If the location happens to differ according to your WebSphere documentation,
* simply specify the actual location through this transaction manager's
* "uowManagerName" bean property.
*
*
NOTE: This JtaTransactionManager is intended to refine specific transaction
* demarcation behavior on Spring's side. It will happily co-exist with independently
* configured WebSphere transaction strategies in your persistence provider, with no
* need to specifically connect those setups in any way.
*
* @author Juergen Hoeller
* @since 2.5
* @see #setUowManager
* @see #setUowManagerName
* @see com.ibm.wsspi.uow.UOWManager
*/
@SuppressWarnings("serial")
public class WebSphereUowTransactionManager extends JtaTransactionManager
implements CallbackPreferringPlatformTransactionManager {
/**
* Default JNDI location for the WebSphere UOWManager.
* @see #setUowManagerName
*/
public static final String DEFAULT_UOW_MANAGER_NAME = "java:comp/websphere/UOWManager";
@Nullable
private UOWManager uowManager;
@Nullable
private String uowManagerName;
/**
* Create a new WebSphereUowTransactionManager.
*/
public WebSphereUowTransactionManager() {
setAutodetectTransactionManager(false);
}
/**
* Create a new WebSphereUowTransactionManager for the given UOWManager.
* @param uowManager the WebSphere UOWManager to use as direct reference
*/
public WebSphereUowTransactionManager(UOWManager uowManager) {
this();
this.uowManager = uowManager;
}
/**
* Set the WebSphere UOWManager to use as direct reference.
*
Typically just used for test setups; in a Java EE environment,
* the UOWManager will always be fetched from JNDI.
* @see #setUserTransactionName
*/
public void setUowManager(UOWManager uowManager) {
this.uowManager = uowManager;
}
/**
* Set the JNDI name of the WebSphere UOWManager.
* The default "java:comp/websphere/UOWManager" is used if not set.
* @see #DEFAULT_USER_TRANSACTION_NAME
* @see #setUowManager
*/
public void setUowManagerName(String uowManagerName) {
this.uowManagerName = uowManagerName;
}
@Override
public void afterPropertiesSet() throws TransactionSystemException {
initUserTransactionAndTransactionManager();
// Fetch UOWManager handle from JNDI, if necessary.
if (this.uowManager == null) {
if (this.uowManagerName != null) {
this.uowManager = lookupUowManager(this.uowManagerName);
}
else {
this.uowManager = lookupDefaultUowManager();
}
}
}
/**
* Look up the WebSphere UOWManager in JNDI via the configured name.
* @param uowManagerName the JNDI name of the UOWManager
* @return the UOWManager object
* @throws TransactionSystemException if the JNDI lookup failed
* @see #setJndiTemplate
* @see #setUowManagerName
*/
protected UOWManager lookupUowManager(String uowManagerName) throws TransactionSystemException {
try {
if (logger.isDebugEnabled()) {
logger.debug("Retrieving WebSphere UOWManager from JNDI location [" + uowManagerName + "]");
}
return getJndiTemplate().lookup(uowManagerName, UOWManager.class);
}
catch (NamingException ex) {
throw new TransactionSystemException(
"WebSphere UOWManager is not available at JNDI location [" + uowManagerName + "]", ex);
}
}
/**
* Obtain the WebSphere UOWManager from the default JNDI location
* "java:comp/websphere/UOWManager".
* @return the UOWManager object
* @throws TransactionSystemException if the JNDI lookup failed
* @see #setJndiTemplate
*/
protected UOWManager lookupDefaultUowManager() throws TransactionSystemException {
try {
logger.debug("Retrieving WebSphere UOWManager from default JNDI location [" + DEFAULT_UOW_MANAGER_NAME + "]");
return getJndiTemplate().lookup(DEFAULT_UOW_MANAGER_NAME, UOWManager.class);
}
catch (NamingException ex) {
logger.debug("WebSphere UOWManager is not available at default JNDI location [" +
DEFAULT_UOW_MANAGER_NAME + "] - falling back to UOWManagerFactory lookup");
return UOWManagerFactory.getUOWManager();
}
}
private UOWManager obtainUOWManager() {
Assert.state(this.uowManager != null, "No UOWManager set");
return this.uowManager;
}
/**
* Registers the synchronizations as interposed JTA Synchronization on the UOWManager.
*/
@Override
protected void doRegisterAfterCompletionWithJtaTransaction(
JtaTransactionObject txObject, List synchronizations) {
obtainUOWManager().registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
}
/**
* Returns {@code true} since WebSphere ResourceAdapters (as exposed in JNDI)
* implicitly perform transaction enlistment if the MessageEndpointFactory's
* {@code isDeliveryTransacted} method returns {@code true}.
* In that case we'll simply skip the {@link #createTransaction} call.
* @see javax.resource.spi.endpoint.MessageEndpointFactory#isDeliveryTransacted
* @see org.springframework.jca.endpoint.AbstractMessageEndpointFactory
* @see TransactionFactory#createTransaction
*/
@Override
public boolean supportsResourceAdapterManagedTransactions() {
return true;
}
@Override
@Nullable
public T execute(@Nullable TransactionDefinition definition, TransactionCallback callback)
throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
UOWManager uowManager = obtainUOWManager();
int pb = def.getPropagationBehavior();
boolean existingTx = (uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE &&
uowManager.getUOWType() != UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION);
int uowType = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION;
boolean joinTx = false;
boolean newSynch = false;
if (existingTx) {
if (pb == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Transaction propagation 'never' but existing transaction found");
}
if (pb == TransactionDefinition.PROPAGATION_NESTED) {
throw new NestedTransactionNotSupportedException(
"Transaction propagation 'nested' not supported for WebSphere UOW transactions");
}
if (pb == TransactionDefinition.PROPAGATION_SUPPORTS ||
pb == TransactionDefinition.PROPAGATION_REQUIRED ||
pb == TransactionDefinition.PROPAGATION_MANDATORY) {
joinTx = true;
newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
}
else if (pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
}
else {
newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
}
}
else {
if (pb == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"Transaction propagation 'mandatory' but no existing transaction found");
}
if (pb == TransactionDefinition.PROPAGATION_SUPPORTS ||
pb == TransactionDefinition.PROPAGATION_NOT_SUPPORTED ||
pb == TransactionDefinition.PROPAGATION_NEVER) {
uowType = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;
newSynch = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
}
else {
newSynch = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
}
}
boolean debug = logger.isDebugEnabled();
if (debug) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(null) : null);
UOWActionAdapter action = null;
try {
boolean actualTransaction = (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION);
if (actualTransaction && def.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
uowManager.setUOWTimeout(uowType, def.getTimeout());
}
if (debug) {
logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
}
action = new UOWActionAdapter<>(def, callback, actualTransaction, !joinTx, newSynch, debug);
uowManager.runUnderUOW(uowType, joinTx, action);
if (debug) {
logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
}
return action.getResult();
}
catch (UOWException | UOWActionException ex) {
TransactionSystemException tse =
new TransactionSystemException("UOWManager transaction processing failed", ex);
Throwable appEx = action.getException();
if (appEx != null) {
logger.error("Application exception overridden by rollback exception", appEx);
tse.initApplicationException(appEx);
}
throw tse;
}
finally {
if (suspendedResources != null) {
resume(null, suspendedResources);
}
}
}
/**
* Adapter that executes the given Spring transaction within the WebSphere UOWAction shape.
*/
private class UOWActionAdapter implements UOWAction, SmartTransactionObject {
private final TransactionDefinition definition;
private final TransactionCallback callback;
private final boolean actualTransaction;
private final boolean newTransaction;
private final boolean newSynchronization;
private boolean debug;
@Nullable
private T result;
@Nullable
private Throwable exception;
public UOWActionAdapter(TransactionDefinition definition, TransactionCallback callback,
boolean actualTransaction, boolean newTransaction, boolean newSynchronization, boolean debug) {
this.definition = definition;
this.callback = callback;
this.actualTransaction = actualTransaction;
this.newTransaction = newTransaction;
this.newSynchronization = newSynchronization;
this.debug = debug;
}
@Override
public void run() {
UOWManager uowManager = obtainUOWManager();
DefaultTransactionStatus status = prepareTransactionStatus(
this.definition, (this.actualTransaction ? this : null),
this.newTransaction, this.newSynchronization, this.debug, null);
try {
this.result = this.callback.doInTransaction(status);
triggerBeforeCommit(status);
}
catch (Throwable ex) {
this.exception = ex;
if (status.isDebug()) {
logger.debug("Rolling back on application exception from transaction callback", ex);
}
uowManager.setRollbackOnly();
}
finally {
if (status.isLocalRollbackOnly()) {
if (status.isDebug()) {
logger.debug("Transaction callback has explicitly requested rollback");
}
uowManager.setRollbackOnly();
}
triggerBeforeCompletion(status);
if (status.isNewSynchronization()) {
List synchronizations = TransactionSynchronizationManager.getSynchronizations();
TransactionSynchronizationManager.clear();
if (!synchronizations.isEmpty()) {
uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
}
}
}
}
@Nullable
public T getResult() {
if (this.exception != null) {
ReflectionUtils.rethrowRuntimeException(this.exception);
}
return this.result;
}
@Nullable
public Throwable getException() {
return this.exception;
}
@Override
public boolean isRollbackOnly() {
return obtainUOWManager().getRollbackOnly();
}
@Override
public void flush() {
TransactionSynchronizationUtils.triggerFlush();
}
}
}