/*
* 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.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import com.caucho.jms.message.MessageImpl;
import com.caucho.jms.message.ObjectMessageImpl;
import com.caucho.jms.message.TextMessageImpl;
import com.caucho.jms.queue.AbstractDestination;
import com.caucho.jms.queue.AbstractQueue;
import com.caucho.jms.queue.MessageCallback;
import com.caucho.jms.queue.MessageException;
import com.caucho.jms.queue.QueueEntry;
import com.caucho.jms.selector.Selector;
import com.caucho.jms.selector.SelectorParser;
import com.caucho.util.Alarm;
import com.caucho.util.L10N;
/**
* A basic message consumer.
*/
public class MessageConsumerImpl<E> implements MessageConsumer
{
private static final Logger log
= Logger.getLogger(MessageConsumerImpl.class.getName());
private static final L10N L = new L10N(MessageConsumerImpl.class);
private final Object _consumerLock = new Object();
protected final JmsSession _session;
private AbstractQueue<E> _queue;
private MessageListener _messageListener;
private ClassLoader _listenerClassLoader;
private MessageConsumerCallback _messageCallback;
private String _messageSelector;
protected Selector _selector;
private boolean _noLocal;
private boolean _isAutoAcknowledge;
private AtomicBoolean _isActive = new AtomicBoolean();
private AtomicBoolean _isClosed = new AtomicBoolean();
MessageConsumerImpl(JmsSession session,
AbstractQueue<E> queue,
String messageSelector,
boolean noLocal)
throws JMSException
{
_session = session;
_queue = queue;
_messageSelector = messageSelector;
if (_messageSelector != null) {
SelectorParser parser = new SelectorParser();
_selector = parser.parse(messageSelector);
}
_noLocal = noLocal;
// _queue.addMessageAvailableListener(this);
switch (_session.getAcknowledgeMode()) {
case Session.AUTO_ACKNOWLEDGE:
case Session.DUPS_OK_ACKNOWLEDGE:
_isAutoAcknowledge = true;
break;
default:
_isAutoAcknowledge = false;
break;
}
}
/**
* Returns the destination
*/
protected AbstractDestination<E> getDestination()
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("getDestination(): MessageConsumer is closed."));
return _queue;
}
/**
* Returns true if local messages are not sent.
*/
public boolean getNoLocal()
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("getNoLocal(): MessageConsumer is closed."));
return _noLocal;
}
/**
* Returns the message listener
*/
public MessageListener getMessageListener()
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("getNoLocal(): MessageConsumer is closed."));
return _messageListener;
}
/**
* Sets the message listener
*/
@Override
public void setMessageListener(MessageListener listener)
throws JMSException
{
setMessageListener(listener, -1);
}
/**
* Sets the message listener with a poll interval
*/
public void setMessageListener(MessageListener listener, long pollInterval)
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("setMessageListener(): MessageConsumer is closed."));
_messageListener = listener;
_messageCallback = new MessageConsumerCallback(listener);
_listenerClassLoader = Thread.currentThread().getContextClassLoader();
// if Consumer has already been started then register the message Call back.
if (isActive()) {
addMessageCallback();
}
}
/**
* Returns the message consumer's selector.
*/
public String getMessageSelector()
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("getMessageSelector(): MessageConsumer is closed."));
return _messageSelector;
}
/**
* Returns the parsed selector.
*/
public Selector getSelector()
{
return _selector;
}
/**
* Returns true if active
*/
public boolean isActive()
throws JMSException
{
if (_isClosed.get() || _session.isClosed())
throw new javax.jms.IllegalStateException(L.l("isActive(): MessageConsumer is closed."));
return _session.isActive() && ! _isClosed.get();
}
/**
* Returns true if closed
*/
public boolean isClosed()
{
return _isClosed.get() || _session.isClosed();
}
/**
* Receives the next message, blocking until a message is available.
*/
@Override
public Message receive()
throws JMSException
{
long timeout = Long.MAX_VALUE / 2;
if (Alarm.isTest())
timeout = 600000L;
return receiveImpl(timeout);
}
/**
* Receives a message from the queue.
*/
@Override
public Message receiveNoWait()
throws JMSException
{
return receiveImpl(0);
}
/**
* Receives a message from the queue.
*/
@Override
public Message receive(long timeout)
throws JMSException
{
Message msg = receiveImpl(timeout);
if (msg != null && log.isLoggable(Level.FINE))
log.fine(_queue + " receive message " + msg);
return msg;
}
/**
* Receives a message from the queue.
*/
protected Message receiveImpl(long timeout)
throws JMSException
{
if (isClosed())
throw new javax.jms.IllegalStateException(L.l("receiveNoWait(): MessageConsumer is closed."));
if (Long.MAX_VALUE / 2 < timeout || timeout < 0)
timeout = Long.MAX_VALUE / 2;
long now = Alarm.getCurrentTimeActual();
long expireTime = timeout > 0 ? now + timeout : 0;
while (_session.isActive()) {
QueueEntry<E> entry
= _queue.receiveEntry(expireTime, _isAutoAcknowledge, _selector);
if (entry == null)
return null;
E payload = entry.getPayload();
if (payload == null)
return null;
MessageImpl msg = null;
if (payload instanceof MessageImpl) {
msg = (MessageImpl) payload;
}
else if (payload instanceof String) {
msg = new TextMessageImpl((String) payload);
msg.setJMSMessageID(entry.getMsgId());
}
else {
msg = new ObjectMessageImpl((Serializable) payload);
msg.setJMSMessageID(entry.getMsgId());
}
msg.setReceive();
/*if (_selector != null && ! _selector.isMatch(msg)) {
_queue.acknowledge(msg.getJMSMessageID());
continue;
}*/
//else {
if (log.isLoggable(Level.FINE))
log.fine(_queue + " receiving message " + msg);
if (! _isAutoAcknowledge)
_session.addTransactedReceive(_queue, msg);
return msg;
//}
}
return null;
}
/**
* Notifies that a message is available.
*/
public boolean notifyMessageAvailable()
{
synchronized (_consumerLock) {
_consumerLock.notifyAll();
}
return _session.notifyMessageAvailable();
}
/**
* Called with the session's thread to handle any messages
*/
boolean handleMessage(MessageListener listener)
{
if (_messageListener != null)
listener = _messageListener;
if (listener == null)
return false;
MessageImpl msg = null;
try {
// MessageCallback<E> callback = _messageCallback;
// XXX: not correct with new model
// _queue.listen(callback);
/*
if (msg == null)
System.out.println(_queue + " NOMESSAGE:");
*/
if (msg != null) {
if (log.isLoggable(Level.FINE)) {
log.fine(_queue + " deliver " + msg + " to listener " + listener);
}
msg.setSession(_session);
// XXX: ejb30/bb/mdb/activationconfig/queue/selectorauto/annotated/negativeTest1
if (_selector == null || _selector.isMatch(msg)) {
_session.addTransactedReceive(_queue, msg);
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_listenerClassLoader);
listener.onMessage(msg);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
if (_session.getTransacted())
_session.commit();
else
msg.acknowledge();
return true;
}
} catch (Exception e) {
log.log(Level.WARNING, L.l("{0}: message listener '{1}' failed for message '{2}' with exception\n{3}",
this, listener, msg, e.toString()),
e);
_queue.addListenerException(e);
}
return false;
}
public void addMessageCallback()
{
MessageConsumerCallback callback = _messageCallback;
if (callback != null) {
boolean isAutoAcknowledge = _isAutoAcknowledge;
_queue.addMessageCallback(callback, isAutoAcknowledge);
}
}
/**
* Starts the consumer
*/
public void start()
{
_isActive.set(true);
addMessageCallback();
}
/**
* Stops the consumer.
*/
public void stop()
throws JMSException
{
_isActive.set(false);
_queue.removeMessageCallback(_messageCallback);
/*
synchronized (_consumerLock) {
_consumerLock.notifyAll();
}
*/
}
/**
* Closes the consumer.
*/
@Override
public void close()
throws JMSException
{
if (_isClosed.getAndSet(true))
return;
if (_queue instanceof TemporaryQueueImpl) {
((TemporaryQueueImpl)_queue).removeMessageConsumer();
}
// _queue.removeMessageAvailableListener(this);
_session.removeConsumer(this);
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _queue + "]";
}
class MessageConsumerCallback implements MessageCallback<E> {
private final MessageListener _listener;
private final ClassLoader _classLoader;
MessageConsumerCallback(MessageListener listener)
{
_listener = listener;
_classLoader = Thread.currentThread().getContextClassLoader();
}
public boolean isClosed()
{
return MessageConsumerImpl.this.isClosed() || ! _isActive.get();
}
@Override
public void messageReceived(String msgId, E payload)
{
MessageImpl message = null;
try {
if (payload instanceof MessageImpl)
message = (MessageImpl) payload;
else if (payload instanceof String) {
message = new TextMessageImpl((String) payload);
message.setJMSMessageID(msgId);
}
else {
message = new ObjectMessageImpl((Serializable) payload);
message.setJMSMessageID(msgId);
}
if (_selector == null || _selector.isMatch(message)) {
// XXX: only if XA
//if (! _isAutoAcknowledge) {
_session.addTransactedReceive(_queue, message);
//}
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
_session.acquireListenSemaphore();
_listener.onMessage(message);
} finally {
thread.setContextClassLoader(oldLoader);
_session.releaseListenSemaphore();
// XXX: commit/rollback?
if (_session.getTransacted())
_session.commit();
else
_session.acknowledge();
}
}
} catch (JMSException e) {
throw new MessageException(e);
}
}
}
}