/**
*
* Copyright 2004 Protique Ltd
* Copyright 2005 Hiram Chirino
*
* 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.activemq.service.boundedvm;
import org.activemq.broker.BrokerClient;
import org.activemq.filter.Filter;
import org.activemq.io.util.MemoryBoundedQueue;
import org.activemq.io.util.MemoryBoundedQueueManager;
import org.activemq.io.util.MemoryManageable;
import org.activemq.message.ActiveMQDestination;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.ConsumerInfo;
import org.activemq.service.DeadLetterPolicy;
import org.activemq.service.MessageContainerAdmin;
import org.activemq.service.MessageIdentity;
import org.activemq.service.QueueListEntry;
import org.activemq.service.RedeliveryPolicy;
import org.activemq.service.Service;
import org.activemq.service.TransactionManager;
import org.activemq.service.TransactionTask;
import org.activemq.service.impl.DefaultQueueList;
import org.activemq.store.MessageStore;
import org.activemq.store.RecoveryListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import EDU.oswego.cs.dl.util.concurrent.Executor;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
import javax.jms.JMSException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A MessageContainer for Durable queues
*
* @version $Revision: 1.1.1.1 $
*/
public class DurableQueueBoundedMessageContainer implements Service, Runnable, MessageContainerAdmin {
private final MessageStore messageStore;
private final MemoryBoundedQueueManager queueManager;
private final ActiveMQDestination destination;
private final Executor threadPool;
private final DeadLetterPolicy deadLetterPolicy;
private final Log log;
private final MemoryBoundedQueue queue;
private final DefaultQueueList subscriptions = new DefaultQueueList();
private final SynchronizedBoolean started = new SynchronizedBoolean(false);
private final SynchronizedBoolean running = new SynchronizedBoolean(false);
private final Object dispatchMutex = new Object();
private final Object subscriptionsMutex = new Object();
private long idleTimestamp; //length of time (ms) there have been no active subscribers
/**
* Construct this beast
*
* @param threadPool
* @param queueManager
* @param destination
* @param redeliveryPolicy
* @param deadLetterPolicy
*/
public DurableQueueBoundedMessageContainer(MessageStore messageStore, Executor threadPool, MemoryBoundedQueueManager queueManager,
ActiveMQDestination destination,RedeliveryPolicy redeliveryPolicy, DeadLetterPolicy deadLetterPolicy) {
this.messageStore = messageStore;
this.threadPool = threadPool;
this.queueManager = queueManager;
this.destination = destination;
this.deadLetterPolicy = deadLetterPolicy;
this.queue = queueManager.getMemoryBoundedQueue("DURABLE_QUEUE:-" + destination.getPhysicalName());
this.log = LogFactory.getLog("DurableQueueBoundedMessageContainer:- " + destination);
}
/**
* @return true if there are subscribers waiting for messages
*/
public boolean isActive(){
return !subscriptions.isEmpty();
}
/**
* @return true if no messages are enqueued
*/
public boolean isEmpty(){
return queue.isEmpty();
}
/**
* @return the timestamp (ms) from the when the last active subscriber stopped
*/
public long getIdleTimestamp(){
return idleTimestamp;
}
/**
* Add a consumer to dispatch messages to
*
* @param filter
* @param info
* @param client
* @return DurableQueueSubscription
* @throws JMSException
*/
public DurableQueueSubscription addConsumer(Filter filter, ConsumerInfo info, BrokerClient client)
throws JMSException {
DurableQueueSubscription ts = findMatch(info);
if (ts == null) {
MemoryBoundedQueue queue = queueManager.getMemoryBoundedQueue("DURABLE_SUB:-"+info.getConsumerId());
MemoryBoundedQueue ackQueue = queueManager.getMemoryBoundedQueue("DURABLE_SUB_ACKED:-"+info.getConsumerId());
ts = new DurableQueueSubscription(client, queue, ackQueue, filter, info);
synchronized (subscriptionsMutex) {
idleTimestamp = 0;
subscriptions.add(ts);
checkRunning();
}
}
return ts;
}
/**
* Remove a consumer
*
* @param info
* @throws JMSException
*/
public void removeConsumer(ConsumerInfo info) throws JMSException {
synchronized (subscriptionsMutex) {
DurableQueueSubscription ts = findMatch(info);
if (ts != null) {
subscriptions.remove(ts);
if (subscriptions.isEmpty()) {
running.commit(true, false);
idleTimestamp = System.currentTimeMillis();
}
// get unacknowledged messages and re-enqueue them
List list = ts.getUndeliveredMessages();
for (int i = list.size() - 1; i >= 0; i--) {
queue.enqueueFirstNoBlock((MemoryManageable) list.get(i));
}
// If it is a queue browser, then re-enqueue the browsed
// messages.
if (ts.isBrowser()) {
list = ts.listAckedMessages();
for (int i = list.size() - 1; i >= 0; i--) {
queue.enqueueFirstNoBlock((MemoryManageable) list
.get(i));
}
ts.removeAllAckedMessages();
}
ts.close();
}
}
}
/**
* start working
*
* @throws JMSException
*/
public void start() throws JMSException {
if (started.commit(false, true)) {
messageStore.start();
messageStore.recover(new RecoveryListener() {
public void recoverMessage(MessageIdentity messageIdentity) throws JMSException {
recoverMessageToBeDelivered(messageIdentity);
}
});
checkRunning();
}
}
private void recoverMessageToBeDelivered(MessageIdentity msgId) throws JMSException {
DurableMessagePointer pointer = new DurableMessagePointer(messageStore, getDestination(), messageStore.getMessage(msgId));
queue.enqueue(pointer);
}
/**
* enqueue a message for dispatching
*
* @param message
* @throws JMSException
*/
public void enqueue(final ActiveMQMessage message) throws JMSException {
final DurableMessagePointer pointer = new DurableMessagePointer(messageStore, getDestination(), message);
if (message.isAdvisory()) {
doAdvisoryDispatchMessage(pointer);
}
else {
messageStore.addMessage(message);
// If there is no transaction.. then this executes directly.
TransactionManager.getContexTransaction().addPostCommitTask(new TransactionTask(){
public void execute() throws Throwable {
queue.enqueue(pointer);
checkRunning();
}
});
}
}
public void redeliver(DurableMessagePointer message) {
queue.enqueueFirstNoBlock(message);
checkRunning();
}
public void redeliver(List messages) {
queue.enqueueAllFirstNoBlock(messages);
checkRunning();
}
/**
* stop working
*/
public void stop() {
started.set(false);
running.set(false);
queue.clear();
}
/**
* close down this container
*
* @throws JMSException
*/
public void close() throws JMSException {
if (started.get()) {
stop();
}
synchronized(subscriptionsMutex){
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
DurableQueueSubscription ts = (DurableQueueSubscription) entry.getElement();
ts.close();
entry = subscriptions.getNextEntry(entry);
}
subscriptions.clear();
}
}
/**
* do some dispatching
*/
public void run() {
// Only allow one thread at a time to dispatch.
synchronized (dispatchMutex) {
boolean dispatched = false;
boolean targeted = false;
DurableMessagePointer messagePointer = null;
int notDispatchedCount = 0;
int sleepTime = 250;
int iterationsWithoutDispatchingBeforeStopping = 10000 / sleepTime;// ~10
// seconds
Map messageParts = new HashMap();
try {
while (started.get() && running.get()) {
dispatched = false;
targeted = false;
synchronized (subscriptionsMutex) {
if (!subscriptions.isEmpty()) {
messagePointer = (DurableMessagePointer) queue
.dequeue(sleepTime);
if (messagePointer != null) {
ActiveMQMessage message = messagePointer
.getMessage();
if (!message.isExpired()) {
QueueListEntry entry = subscriptions
.getFirstEntry();
while (entry != null) {
DurableQueueSubscription ts = (DurableQueueSubscription) entry
.getElement();
if (ts.isTarget(message)) {
targeted = true;
if (message.isMessagePart()) {
DurableQueueSubscription sameTarget = (DurableQueueSubscription) messageParts
.get(message
.getParentMessageID());
if (sameTarget == null) {
sameTarget = ts;
messageParts
.put(
message
.getParentMessageID(),
sameTarget);
}
sameTarget
.doDispatch(messagePointer);
if (message.isLastMessagePart()) {
messageParts
.remove(message
.getParentMessageID());
}
messagePointer = null;
dispatched = true;
notDispatchedCount = 0;
break;
} else if (ts.canAcceptMessages()) {
ts.doDispatch(messagePointer);
messagePointer = null;
dispatched = true;
notDispatchedCount = 0;
subscriptions.rotate();
break;
}
}
entry = subscriptions
.getNextEntry(entry);
}
} else {
// expire message
if (log.isDebugEnabled()) {
log.debug("expired message: "
+ messagePointer);
}
if (deadLetterPolicy != null) {
deadLetterPolicy
.sendToDeadLetter(messagePointer
.getMessage());
}
messagePointer = null;
}
}
}
}
if (!dispatched) {
if (messagePointer != null) {
if (targeted) {
queue.enqueueFirstNoBlock(messagePointer);
} else {
//no matching subscribers - dump to end and hope one shows up ...
queue.enqueueNoBlock(messagePointer);
}
}
if (running.get()) {
if (notDispatchedCount++ > iterationsWithoutDispatchingBeforeStopping
&& queue.isEmpty()) {
synchronized (running) {
running.commit(true, false);
}
} else {
Thread.sleep(sleepTime);
}
}
}
}
} catch (InterruptedException ie) {
//someone is stopping us from another thread
} catch (Throwable e) {
log.warn("stop dispatching", e);
stop();
}
}
}
private DurableQueueSubscription findMatch(ConsumerInfo info) throws JMSException {
DurableQueueSubscription result = null;
synchronized (subscriptionsMutex) {
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
DurableQueueSubscription ts = (DurableQueueSubscription) 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();
}
protected void clear() {
queue.clear();
}
protected void removeExpiredMessages() {
long currentTime = System.currentTimeMillis();
List list = queue.getContents();
for (int i = 0;i < list.size();i++) {
DurableMessagePointer msgPointer = (DurableMessagePointer) list.get(i);
ActiveMQMessage message = msgPointer.getMessage();
if (message.isExpired(currentTime)) {
// TODO: remove message from message store.
queue.remove(msgPointer);
if (log.isDebugEnabled()) {
log.debug("expired message: " + msgPointer);
}
}
}
}
protected void checkRunning(){
if (!running.get() && started.get() && !subscriptions.isEmpty()) {
synchronized (running) {
if (running.commit(false, true)) {
try {
threadPool.execute(this);
}
catch (InterruptedException e) {
log.error(this + " Couldn't start executing ",e);
}
}
}
}
}
/**
* @see org.activemq.service.MessageContainer#getMessageContainerAdmin()
*/
public MessageContainerAdmin getMessageContainerAdmin() {
return this;
}
/**
* @see org.activemq.service.MessageContainerAdmin#empty()
*/
public void empty() throws JMSException {
if( subscriptions.isEmpty() ) {
messageStore.removeAllMessages();
queue.clear();
} else {
throw new JMSException("Cannot empty a queue while it is use.");
}
}
/**
* Dispatch an Advisory Message
* @param messagePointer
*/
private synchronized void doAdvisoryDispatchMessage(DurableMessagePointer messagePointer) {
ActiveMQMessage message = messagePointer.getMessage();
try {
if (message.isAdvisory() && !message.isExpired()) {
synchronized (subscriptionsMutex) {
QueueListEntry entry = subscriptions.getFirstEntry();
while (entry != null) {
DurableQueueSubscription ts = (DurableQueueSubscription) entry
.getElement();
if (ts.isTarget(message)) {
ts.doDispatch(messagePointer);
break;
}
entry = subscriptions.getNextEntry(entry);
}
}
}
} catch (JMSException jmsEx) {
log.warn("Failed to dispatch advisory", jmsEx);
}
}
}