Open Source Repository

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



org/springframework/jca/endpoint/AbstractMessageEndpointFactory.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.jca.endpoint;

import java.lang.reflect.Method;
import javax.resource.ResourceException;
import javax.resource.spi.ApplicationServerInternalException;
import javax.resource.spi.UnavailableException;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.transaction.jta.SimpleTransactionFactory;
import org.springframework.transaction.jta.TransactionFactory;

/**
 * Abstract base implementation of the JCA 1.5/1.6
 {@link javax.resource.spi.endpoint.MessageEndpointFactory} interface,
 * providing transaction management capabilities as well as ClassLoader
 * exposure for endpoint invocations.
 *
 @author Juergen Hoeller
 @since 2.5
 @see #setTransactionManager
 */
public abstract class AbstractMessageEndpointFactory implements MessageEndpointFactory {

  /** Logger available to subclasses */
  protected final Log logger = LogFactory.getLog(getClass());

  private TransactionFactory transactionFactory;

  private String transactionName;

  private int transactionTimeout = -1;


  /**
   * Set the the XA transaction manager to use for wrapping endpoint
   * invocations, enlisting the endpoint resource in each such transaction.
   <p>The passed-in object may be a transaction manager which implements
   * Spring's {@link org.springframework.transaction.jta.TransactionFactory}
   * interface, or a plain {@link javax.transaction.TransactionManager}.
   <p>If no transaction manager is specified, the endpoint invocation
   * will simply not be wrapped in an XA transaction. Check out your
   * resource provider's ActivationSpec documentation for local
   * transaction options of your particular provider.
   @see #setTransactionName
   @see #setTransactionTimeout
   */
  public void setTransactionManager(Object transactionManager) {
    if (transactionManager instanceof TransactionFactory) {
      this.transactionFactory = (TransactionFactorytransactionManager;
    }
    else if (transactionManager instanceof TransactionManager) {
      this.transactionFactory = new SimpleTransactionFactory((TransactionManagertransactionManager);
    }
    else {
      throw new IllegalArgumentException("Transaction manager [" + transactionManager +
          "] is neither a [org.springframework.transaction.jta.TransactionFactory} nor a " +
          "[javax.transaction.TransactionManager]");
    }
  }

  /**
   * Set the Spring TransactionFactory to use for wrapping endpoint
   * invocations, enlisting the endpoint resource in each such transaction.
   <p>Alternatively, specify an appropriate transaction manager through
   * the {@link #setTransactionManager "transactionManager"} property.
   <p>If no transaction factory is specified, the endpoint invocation
   * will simply not be wrapped in an XA transaction. Check out your
   * resource provider's ActivationSpec documentation for local
   * transaction options of your particular provider.
   @see #setTransactionName
   @see #setTransactionTimeout
   */
  public void setTransactionFactory(TransactionFactory transactionFactory) {
    this.transactionFactory = transactionFactory;
  }

  /**
   * Specify the name of the transaction, if any.
   <p>Default is none. A specified name will be passed on to the transaction
   * manager, allowing to identify the transaction in a transaction monitor.
   */
  public void setTransactionName(String transactionName) {
    this.transactionName = transactionName;
  }

  /**
   * Specify the transaction timeout, if any.
   <p>Default is -1: rely on the transaction manager's default timeout.
   * Specify a concrete timeout to restrict the maximum duration of each
   * endpoint invocation.
   */
  public void setTransactionTimeout(int transactionTimeout) {
    this.transactionTimeout = transactionTimeout;
  }


  /**
   * This implementation returns <code>true</code> if a transaction manager
   * has been specified; <code>false</code> otherwise.
   @see #setTransactionManager
   @see #setTransactionFactory
   */
  public boolean isDeliveryTransacted(Method methodthrows NoSuchMethodException {
    return (this.transactionFactory != null);
  }

  /**
   * The standard JCA 1.5 version of <code>createEndpoint</code>.
   <p>This implementation delegates to {@link #createEndpointInternal()},
   * initializing the endpoint's XAResource before the endpoint gets invoked.
   */
  public MessageEndpoint createEndpoint(XAResource xaResourcethrows UnavailableException {
    AbstractMessageEndpoint endpoint = createEndpointInternal();
    endpoint.initXAResource(xaResource);
    return endpoint;
  }

  /**
   * The alternative JCA 1.6 version of <code>createEndpoint</code>.
   <p>This implementation delegates to {@link #createEndpointInternal()},
   * ignoring the specified timeout. It is only here for JCA 1.6 compliance.
   */
  public MessageEndpoint createEndpoint(XAResource xaResource, long timeoutthrows UnavailableException {
    AbstractMessageEndpoint endpoint = createEndpointInternal();
    endpoint.initXAResource(xaResource);
    return endpoint;
  }

  /**
   * Create the actual endpoint instance, as a subclass of the
   {@link AbstractMessageEndpoint} inner class of this factory.
   @return the actual endpoint instance (never <code>null</code>)
   @throws UnavailableException if no endpoint is available at present
   */
  protected abstract AbstractMessageEndpoint createEndpointInternal()
      throws UnavailableException;


  /**
   * Inner class for actual endpoint implementations, based on template
   * method to allow for any kind of concrete endpoint implementation.
   */
  protected abstract class AbstractMessageEndpoint implements MessageEndpoint {

    private TransactionDelegate transactionDelegate;

    private boolean beforeDeliveryCalled = false;

    private ClassLoader previousContextClassLoader;

    /**
     * Initialize this endpoint's TransactionDelegate.
     @param xaResource the XAResource for this endpoint
     */
    void initXAResource(XAResource xaResource) {
      this.transactionDelegate = new TransactionDelegate(xaResource);
    }

    /**
     * This <code>beforeDelivery</code> implementation starts a transaction,
     * if necessary, and exposes the endpoint ClassLoader as current
     * thread context ClassLoader.
     <p>Note that the JCA 1.5 specification does not require a ResourceAdapter
     * to call this method before invoking the concrete endpoint. If this method
     * has not been called (check {@link #hasBeforeDeliveryBeenCalled()}), the
     * concrete endpoint method should call <code>beforeDelivery</code> and its
     * sibling {@link #afterDelivery()} explicitly, as part of its own processing.
     */
    public void beforeDelivery(Method methodthrows ResourceException {
      this.beforeDeliveryCalled = true;
      try {
        this.transactionDelegate.beginTransaction();
      }
      catch (Throwable ex) {
        throw new ApplicationServerInternalException("Failed to begin transaction", ex);
      }
      Thread currentThread = Thread.currentThread();
      this.previousContextClassLoader = currentThread.getContextClassLoader();
      currentThread.setContextClassLoader(getEndpointClassLoader());
    }

    /**
     * Template method for exposing the endpoint's ClassLoader
     * (typically the ClassLoader that the message listener class
     * has been loaded with).
     @return the endpoint ClassLoader (never <code>null</code>)
     */
    protected abstract ClassLoader getEndpointClassLoader();

    /**
     * Return whether the {@link #beforeDelivery} method of this endpoint
     * has already been called.
     */
    protected final boolean hasBeforeDeliveryBeenCalled() {
      return this.beforeDeliveryCalled;
    }

    /**
     * Callback method for notifying the endpoint base class
     * that the concrete endpoint invocation led to an exception.
     <p>To be invoked by subclasses in case of the concrete
     * endpoint throwing an exception.
     @param ex the exception thrown from the concrete endpoint
     */
    protected final void onEndpointException(Throwable ex) {
      this.transactionDelegate.setRollbackOnly();
    }

    /**
     * This <code>afterDelivery</code> implementation resets the thread context
     * ClassLoader and completes the transaction, if any.
     <p>Note that the JCA 1.5 specification does not require a ResourceAdapter
     * to call this method after invoking the concrete endpoint. See the
     * explanation in {@link #beforeDelivery}'s javadoc.
     */
    public void afterDelivery() throws ResourceException {
      this.beforeDeliveryCalled = false;
      Thread.currentThread().setContextClassLoader(this.previousContextClassLoader);
      this.previousContextClassLoader = null;
      try {
        this.transactionDelegate.endTransaction();
      }
      catch (Throwable ex) {
        throw new ApplicationServerInternalException("Failed to complete transaction", ex);
      }
    }

    public void release() {
      try {
        this.transactionDelegate.setRollbackOnly();
        this.transactionDelegate.endTransaction();
      }
      catch (Throwable ex) {
        logger.error("Could not complete unfinished transaction on endpoint release", ex);
      }
    }
  }


  /**
   * Private inner class that performs the actual transaction handling,
   * including enlistment of the endpoint's XAResource.
   */
  private class TransactionDelegate {

    private final XAResource xaResource;

    private Transaction transaction;

    private boolean rollbackOnly;

    public TransactionDelegate(XAResource xaResource) {
      if (xaResource == null) {
        if (transactionFactory != null && !transactionFactory.supportsResourceAdapterManagedTransactions()) {
          throw new IllegalStateException("ResourceAdapter-provided XAResource is required for " +
              "transaction management. Check your ResourceAdapter's configuration.");
        }
      }
      this.xaResource = xaResource;
    }

    public void beginTransaction() throws Exception {
      if (transactionFactory != null && this.xaResource != null) {
        this.transaction = transactionFactory.createTransaction(transactionName, transactionTimeout);
        this.transaction.enlistResource(this.xaResource);
      }
    }

    public void setRollbackOnly() {
      if (this.transaction != null) {
        this.rollbackOnly = true;
      }
    }

    public void endTransaction() throws Exception {
      if (this.transaction != null) {
        try {
          if (this.rollbackOnly) {
            this.transaction.rollback();
          }
          else {
            this.transaction.commit();
          }
        }
        finally {
          this.transaction = null;
          this.rollbackOnly = false;
        }
      }
    }
  }

}