Open Source Repository

Home /spring/spring-transaction-3.0.5 | Repository Home


org/springframework/transaction/jta/WebSphereUowTransactionManager.java
/*
 * Copyright 2002-2010 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
 *
 *      http://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.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.DefaultTransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
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.
 *
 <p>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</code>
 * API on WebSphere, staying within supported WebSphere API boundaries.
 *
 <p>This transaction manager implementation derives from Spring's standard
 {@link JtaTransactionManager}, inheriting the capability to support programmatic
 * transaction demarcation via <code>getTransaction</code> <code>commit</code> /
 <code>rollback</code> calls through a JTA UserTransaction handle, for callers
 * that do not use the TransactionCallback-based {@link #execute} method. However,
 * transaction suspension is <i>not</i> supported in this <code>getTransaction</code>
 * 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.
 *
 <p>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.
 *
 @author Juergen Hoeller
 @since 2.5
 @see #setUowManager
 @see #setUowManagerName
 @see com.ibm.wsspi.uow.UOWManager
 */
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";


  private UOWManager uowManager;

  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.
   <p>Typically just used for test setups; in a J2EE 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 uowManagerNamethrows 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();
    }
  }

  /**
   * Registers the synchronizations as interposed JTA Synchronization on the UOWManager.
   */
  @Override
  protected void doRegisterAfterCompletionWithJtaTransaction(
      JtaTransactionObject txObject, List<TransactionSynchronization> synchronizations) {

    this.uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
  }

  /**
   * Returns <code>true</code> since WebSphere ResourceAdapters (as exposed in JNDI)
   * implicitly perform transaction enlistment if the MessageEndpointFactory's
   <code>isDeliveryTransacted</code> method returns <code>true</code>.
   * 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;
  }


  public <T> T execute(TransactionDefinition definition, TransactionCallback<T> callbackthrows TransactionException {
    if (definition == null) {
      // Use defaults if no transaction definition given.
      definition = new DefaultTransactionDefinition();
    }

    if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
      throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
    }
    int pb = definition.getPropagationBehavior();
    boolean existingTx = (this.uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE &&
        this.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 [" + definition.getName() "]: " + definition);
    }
    SuspendedResourcesHolder suspendedResources = (!joinTx ? suspend(nullnull);
    try {
      if (definition.getTimeout() > TransactionDefinition.TIMEOUT_DEFAULT) {
        this.uowManager.setUOWTimeout(uowType, definition.getTimeout());
      }
      if (debug) {
        logger.debug("Invoking WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
      }
      UOWActionAdapter<T> action = new UOWActionAdapter<T>(
          definition, callback, (uowType == UOWManager.UOW_TYPE_GLOBAL_TRANSACTION), !joinTx, newSynch, debug);
      this.uowManager.runUnderUOW(uowType, joinTx, action);
      if (debug) {
        logger.debug("Returned from WebSphere UOW action: type=" + uowType + ", join=" + joinTx);
      }
      return action.getResult();
    }
    catch (UOWException ex) {
      throw new TransactionSystemException("UOWManager transaction processing failed", ex);
    }
    catch (UOWActionException ex) {
      throw new TransactionSystemException("UOWManager threw unexpected UOWActionException", ex);
    }
    finally {
      if (suspendedResources != null) {
        resume(null, suspendedResources);
      }
    }
  }


  /**
   * Adapter that executes the given Spring transaction within the WebSphere UOWAction shape.
   */
  private class UOWActionAdapter<T> implements UOWAction {

    private final TransactionDefinition definition;

    private final TransactionCallback<T> callback;

    private final boolean actualTransaction;

    private final boolean newTransaction;

    private final boolean newSynchronization;

    private boolean debug;

    private T result;

    private Throwable exception;

    public UOWActionAdapter(TransactionDefinition definition, TransactionCallback<T> 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;
    }

    public void run() {
      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;
        uowManager.setRollbackOnly();
      }
      finally {
        if (status.isLocalRollbackOnly()) {
          if (status.isDebug()) {
            logger.debug("Transactional code has requested rollback");
          }
          uowManager.setRollbackOnly();
        }
        triggerBeforeCompletion(status);
        if (status.isNewSynchronization()) {
          List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
          TransactionSynchronizationManager.clear();
          if (!synchronizations.isEmpty()) {
            uowManager.registerInterposedSynchronization(new JtaAfterCompletionSynchronization(synchronizations));
          }
        }
      }
    }

    public T getResult() {
      if (this.exception != null) {
        ReflectionUtils.rethrowRuntimeException(this.exception);
      }
      return this.result;
    }
  }

}