/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.jms.connection;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import javax.jms.XASession;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import com.caucho.env.thread.ThreadPool;
import com.caucho.jms.message.BytesMessageImpl;
import com.caucho.jms.message.MapMessageImpl;
import com.caucho.jms.message.MessageFactory;
import com.caucho.jms.message.MessageImpl;
import com.caucho.jms.message.ObjectMessageImpl;
import com.caucho.jms.message.StreamMessageImpl;
import com.caucho.jms.message.TextMessageImpl;
import com.caucho.jms.queue.AbstractDestination;
import com.caucho.jms.queue.AbstractQueue;
import com.caucho.jms.queue.AbstractTopic;
import com.caucho.util.Alarm;
import com.caucho.util.Base64;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;
import com.caucho.util.ThreadTask;
/**
* Manages the JMS session.
*/
public class JmsSession implements XASession, ThreadTask, XAResource
{
protected static final Logger log
= Logger.getLogger(JmsSession.class.getName());
protected static final L10N L = new L10N(JmsSession.class);
private static final long SHUTDOWN_WAIT_TIME = 10000;
private boolean _isXA;
private Xid _xid;
private TransactionManager _tm;
private boolean _isTransacted;
private int _acknowledgeMode;
private ClassLoader _classLoader;
private ConnectionImpl _connection;
private final ArrayList<MessageConsumerImpl<Message>> _consumers
= new ArrayList<MessageConsumerImpl<Message>>();
private MessageFactory _messageFactory = new MessageFactory();
private MessageListener _messageListener;
private boolean _isAsynchronous;
// 4.4.1 - client's responsibility
private Thread _thread;
// transacted messages
private ArrayList<TransactedMessage> _transactedMessages;
// true if the listener thread is running
private volatile boolean _isRunning;
private volatile boolean _isClosed;
private volatile boolean _hasMessage;
private String _publisherId;
private Semaphore _listenSemaphore = new Semaphore(1);
public JmsSession(ConnectionImpl connection,
boolean isTransacted, int ackMode,
boolean isXA)
throws JMSException
{
_classLoader = Thread.currentThread().getContextClassLoader();
_connection = connection;
_isXA = isXA;
_isTransacted = isTransacted;
_acknowledgeMode = ackMode;
StringBuilder sb = new StringBuilder();
sb.append("jms:");
Base64.encode(sb, RandomUtil.getRandomLong());
_publisherId = sb.toString();
if (isTransacted)
_acknowledgeMode = 0;
else {
switch (ackMode) {
case CLIENT_ACKNOWLEDGE:
case DUPS_OK_ACKNOWLEDGE:
case AUTO_ACKNOWLEDGE:
_acknowledgeMode = ackMode;
break;
default:
try {
log.warning(L.l("JmsSession {0} is an illegal acknowledge mode",
ackMode));
// XXX: tck
// throw new JMSException(L.l("{0} is an illegal acknowledge mode", ackMode));
log.warning(L.l("JmsSession {0} is an illegal acknowledge mode",
ackMode));
_acknowledgeMode = AUTO_ACKNOWLEDGE;
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
break;
}
}
// ejb/7000
/*
try {
InitialContext ic = new InitialContext();
_tm = (TransactionManager) ic.lookup("java:comp/TransactionManager");
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
}
*/
_connection.addSession(this);
}
/**
* Returns the connection.
*/
ConnectionImpl getConnection()
{
return _connection;
}
/**
* Returns the ClassLoader.
*/
ClassLoader getClassLoader()
{
return _classLoader;
}
/**
* Returns the connection's clientID
*/
public String getClientID()
throws JMSException
{
return _connection.getClientID();
}
public String getPublisherId()
{
return _publisherId;
}
/**
* Returns true if the connection is active.
*/
public boolean isActive()
{
return ! _isClosed && _connection.isActive();
}
/**
* Returns true if the connection is active.
*/
boolean isStopping()
{
return _connection.isStopping();
}
/**
* Returns true if the session is in a transaction.
*/
public boolean getTransacted()
throws JMSException
{
checkOpen();
return _isTransacted;
}
/**
* Returns the acknowledge mode for the session.
*/
public int getAcknowledgeMode()
throws JMSException
{
checkOpen();
return _acknowledgeMode;
}
/**
* Returns the message listener
*/
public MessageListener getMessageListener()
throws JMSException
{
checkOpen();
return _messageListener;
}
/**
* Sets the message listener
*/
public void setMessageListener(MessageListener listener)
throws JMSException
{
checkOpen();
_messageListener = listener;
setAsynchronous();
}
/**
* Set true for a synchronous session.
*/
void setAsynchronous()
{
_isAsynchronous = true;
notifyMessageAvailable();
}
/**
* Set true for a synchronous session.
*/
boolean isAsynchronous()
{
return _isAsynchronous;
}
/**
* Creates a new byte[] message.
*/
public BytesMessage createBytesMessage()
throws JMSException
{
checkOpen();
return new BytesMessageImpl();
}
/**
* Creates a new map message.
*/
public MapMessage createMapMessage()
throws JMSException
{
checkOpen();
return new MapMessageImpl();
}
/**
* Creates a message. Used when only header info is important.
*/
public Message createMessage()
throws JMSException
{
checkOpen();
return new MessageImpl();
}
/**
* Creates an object message.
*/
public ObjectMessage createObjectMessage()
throws JMSException
{
checkOpen();
return new ObjectMessageImpl();
}
/**
* Creates an object message.
*
* @param obj a serializable message.
*/
public ObjectMessage createObjectMessage(Serializable obj)
throws JMSException
{
checkOpen();
ObjectMessage msg = createObjectMessage();
msg.setObject(obj);
return msg;
}
/**
* Creates a stream message.
*/
public StreamMessage createStreamMessage()
throws JMSException
{
checkOpen();
return new StreamMessageImpl();
}
/**
* Creates a text message.
*/
public TextMessage createTextMessage()
throws JMSException
{
checkOpen();
return new TextMessageImpl();
}
/**
* Creates a text message.
*/
public TextMessage createTextMessage(String message)
throws JMSException
{
checkOpen();
TextMessage msg = createTextMessage();
msg.setText(message);
return msg;
}
/**
* Creates a consumer to receive messages.
*
* @param destination the destination to receive messages from.
*/
public MessageConsumer createConsumer(Destination destination)
throws JMSException
{
checkOpen();
return createConsumer(destination, null, false);
}
/**
* Creates a consumer to receive messages.
*
* @param destination the destination to receive messages from.
* @param messageSelector query to restrict the messages.
*/
public MessageConsumer createConsumer(Destination destination,
String messageSelector)
throws JMSException
{
checkOpen();
return createConsumer(destination, messageSelector, false);
}
/**
* Creates a consumer to receive messages.
*
* @param destination the destination to receive messages from.
* @param messageSelector query to restrict the messages.
*/
public MessageConsumer createConsumer(Destination destination,
String messageSelector,
boolean noLocal)
throws JMSException
{
checkOpen();
if (destination == null)
throw new InvalidDestinationException(L.l("destination is null. Destination may not be null for Session.createConsumer"));
if (destination instanceof TemporaryQueueImpl) {
// Consumer can not be created on a different Connection on Temporary Queue.
if (!((TemporaryQueueImpl)destination).getSession().getConnection()
.equals(_connection)) {
throw new javax.jms.IllegalStateException(L.l("temporary queue '{0}' does not belong to this session '{1}'",
destination, this));
}
((TemporaryQueueImpl)destination).addMessageConsumer();
}
if (destination instanceof TemporaryTopicImpl) {
// Consumer can not be created on a different Connection on Temporary Queue.
if (!((TemporaryTopicImpl)destination).getSession().getConnection()
.equals(_connection)) {
throw new javax.jms.IllegalStateException(L.l("temporary queue '{0}' does not belong to this session '{1}'",
destination, this));
}
((TemporaryTopicImpl)destination).addMessageConsumer();
}
MessageConsumerImpl consumer;
if (destination instanceof AbstractQueue) {
AbstractQueue dest = (AbstractQueue) destination;
consumer = new QueueReceiverImpl(this, dest, messageSelector, noLocal);
}
else if (destination instanceof AbstractTopic) {
AbstractTopic dest = (AbstractTopic) destination;
consumer = new TopicSubscriberImpl(this, dest, messageSelector, noLocal);
}
else
throw new InvalidDestinationException(L.l("'{0}' is an unknown destination. The destination must be a Resin JMS Destination.",
destination));
addConsumer(consumer);
if (isActive())
consumer.start();
return consumer;
}
/**
* Creates a producer to produce messages.
*
* @param destination the destination to send messages from.
*/
public MessageProducer createProducer(Destination destination)
throws JMSException
{
checkOpen();
if (destination == null) {
return new MessageProducerImpl(this, null);
}
if (! (destination instanceof AbstractDestination<?>))
throw new InvalidDestinationException(L.l("'{0}' is an unknown destination. The destination must be a Resin JMS destination for Session.createProducer.",
destination));
AbstractDestination<Message> dest
= (AbstractDestination<Message>) destination;
return new MessageProducerImpl(this, dest);
}
/**
* Creates a QueueBrowser to browse messages in the queue.
*
* @param queue the queue to send messages to.
*/
public QueueBrowser createBrowser(Queue queue)
throws JMSException
{
checkOpen();
return createBrowser(queue, null);
}
/**
* Creates a QueueBrowser to browse messages in the queue.
*
* @param queue the queue to send messages to.
*/
public QueueBrowser createBrowser(Queue queue, String messageSelector)
throws JMSException
{
checkOpen();
if (queue == null)
throw new InvalidDestinationException(L.l("queue is null. Queue may not be null for Session.createBrowser"));
if (! (queue instanceof AbstractQueue<?>))
throw new InvalidDestinationException(L.l("'{0}' is an unknown queue. The queue must be a Resin JMS Queue for Session.createBrowser.",
queue));
return new MessageBrowserImpl(this, (AbstractQueue<?>) queue,
messageSelector);
}
/**
* Creates a new queue.
*/
public Queue createQueue(String queueName)
throws JMSException
{
checkOpen();
return _connection.createQueue(queueName);
}
/**
* Creates a temporary queue.
*/
public TemporaryQueue createTemporaryQueue()
throws JMSException
{
checkOpen();
return new TemporaryQueueImpl(this);
}
/**
* Creates a new topic.
*/
public Topic createTopic(String topicName)
throws JMSException
{
checkOpen();
return _connection.createTopic(topicName);
}
/**
* Creates a temporary topic.
*/
public TemporaryTopic createTemporaryTopic()
throws JMSException
{
checkOpen();
return new TemporaryTopicImpl(this);
}
/**
* Creates a durable subscriber to receive messages.
*
* @param topic the topic to receive messages from.
*/
public TopicSubscriber createDurableSubscriber(Topic topic, String name)
throws JMSException
{
checkOpen();
if (getClientID() == null)
throw new JMSException(L.l("connection may not create a durable subscriber because it does not have an assigned ClientID."));
return createDurableSubscriber(topic, name, null, false);
}
/**
* Creates a subscriber to receive messages.
*
* @param topic the topic to receive messages from.
* @param messageSelector topic to restrict the messages.
* @param noLocal if true, don't receive messages we've sent
*/
public TopicSubscriber createDurableSubscriber(Topic topic,
String name,
String messageSelector,
boolean noLocal)
throws JMSException
{
checkOpen();
if (topic == null)
throw new InvalidDestinationException(L.l("destination is null. Destination may not be null for Session.createDurableSubscriber"));
if (! (topic instanceof AbstractTopic<?>))
throw new InvalidDestinationException(L.l("'{0}' is an unknown destination. The destination must be a Resin JMS Destination.",
topic));
AbstractTopic<?> topicImpl = (AbstractTopic<?>) topic;
if (_connection.getDurableSubscriber(name) != null) {
// jms/2130
// unsubscribe(name);
/*
throw new JMSException(L.l("'{0}' is already an active durable subscriber",
name));
*/
}
AbstractQueue<?> queue = topicImpl.createSubscriber(getPublisherId(), name, noLocal);
TopicSubscriberImpl consumer;
consumer = new TopicSubscriberImpl(this, topicImpl, queue,
messageSelector, noLocal);
_connection.putDurableSubscriber(name, consumer);
addConsumer(consumer);
return consumer;
}
/**
* Unsubscribe from a durable subscription.
*/
public void unsubscribe(String name)
throws JMSException
{
checkOpen();
if (name == null)
throw new InvalidDestinationException(L.l("destination is null. Destination may not be null for Session.unsubscribe"));
TopicSubscriber subscriber = _connection.removeDurableSubscriber(name);
if (subscriber == null)
throw new InvalidDestinationException(L.l("'{0}' is an unknown subscriber for Session.unsubscribe",
name));
subscriber.close();
}
/**
* Starts the session.
*/
void start()
{
if (log.isLoggable(Level.FINE))
log.fine(toString() + " active");
synchronized (_consumers) {
for (MessageConsumerImpl<Message> consumer : _consumers) {
consumer.start();
}
}
notifyMessageAvailable();
}
/**
* Stops the session.
*/
void stop()
{
if (log.isLoggable(Level.FINE))
log.fine(toString() + " stopping");
synchronized (_consumers) {
long timeout = Alarm.getCurrentTime() + SHUTDOWN_WAIT_TIME;
while (_isRunning && Alarm.getCurrentTime() < timeout) {
try {
_consumers.wait(SHUTDOWN_WAIT_TIME);
if (Alarm.isTest()) {
return;
}
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
}
}
ArrayList<MessageConsumerImpl<Message>> consumers
= new ArrayList<MessageConsumerImpl<Message>>(_consumers);
for (MessageConsumerImpl<Message> consumer : consumers) {
try {
// XXX: should be stop()?
consumer.stop();
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
}
}
}
}
/**
* Commits the messages.
*/
@Override
public void commit()
throws JMSException
{
checkOpen();
commit(false);
}
/**
* Commits the messages.
*/
private void commit(boolean isXA)
throws JMSException
{
_xid = null;
// jms/2552
if (! _isTransacted && ! isXA)
throw new IllegalStateException(L.l("commit() can only be called on a transacted session."));
_isXA = false;
ArrayList<TransactedMessage> messages = _transactedMessages;
if (messages == null || messages.size() == 0) {
return;
}
try {
for (int i = 0; i < messages.size(); i++) {
TransactedMessage msg = messages.get(i);
if (msg != null)
msg.commit();
}
} finally {
messages.clear();
}
if (! isXA)
acknowledge();
}
/**
* Acknowledge received
*/
public void acknowledge()
throws JMSException
{
checkOpen();
if (_transactedMessages != null) {
for (int i = _transactedMessages.size() - 1; i >= 0; i--) {
TransactedMessage msg = _transactedMessages.get(i);
if (msg instanceof ReceiveMessage) {
_transactedMessages.remove(i);
msg.commit();
}
}
}
}
/**
* Recovers the messages.
*/
@Override
public void recover()
throws JMSException
{
checkOpen();
if (_isTransacted)
throw new IllegalStateException(L.l("recover() may not be called on a transacted session."));
if (_transactedMessages != null) {
for (int i = _transactedMessages.size() - 1; i >= 0; i--) {
TransactedMessage msg = _transactedMessages.get(i);
if (msg instanceof ReceiveMessage) {
_transactedMessages.remove(i);
msg.rollback();
}
}
}
}
/**
* Rollsback the messages.
*/
@Override
public void rollback()
throws JMSException
{
checkOpen();
rollbackImpl();
}
/**
* Rollsback the messages.
*/
public void rollbackImpl()
throws JMSException
{
if (! _isTransacted && ! _isXA)
throw new IllegalStateException(L.l("rollback() can only be called on a transacted session."));
if (_transactedMessages != null) {
for (int i = 0; i < _transactedMessages.size(); i++) {
_transactedMessages.get(i).rollback();
}
_transactedMessages.clear();
}
}
/**
* Closes the session
*/
@Override
public void close()
throws JMSException
{
if (_isClosed)
return;
try {
stop();
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
ArrayList<TransactedMessage> messages = _transactedMessages;
if (messages != null && _xid == null) {
_transactedMessages = null;
try {
for (int i = 0; i < messages.size(); i++) {
messages.get(i).close();
}
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
}
for (int i = 0; i < _consumers.size(); i++) {
MessageConsumerImpl consumer = _consumers.get(i);
try {
consumer.close();
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
}
try {
_connection.removeSession(this);
} finally {
_isClosed = true;
}
_classLoader = null;
}
protected void addConsumer(MessageConsumerImpl consumer)
{
_consumers.add(consumer);
notifyMessageAvailable();
}
protected void removeConsumer(MessageConsumerImpl consumer)
{
if (_consumers != null)
_consumers.remove(consumer);
}
/**
* Notifies the receiver.
*/
boolean notifyMessageAvailable()
{
synchronized (_consumers) {
_hasMessage = true;
if (_isRunning || ! _isAsynchronous || ! isActive())
return false;
_isRunning = true;
}
ThreadPool.getThreadPool().schedule(this);
if (Alarm.isTest()) {
// the yield is only needed for the regressions
try {
Thread.sleep(10);
} catch (Exception e) {
}
}
return true;
}
/**
* Adds a message to the session message queue.
*/
public void send(AbstractDestination queue,
Message appMessage,
int deliveryMode,
int priority,
long timeout)
throws JMSException
{
checkOpen();
if (queue == null)
throw new UnsupportedOperationException(L.l("empty queue is not allowed for this session."));
if (appMessage.getJMSDestination() == null)
appMessage.setJMSDestination(queue);
// <P>When a message is sent, the <CODE>JMSExpiration</CODE> header field
// is left unassigned. After completion of the <CODE>send</CODE> or
// <CODE>publish</CODE> method, it holds the expiration time of the message.
// This is the sum of the time-to-live value specified by the client
// and the GMT at the time of the <CODE>send</CODE> or <CODE>publish</CODE>.
// <P>If the time-to-live is specified as zero,
// <CODE>JMSExpiration</CODE> is set to zero to indicate that the message
// does not expire. <P>When a message's expiration time is reached, a
// provider should discard it. The JMS API does not define any form
// of notification of message expiration. <P>Clients should not receive
// messages that have expired; however, the JMS API does not
// guarantee that this will not happen.
long now = Alarm.getExactTime();
long expireTime = 0;
if (timeout == 0) {
expireTime = now + MessageProducerImpl.DEFAULT_TIME_TO_LIVE;
} else {
expireTime = now + timeout;
appMessage.setJMSExpiration(expireTime);
}
appMessage.setJMSMessageID(queue.generateMessageID());
appMessage.setJMSPriority(priority);
appMessage.setJMSTimestamp(now);
appMessage.setJMSDeliveryMode(deliveryMode);
MessageImpl message = _messageFactory.copy(appMessage);
// ejb/0970
boolean isXA = false;
try {
if (_isTransacted && _tm != null && _tm.getTransaction() != null)
isXA = true;
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
if (_isTransacted || isXA) {
if (_transactedMessages == null)
_transactedMessages = new ArrayList<TransactedMessage>();
TransactedMessage transMsg = new SendMessage(queue,
message,
expireTime);
_transactedMessages.add(transMsg);
if (_xid == null)
enlist();
}
else {
if (log.isLoggable(Level.FINE))
log.fine(queue + " sending " + message);
queue.send(message.getJMSMessageID(),
message,
priority,
expireTime,
getPublisherId());
}
}
private void enlist()
{
if (_tm != null) {
try {
Transaction trans = _tm.getTransaction();
if (trans != null)
trans.enlistResource(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private void delist()
{
if (_tm != null) {
try {
Transaction trans = _tm.getTransaction();
if (trans != null)
trans.delistResource(this, 0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Adds a message to the session message queue.
*/
void addTransactedReceive(AbstractDestination queue,
MessageImpl message)
{
message.setSession(this);
if (_transactedMessages == null)
_transactedMessages = new ArrayList<TransactedMessage>();
TransactedMessage transMsg = new ReceiveMessage(queue, message);
_transactedMessages.add(transMsg);
if (_tm != null && _transactedMessages.size() == 1) {
enlist();
}
}
//
// XA
//
public Session getSession()
{
return this;
}
public XAResource getXAResource()
{
return this;
}
/**
* Returns true if the specified resource has the same RM.
*/
public boolean isSameRM(XAResource xa)
throws XAException
{
return this == xa;
}
/**
* Sets the transaction timeout in seconds.
*/
public boolean setTransactionTimeout(int timeout)
throws XAException
{
return true;
}
/**
* Gets the transaction timeout in seconds.
*/
public int getTransactionTimeout()
throws XAException
{
return 0;
}
/**
* Called when the resource is associated with a transaction.
*/
public void start(Xid xid, int flags)
throws XAException
{
_xid = xid;
}
/**
* Called when the resource is is done with a transaction.
*/
public void end(Xid xid, int flags)
throws XAException
{
_xid = null;
}
/**
* Called to start the first phase of the commit.
*/
public int prepare(Xid xid)
throws XAException
{
return 0;
}
/**
* Called to commit.
*/
public void commit(Xid xid, boolean onePhase)
throws XAException
{
try {
commit(true);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
delist();
_isXA = false;
}
}
/**
* Called to roll back.
*/
@Override
public void rollback(Xid xid)
throws XAException
{
try {
rollbackImpl();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
delist();
_isXA = false;
}
}
/**
* Called to forget an Xid that had a heuristic commit.
*/
public void forget(Xid xid)
throws XAException
{
}
/**
* Called to find Xid's that need recovery.
*/
public Xid[] recover(int flag)
throws XAException
{
return null;
}
public void acquireListenSemaphore()
{
try {
_listenSemaphore.acquire();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void releaseListenSemaphore()
{
try {
_listenSemaphore.release();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Called to synchronously receive messages
*/
public void run()
{
Thread.currentThread().setContextClassLoader(_classLoader);
boolean isValid = true;
while (isValid) {
isValid = false;
_hasMessage = false;
try {
for (int i = 0; i < _consumers.size(); i++) {
MessageConsumerImpl consumer = _consumers.get(i);
while (isActive() && consumer.handleMessage(_messageListener)) {
}
}
isValid = isActive();
} finally {
synchronized (_consumers) {
if (! isValid)
_isRunning = false;
else if (! _hasMessage) {
_isRunning = false;
isValid = false;
}
// notification, e.g. for shutdown
_consumers.notifyAll();
}
}
}
}
public boolean isClosed()
{
return _isClosed;
}
/**
* Checks that the session is open.
*/
public void checkOpen()
throws javax.jms.IllegalStateException
{
if (_isClosed)
throw new javax.jms.IllegalStateException(L.l("session is closed"));
}
/**
* Verifies that multiple threads aren't using the session.
*
* 4.4.1 the client takes the responsibility. There's no
* validation check.
*/
void checkThread()
throws JMSException
{
Thread thread = _thread;
if (thread != Thread.currentThread() && thread != null) {
Exception e = new IllegalStateException(L.l("Can't use session from concurrent threads."));
log.log(Level.WARNING, e.toString(), e);
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[]";
}
abstract class TransactedMessage {
abstract void commit()
throws JMSException;
abstract void rollback()
throws JMSException;
abstract void close()
throws JMSException;
}
class SendMessage extends TransactedMessage {
private final AbstractDestination<Message> _queue;
private final MessageImpl _message;
private final long _expires;
SendMessage(AbstractDestination<Message> queue,
MessageImpl message,
long expires)
{
_queue = queue;
_message = message;
_expires = expires;
}
void commit()
throws JMSException
{
_queue.send(_message.getJMSMessageID(),
_message,
_message.getJMSPriority(),
_expires,
getPublisherId());
}
void rollback()
throws JMSException
{
}
void close()
throws JMSException
{
commit();
}
}
class ReceiveMessage extends TransactedMessage {
private final AbstractDestination<Message> _queue;
private final MessageImpl _message;
ReceiveMessage(AbstractDestination<Message> queue, MessageImpl message)
{
_queue = queue;
_message = message;
if (queue == null)
throw new NullPointerException();
if (_message == null || message.getJMSMessageID() == null)
throw new NullPointerException();
}
@Override
void commit()
throws JMSException
{
_queue.acknowledge(_message.getJMSMessageID());
}
@Override
void rollback()
throws JMSException
{
// ejb/700b
if (_message.getJMSRedelivered()) {
log.warning(this + " removing rollbacked message " + _message);
_queue.acknowledge(_message.getJMSMessageID());
}
else {
_queue.rollback(_message.getJMSMessageID());
_message.setJMSRedelivered(true);
}
}
void close()
throws JMSException
{
rollback();
}
}
}