/*
* 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.resource.adapter.jms.inflow;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ServerSession;
import javax.jms.Session;
import javax.jms.XAConnection;
import javax.jms.XASession;
import javax.resource.spi.endpoint.MessageEndpoint;
import javax.resource.spi.endpoint.MessageEndpointFactory;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkEvent;
import javax.resource.spi.work.WorkException;
import javax.resource.spi.work.WorkListener;
import javax.resource.spi.work.WorkManager;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;
import org.jboss.logging.Logger;
/**
* A generic jms session pool.
*
* @author <a href="adrian@jboss.com">Adrian Brock</a>
* @author <a href="mailto:weston.price@jboss.com>Weston Price</a>
* @version $Revision: 91228 $
*/
public class JmsServerSession implements ServerSession, MessageListener, Work,
WorkListener
{
/** The log */
private static final Logger log = Logger.getLogger(JmsServerSession.class);
/** The session pool */
JmsServerSessionPool pool;
/** The transacted flag */
boolean transacted;
/** The acknowledge mode */
int acknowledge;
/** The session */
Session session;
/** Any XA session */
XASession xaSession;
/** The endpoint */
MessageEndpoint endpoint;
/** Any DLQ handler */
DLQHandler dlqHandler;
TransactionDemarcationStrategy txnStrategy;
/**
* Create a new JmsServerSession
*
* @param pool
* the server session pool
*/
public JmsServerSession(JmsServerSessionPool pool)
{
this.pool = pool;
}
/**
* Setup the session
*/
public void setup() throws Exception
{
JmsActivation activation = pool.getActivation();
JmsActivationSpec spec = activation.getActivationSpec();
dlqHandler = activation.getDLQHandler();
Connection connection = activation.getConnection();
// Create the session
if (connection instanceof XAConnection
&& activation.isDeliveryTransacted())
{
xaSession = ((XAConnection) connection).createXASession();
session = xaSession.getSession();
} else
{
transacted = spec.isSessionTransacted();
acknowledge = spec.getAcknowledgeModeInt();
session = connection.createSession(transacted, acknowledge);
}
// Get the endpoint
MessageEndpointFactory endpointFactory = activation
.getMessageEndpointFactory();
XAResource xaResource = null;
if (activation.isDeliveryTransacted() && xaSession != null)
xaResource = xaSession.getXAResource();
endpoint = endpointFactory.createEndpoint(xaResource);
// Set the message listener
session.setMessageListener(this);
}
/**
* Stop the session
*/
public void teardown()
{
try
{
if (endpoint != null)
endpoint.release();
} catch (Throwable t)
{
log.debug("Error releasing endpoint " + endpoint, t);
}
try
{
if (xaSession != null)
xaSession.close();
} catch (Throwable t)
{
log.debug("Error releasing xaSession " + xaSession, t);
}
try
{
if (session != null)
session.close();
} catch (Throwable t)
{
log.debug("Error releasing session " + session, t);
}
}
public void onMessage(Message message)
{
try
{
if (!(txnStrategy != null && txnStrategy instanceof TraditionalXATransactionDemarcationStrategy))
endpoint.beforeDelivery(JmsActivation.ONMESSAGE);
try
{
if (dlqHandler == null
|| dlqHandler.handleRedeliveredMessage(message) == false)
{
MessageListener listener = (MessageListener) endpoint;
listener.onMessage(message);
}
} finally
{
if (!(txnStrategy != null && txnStrategy instanceof TraditionalXATransactionDemarcationStrategy))
endpoint.afterDelivery();
if (dlqHandler != null)
dlqHandler.messageDelivered(message);
}
}
catch (Throwable t)
{
log.error("Unexpected error delivering message " + message, t);
if (txnStrategy != null)
txnStrategy.error();
}
}
public Session getSession() throws JMSException
{
return session;
}
public void start() throws JMSException
{
JmsActivation activation = pool.getActivation();
WorkManager workManager = activation.getWorkManager();
try
{
workManager.scheduleWork(this, 0, null, this);
} catch (WorkException e)
{
log.error("Unable to schedule work", e);
throw new JMSException("Unable to schedule work: " + e.toString());
}
}
public void run()
{
try
{
txnStrategy = createTransactionDemarcation();
} catch (Throwable t)
{
log.error("Error creating transaction demarcation. Cannot continue.");
return;
}
try
{
session.run();
} catch (Throwable t)
{
if (txnStrategy != null)
txnStrategy.error();
} finally
{
if (txnStrategy != null)
txnStrategy.end();
txnStrategy = null;
}
}
private TransactionDemarcationStrategy createTransactionDemarcation()
{
return new DemarcationStrategyFactory().getStrategy();
}
public void release()
{
}
public void workAccepted(WorkEvent e)
{
}
public void workCompleted(WorkEvent e)
{
pool.returnServerSession(this);
}
public void workRejected(WorkEvent e)
{
pool.returnServerSession(this);
}
public void workStarted(WorkEvent e)
{
}
private class DemarcationStrategyFactory
{
TransactionDemarcationStrategy getStrategy()
{
TransactionDemarcationStrategy current = null;
final JmsActivationSpec spec = pool.getActivation().getActivationSpec();
final JmsActivation activation = pool.getActivation();
try
{
//If we have a transacted delivery
if (activation.isDeliveryTransacted())
{
//if we have an XASession
if (xaSession != null)
{
if (spec.isForceTransacted())
{
current = new XATransactionDemarcationStrategy();
}
else
{
current = new TraditionalXATransactionDemarcationStrategy();
}
}
else //if we don't have an XASession, simulate it with a transacted session
{
current = new SimulatedXATransactionDemarcationStrategy();
}
}
else
{
current = new LocalDemarcationStrategy();
}
}
catch (Throwable t)
{
log.error(this + " error creating transaction demarcation ", t);
}
if (current != null && log.isTraceEnabled())
log.trace("Using strategy: " + current.getClass().getName());
return current;
}
}
private interface TransactionDemarcationStrategy
{
void error();
void end();
}
/**
* This class simulates xa using a transacted session for connection factories that don't have an xa interface. This is true with
* default ibmmq adapter. It is not XA, but still needs to be able to have transactions. So for these connection factories we
* use transacted sessions to commit and rollback, while monitoring the transaction.
*
* JBAS-6343 - This class is exactly like the XADemarcationStrategy, but it uses a transacted session under the covers.
* Unfortuneately we have to start a transaction the for local, because we need to be able to get a handle to any failed
* transactions.
* @author jhowell
*
*/
private class SimulatedXATransactionDemarcationStrategy implements
TransactionDemarcationStrategy
{
boolean trace = log.isTraceEnabled();
Transaction trans = null;
TransactionManager tm = pool.getActivation().getTransactionManager();;
public SimulatedXATransactionDemarcationStrategy() throws Throwable
{
final int timeout = pool.getActivation().getActivationSpec()
.getTransactionTimeout();
if (timeout > 0)
{
log.trace("Setting transactionTimeout for JMSSessionPool to "
+ timeout);
tm.setTransactionTimeout(timeout);
}
tm.begin();
try
{
trans = tm.getTransaction();
if (trace)
log.trace(JmsServerSession.this + " using tx=" + trans);
} catch (Throwable t)
{
try
{
tm.rollback();
} catch (Throwable ignored)
{
log.trace(JmsServerSession.this+ " ignored error rolling back after failing to get transaction", ignored);
}
throw t;
}
}
public void error()
{
// Mark for tollback TX via TM
try
{
if (trace)
log.trace(JmsServerSession.this+ " using TM to mark TX for rollback tx=" + trans);
trans.setRollbackOnly();
} catch (Throwable t)
{
log.error(JmsServerSession.this + " failed to set rollback only", t);
}
//even if the rollback fails on the transaction, we want to rollback the session.
try
{
session.rollback();
} catch (JMSException e)
{
log.error(JmsServerSession.this + " failed to rollback the transacted session", e);
}
}
public void end()
{
try
{
// Use the TM to commit the Tx (assert the correct association)
Transaction currentTx = tm.getTransaction();
if (trans.equals(currentTx) == false)
throw new IllegalStateException(
"Wrong tx association: expected " + trans + " was "
+ currentTx);
// Marked rollback
if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK)
{
if (trace)
log.trace(JmsServerSession.this
+ " rolling back JMS transaction tx=" + trans);
// actually roll it back
tm.rollback();
session.rollback();
}
else if (trans.getStatus() == Status.STATUS_ACTIVE)
{
// Commit tx
// This will happen if
// a) everything goes well
// b) app. exception was thrown
if (trace)
log.trace(JmsServerSession.this + " commiting the JMS transaction tx=" + trans);
tm.commit();
session.commit();
} else
{
tm.suspend();
session.rollback();
}
} catch (Throwable t)
{
log.error(JmsServerSession.this + " failed to commit/rollback", t);
//if anything goes wrong with the transaction, we need to rollback the session.
try
{
session.rollback();
} catch (JMSException e)
{
log.error(JmsServerSession.this + " failed to rollback transacted session after transaction failure", t);
}
}
}
}
/**
* LocalDemaracationStrategy is for anything where the delivery is not marked as transacted.
* In CMT the delivery is always marked as transacted. BMT does not mark the delivery as transacted, but
* it does mark the session as transacted. BMT uses this class with Transacted sessions in order to rollback or commit the
* message.
* @author jhowell
*
*/
private class LocalDemarcationStrategy implements
TransactionDemarcationStrategy
{
public void end()
{
final JmsActivationSpec spec = pool.getActivation()
.getActivationSpec();
if (spec.isSessionTransacted())
{
if (session != null)
{
try
{
session.commit();
} catch (JMSException e)
{
log.error("Failed to commit session transaction", e);
}
}
}
}
public void error()
{
final JmsActivationSpec spec = pool.getActivation()
.getActivationSpec();
if (spec.isSessionTransacted())
{
if (session != null)
try
{
/*
* Looks strange, but this basically means
*
* If the underlying connection was non-XA and the
* transaction attribute is REQUIRED we rollback. Also,
* if the underlying connection was non-XA and the
* transaction attribute is NOT_SUPPORT and the non
* standard redelivery behavior is enabled we rollback
* to force redelivery.
*/
if (pool.getActivation().isDeliveryTransacted()
|| spec.getRedeliverUnspecified())
{
session.rollback();
}
} catch (JMSException e)
{
log.error("Failed to rollback session transaction", e);
}
}
}
}
/**
* This class is used for XATransactions(ie. CMT) for the mdb message delivery. It creates a transaction for the message delivery,
* enlists the XASession object in the transaction and then after the on message is called, it will commit/rollback the transaction.
* @author jhowell
*
*/
private class XATransactionDemarcationStrategy implements
TransactionDemarcationStrategy
{
boolean trace = log.isTraceEnabled();
Transaction trans = null;
TransactionManager tm = pool.getActivation().getTransactionManager();;
public XATransactionDemarcationStrategy() throws Throwable
{
final int timeout = pool.getActivation().getActivationSpec()
.getTransactionTimeout();
if (timeout > 0)
{
log.trace("Setting transactionTimeout for JMSSessionPool to "
+ timeout);
tm.setTransactionTimeout(timeout);
}
tm.begin();
try
{
trans = tm.getTransaction();
if (trace)
log.trace(JmsServerSession.this + " using tx=" + trans);
if (xaSession != null)
{
XAResource res = xaSession.getXAResource();
if (!trans.enlistResource(res))
{
throw new JMSException("could not enlist resource");
}
if (trace)
log.trace(JmsServerSession.this + " XAResource '" + res
+ "' enlisted.");
}
} catch (Throwable t)
{
try
{
tm.rollback();
} catch (Throwable ignored)
{
log
.trace(
JmsServerSession.this
+ " ignored error rolling back after failed enlist",
ignored);
}
throw t;
}
}
public void error()
{
// Mark for tollback TX via TM
try
{
if (trace)
log.trace(JmsServerSession.this
+ " using TM to mark TX for rollback tx=" + trans);
trans.setRollbackOnly();
} catch (Throwable t)
{
log.error(JmsServerSession.this
+ " failed to set rollback only", t);
}
}
public void end()
{
try
{
// Use the TM to commit the Tx (assert the correct association)
Transaction currentTx = tm.getTransaction();
if (trans.equals(currentTx) == false)
throw new IllegalStateException(
"Wrong tx association: expected " + trans + " was "
+ currentTx);
// Marked rollback
if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK)
{
if (trace)
log.trace(JmsServerSession.this
+ " rolling back JMS transaction tx=" + trans);
// actually roll it back
tm.rollback();
// NO XASession? then manually rollback.
// This is not so good but
// it's the best we can do if we have no XASession.
if (xaSession == null
&& pool.getActivation().isDeliveryTransacted())
{
session.rollback();
}
}
else if (trans.getStatus() == Status.STATUS_ACTIVE)
{
// Commit tx
// This will happen if
// a) everything goes well
// b) app. exception was thrown
if (trace)
log.trace(JmsServerSession.this
+ " commiting the JMS transaction tx=" + trans);
tm.commit();
// NO XASession? then manually commit. This is not so good
// but
// it's the best we can do if we have no XASession.
if (xaSession == null
&& pool.getActivation().isDeliveryTransacted())
{
session.commit();
}
} else
{
tm.suspend();
if (xaSession == null
&& pool.getActivation().isDeliveryTransacted())
{
session.rollback();
}
}
} catch (Throwable t)
{
log.error(JmsServerSession.this + " failed to commit/rollback",
t);
}
}
}
/**
* This class is used for traditional XATransaction interaction as described in JCA 1.5 12.5.6
*/
private class TraditionalXATransactionDemarcationStrategy implements TransactionDemarcationStrategy
{
boolean trace = log.isTraceEnabled();
TransactionManager tm = pool.getActivation().getTransactionManager();;
public TraditionalXATransactionDemarcationStrategy() throws Throwable
{
final int timeout = pool.getActivation().getActivationSpec().getTransactionTimeout();
if (timeout > 0)
{
log.trace("Setting transactionTimeout for JMSSessionPool to " + timeout);
tm.setTransactionTimeout(timeout);
}
}
public void error()
{
}
public void end()
{
}
}
}