/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.inflow;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.resource.ResourceException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;
import org.jboss.ejb.MessageDrivenContainer;
import org.jboss.invocation.Invocation;
import org.jboss.logging.Logger;
import org.jboss.proxy.Interceptor;
/**
* Implements the application server message endpoint requirements.
*
* @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
* @version $Revision: 67780 $
*/
public class MessageEndpointInterceptor extends Interceptor
{
/** The serialVersionUID */
private static final long serialVersionUID = -8740717288847385688L;
/** The log */
private static final Logger log = Logger.getLogger(MessageEndpointInterceptor.class);
/** The key for the factory */
public static final String MESSAGE_ENDPOINT_FACTORY = "MessageEndpoint.Factory";
/** The key for the xa resource */
public static final String MESSAGE_ENDPOINT_XARESOURCE = "MessageEndpoint.XAResource";
/** Whether trace is enabled */
private boolean trace = log.isTraceEnabled();
/** Cached version of our proxy string */
private String cachedProxyString = null;
/** Whether this proxy has been released */
protected AtomicBoolean released = new AtomicBoolean(false);
/** Whether we have delivered a message */
protected AtomicBoolean delivered = new AtomicBoolean(false);
/** The in use thread */
protected Thread inUseThread = null;
/** The old classloader of the thread */
protected ClassLoader oldClassLoader = null;
/** Any transaction we started */
protected Transaction transaction = null;
/** Any suspended transaction */
protected Transaction suspended = null;
/** The beforeDeliveryInvoked used to identify sequence of before/after invocation*/
protected boolean beforeDeliveryInvoked = false;
/** The message endpoint factory */
private JBossMessageEndpointFactory endpointFactory;
public MessageEndpointInterceptor()
{
}
public Object invoke(Invocation mi) throws Throwable
{
// Are we still useable?
if (released.get())
throw new IllegalStateException("This message endpoint + " + getProxyString(mi) + " has been released");
// Concurrent invocation?
synchronized (this)
{
Thread currentThread = Thread.currentThread();
if (inUseThread != null && inUseThread.equals(currentThread) == false)
throw new IllegalStateException("This message endpoint + " + getProxyString(mi) + " is already in use by another thread " + inUseThread);
inUseThread = currentThread;
}
String method = mi.getMethod().getName();
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " in use by " + method + " " + inUseThread);
// Which operation?
if (method.equals("release"))
{
release(mi);
return null;
}
else if (method.equals("beforeDelivery"))
{
before(mi);
return null;
}
else if (method.equals("afterDelivery"))
{
after(mi);
return null;
}
else
return delivery(mi);
}
/**
* Release this message endpoint.
*
* @param mi the invocation
* @throws Throwable for any error
*/
protected void release(Invocation mi) throws Throwable
{
// We are now released
released.set(true);
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " released");
// Tidyup any outstanding delivery
if (getOldClassLoader() != null)
{
try
{
finish("release", mi, false);
}
catch (Throwable t)
{
log.warn("Error in release ", t);
}
}
}
/**
* Before delivery processing.
*
* @param mi the invocation
* @throws Throwable for any error
*/
protected void before(Invocation mi) throws Throwable
{
// Called out of sequence
if (getBeforeDeliveryInvoke())
throw new IllegalStateException("Missing afterDelivery from the previous beforeDelivery for message endpoint " + getProxyString(mi));
// Set the classloader
MessageDrivenContainer container = getContainer(mi);
synchronized (this)
{
oldClassLoader = GetTCLAction.getContextClassLoader(inUseThread);
SetTCLAction.setContextClassLoader(inUseThread, container.getClassLoader());
}
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " set context classloader to " + container.getClassLoader());
// start any transaction
try
{
startTransaction("beforeDelivery", mi, container);
setBeforeDeliveryInvoke(true);
}
catch (Throwable t)
{
setBeforeDeliveryInvoke(false);
resetContextClassLoader(mi);
throw new ResourceException(t);
}
}
/**
* After delivery processing.
*
* @param mi the invocation
* @throws Throwable for any error
*/
protected void after(Invocation mi) throws Throwable
{
// Called out of sequence
if(!getBeforeDeliveryInvoke())
{
throw new IllegalStateException("afterDelivery without a previous beforeDelivery for message endpoint " + getProxyString(mi));
}
// Finish this delivery committing if we can
try
{
finish("afterDelivery", mi, true);
}
catch (Throwable t)
{
throw new ResourceException(t);
}
}
/**
* Delivery.
*
* @param mi the invocation
* @return the result of the delivery
* @throws Throwable for any error
*/
protected Object delivery(Invocation mi) throws Throwable
{
// Have we already delivered a message?
if (delivered.get())
throw new IllegalStateException("Multiple message delivery between before and after delivery is not allowed for message endpoint " + getProxyString(mi));
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " delivering");
// Mark delivery if beforeDelivery was invoked
if (getOldClassLoader() != null)
delivered.set(true);
MessageDrivenContainer container = getContainer(mi);
boolean commit = true;
try
{
// Check for starting a transaction
if (getOldClassLoader() == null)
startTransaction("delivery", mi, container);
return getNext().invoke(mi);
}
catch (Throwable t)
{
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " delivery error", t);
if (t instanceof Error || t instanceof RuntimeException)
{
Transaction transaction = getTransaction();
if (transaction != null)
transaction.setRollbackOnly();
commit = false;
}
throw t;
}
finally
{
// No before/after delivery, end any transaction and release the lock
if (getOldClassLoader() == null)
{
try
{
// Finish any transaction we started
endTransaction(mi, commit);
}
finally
{
releaseThreadLock(mi);
}
}
}
}
/**
* Finish the current delivery
*
* @param context the lifecycle method
* @param mi the invocation
* @param commit whether to commit
* @throws Throwable for any error
*/
protected void finish(String context, Invocation mi, boolean commit) throws Throwable
{
try
{
endTransaction(mi, commit);
}
finally
{
setBeforeDeliveryInvoke(false);
// Reset delivered flag
delivered.set(false);
// Change back to the original context classloader
resetContextClassLoader(mi);
// We no longer hold the lock
releaseThreadLock(mi);
}
}
/**
* Start a transaction
*
* @param context the lifecycle method
* @param mi the invocation
* @param container the container
* @throws Throwable for any error
*/
protected void startTransaction(String context, Invocation mi, MessageDrivenContainer container) throws Throwable
{
// Get any passed resource
XAResource resource = (XAResource) mi.getInvocationContext().getValue(MESSAGE_ENDPOINT_XARESOURCE);
Method method = null;
// Normal delivery
if ("delivery".equals(context))
method = mi.getMethod();
// Before delivery
else
method = (Method) mi.getArguments()[0];
// Is the delivery transacted?
boolean isTransacted = getMessageEndpointFactory(mi).isDeliveryTransacted(method);
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " " + context + " method=" + method + " xaResource=" + resource + " transacted=" + isTransacted);
// Get the transaction status
TransactionManager tm = container.getTransactionManager();
Transaction tx = tm.suspend();
synchronized (this)
{
suspended = tx;
}
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " " + context + " currentTx=" + suspended);
// Delivery is transacted
if (isTransacted)
{
// No transaction means we start a new transaction and enlist the resource
if (suspended == null)
{
tm.begin();
tx = tm.getTransaction();
synchronized (this)
{
transaction = tx;
}
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " started transaction=" + transaction);
// Enlist the XAResource in the transaction
if (resource != null)
{
transaction.enlistResource(resource);
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " enlisted=" + resource);
}
}
else
{
// If there is already a transaction we ignore the XAResource (by spec 12.5.9)
try
{
tm.resume(suspended);
}
finally
{
synchronized (this)
{
suspended = null;
}
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " transaction=" + suspended + " already active, IGNORED=" + resource);
}
}
}
}
/**
* End the transaction
*
* @param mi the invocation
* @param commit whether to try to commit
* @throws Throwable for any error
*/
protected void endTransaction(Invocation mi, boolean commit) throws Throwable
{
TransactionManager tm = null;
Transaction currentTx = null;
try
{
// If we started the transaction, commit it
Transaction transaction = getTransaction();
if (transaction != null)
{
tm = getContainer(mi).getTransactionManager();
currentTx = tm.getTransaction();
// Suspend any bad transaction - there is bug somewhere, but we will try to tidy things up
if (currentTx != null && currentTx.equals(transaction) == false)
{
log.warn("Current transaction " + currentTx + " is not the expected transaction.");
tm.suspend();
tm.resume(transaction);
}
else
{
// We have the correct transaction
currentTx = null;
}
// Commit or rollback depending on the status
if (commit == false || transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK)
{
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " rollback");
tm.rollback();
}
else
{
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " commit");
tm.commit();
}
}
// If we suspended the incoming transaction, resume it
Transaction suspended = getSuspended();
if (suspended != null)
{
try
{
tm = getContainer(mi).getTransactionManager();
tm.resume(suspended);
}
finally
{
synchronized (this)
{
this.suspended = null;
}
}
}
}
finally
{
synchronized (this)
{
transaction = null;
}
// Resume any suspended transaction
if (currentTx != null)
{
try
{
tm.resume(currentTx);
}
catch (Throwable t)
{
log.warn("MessageEndpoint " + getProxyString(mi) + " failed to resume old transaction " + currentTx);
}
}
}
}
/**
* Reset the context classloader
*
* @param mi the invocation
*/
protected void resetContextClassLoader(Invocation mi)
{
synchronized (this)
{
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " reset classloader " + oldClassLoader);
SetTCLAction.setContextClassLoader(inUseThread, oldClassLoader);
oldClassLoader = null;
}
}
protected void setBeforeDeliveryInvoke(boolean bdi)
{
this.beforeDeliveryInvoked = bdi;
}
protected boolean getBeforeDeliveryInvoke()
{
return this.beforeDeliveryInvoked;
}
/**
* Release the thread lock
*
* @param mi the invocation
*/
protected void releaseThreadLock(Invocation mi)
{
synchronized (this)
{
if (trace)
log.trace("MessageEndpoint " + getProxyString(mi) + " no longer in use by " + inUseThread);
inUseThread = null;
}
}
/**
* Get our proxy's string value.
*
* @param mi the invocation
* @return the string
*/
protected String getProxyString(Invocation mi)
{
if (cachedProxyString == null)
cachedProxyString = mi.getInvocationContext().getCacheId().toString();
return cachedProxyString;
}
/**
* Get the message endpoint factory
*
* @param mi the invocation
* @return the message endpoint factory
*/
protected JBossMessageEndpointFactory getMessageEndpointFactory(Invocation mi)
{
if (endpointFactory == null)
endpointFactory = (JBossMessageEndpointFactory) mi.getInvocationContext().getValue(MESSAGE_ENDPOINT_FACTORY);
if (endpointFactory == null)
throw new IllegalStateException("No message endpoint factory in " + mi.getInvocationContext().context);
return endpointFactory;
}
/**
* Get the container
*
* @param mi the invocation
* @return the container
*/
protected MessageDrivenContainer getContainer(Invocation mi)
{
JBossMessageEndpointFactory messageEndpointFactory = getMessageEndpointFactory(mi);
MessageDrivenContainer container = messageEndpointFactory.getContainer();
if (container == null)
throw new IllegalStateException("No container associated with message endpoint factory: " + messageEndpointFactory.getServiceName());
return container;
}
protected synchronized ClassLoader getOldClassLoader()
{
return oldClassLoader;
}
protected synchronized Transaction getTransaction()
{
return transaction;
}
protected synchronized Transaction getSuspended()
{
return suspended;
}
}