/**
*
* Copyright 2004 Protique Ltd
*
* 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.codehaus.activemq.service.boundedvm;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.broker.BrokerClient;
import org.codehaus.activemq.filter.Filter;
import org.codehaus.activemq.message.ActiveMQDestination;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.message.util.MemoryBoundedQueue;
import org.codehaus.activemq.message.util.MemoryBoundedQueueManager;
import org.codehaus.activemq.service.MessageContainer;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.QueueListEntry;
import org.codehaus.activemq.service.Service;
import org.codehaus.activemq.service.impl.DefaultQueueList;
import javax.jms.JMSException;
import java.util.List;
import java.util.ListIterator;
/**
* A MessageContainer for transient queues
*
* @version $Revision: 1.6 $
*/
public class TransientQueueBoundedMessageContainer implements MessageContainer, Service, Runnable {
private MemoryBoundedQueueManager queueManager;
private ActiveMQDestination destination;
private SynchronizedBoolean started;
private MemoryBoundedQueue queue;
private Thread worker;
private DefaultQueueList subscriptions;
private Log log;
/**
* Construct this beast
*
* @param queueManager
* @param destination
*/
public TransientQueueBoundedMessageContainer(MemoryBoundedQueueManager queueManager, ActiveMQDestination destination) {
this.queueManager = queueManager;
this.destination = destination;
this.queue = queueManager.getMemoryBoundedQueue("TRANSIENT_QUEUE:-" + destination.getPhysicalName());
this.started = new SynchronizedBoolean(false);
this.subscriptions = new DefaultQueueList();
this.log = LogFactory.getLog("TransientQueueBoundedMessageContainer:- " + destination);
}
/**
* @return true if this Container has no active subscriptions and there are no messages to dispatch
*/
public boolean isInactive() {
return subscriptions.isEmpty() && queue.isEmpty();
}
/**
* Add a consumer to dispatch messages to
*
* @param filter
* @param info
* @param client
* @return
* @throws JMSException
*/
public TransientQueueSubscription addConsumer(Filter filter, ConsumerInfo info, BrokerClient client)
throws JMSException {
TransientQueueSubscription ts = findMatch(info);
if (ts == null) {
MemoryBoundedQueue queue = queueManager.getMemoryBoundedQueue(client.toString() + info.getConsumerId());
ts = new TransientQueueSubscription(client, queue, filter, info);
subscriptions.add(ts);
}
return ts;
}
/**
* Remove a consumer
*
* @param info
* @throws JMSException
*/
public void removeConsumer(ConsumerInfo info) throws JMSException {
TransientQueueSubscription ts = findMatch(info);
if (ts != null) {
subscriptions.remove(ts);
//get unacknowledged messages and re-enqueue them
List list = ts.getUndeliveredMessages();
for (ListIterator i = list.listIterator(list.size()); i.hasPrevious();) {
ActiveMQMessage message = (ActiveMQMessage) i.previous();
message.setJMSRedelivered(true);
queue.enqueueFirstNoBlock(message);
}
list.clear();
ts.close();
}
}
/**
* start working
*/
public void start() {
if (started.commit(false, true)) {
worker = new Thread(this, "TransientQueueDispatcher");
worker.setPriority(Thread.NORM_PRIORITY + 1);
worker.start();
}
}
/**
* enqueue a message for dispatching
*
* @param message
*/
public void enqueue(ActiveMQMessage message) {
queue.enqueue(message);
}
/**
* enqueue a message for dispatching
*
* @param message
*/
public void enqueueFirst(ActiveMQMessage message) {
queue.enqueueFirstNoBlock(message);
}
/**
* stop working
*/
public void stop() {
started.set(false);
queue.clear();
}
/**
* close down this container
*
* @throws JMSException
*/
public void close() throws JMSException {
if (started.get()) {
stop();
}
queue.close();
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
TransientQueueSubscription ts = (TransientQueueSubscription) entry.getElement();
ts.close();
entry = subscriptions.getNextEntry(entry);
}
subscriptions.clear();
}
/**
* do some dispatching
*/
public void run() {
boolean dispatched = false;
boolean targeted = false;
ActiveMQMessage message = null;
try {
while (started.get()) {
dispatched = false;
targeted = false;
if (!subscriptions.isEmpty()) {
message = (ActiveMQMessage) queue.dequeue(2000);
if (message != null) {
if (!message.isExpired()) {
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
TransientQueueSubscription ts = (TransientQueueSubscription) entry.getElement();
if (ts.isTarget(message)) {
targeted = true;
if (ts.canAcceptMessages()) {
ts.doDispatch(message);
message = null;
dispatched = true;
subscriptions.rotate();
break;
}
}
entry = subscriptions.getNextEntry(entry);
}
}
else {
//expire message
if (log.isDebugEnabled()) {
log.debug("expired message: " + message);
}
message = null;
}
}
}
if (!dispatched) {
if (message != null) {
if (targeted) {
queue.enqueueFirstNoBlock(message);
}
else {
//no matching subscribers - dump to end and hope one shows up ...
queue.enqueueNoBlock(message);
}
}
Thread.sleep(250);
}
}
}
catch (Exception e) {
stop();
log.warn("stop dispatching", e);
}
}
private TransientQueueSubscription findMatch(ConsumerInfo info) throws JMSException {
TransientQueueSubscription result = null;
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
TransientQueueSubscription ts = (TransientQueueSubscription) entry.getElement();
if (ts.getConsumerInfo().equals(info)) {
result = ts;
break;
}
entry = subscriptions.getNextEntry(entry);
}
return result;
}
/**
* @return the destination associated with this container
*/
public ActiveMQDestination getDestination() {
return destination;
}
/**
* @return the destination name
*/
public String getDestinationName() {
return destination.getPhysicalName();
}
/**
* @param msg
* @return
* @throws JMSException
*/
public MessageIdentity addMessage(ActiveMQMessage msg) throws JMSException {
return null;
}
/**
* @param messageIdentity
* @param ack
* @throws JMSException
*/
public void delete(MessageIdentity messageIdentity, MessageAck ack) throws JMSException {
}
/**
* @param messageIdentity
* @return
* @throws JMSException
*/
public ActiveMQMessage getMessage(MessageIdentity messageIdentity) throws JMSException {
return null;
}
/**
* @param messageIdentity
* @throws JMSException
*/
public void registerMessageInterest(MessageIdentity messageIdentity) throws JMSException {
}
/**
* @param messageIdentity
* @param ack
* @throws JMSException
*/
public void unregisterMessageInterest(MessageIdentity messageIdentity, MessageAck ack) throws JMSException {
}
/**
* @param messageIdentity
* @return
* @throws JMSException
*/
public boolean containsMessage(MessageIdentity messageIdentity) throws JMSException {
return false;
}
protected boolean hasActiveSubscribers() {
return !subscriptions.isEmpty();
}
protected void clear() {
queue.clear();
}
protected void removeExpiredMessages() {
long currentTime = System.currentTimeMillis();
List list = queue.getContents();
for (int i = 0; i < list.size(); i++) {
ActiveMQMessage msg = (ActiveMQMessage) list.get(i);
if (msg.isExpired(currentTime)) {
queue.remove(msg);
if (log.isDebugEnabled()) {
log.debug("expired message: " + msg);
}
}
}
}
}