/**
*
* 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.activemq.service.impl;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.MessageAck;
import org.activemq.service.DeadLetterPolicy;
import org.activemq.service.MessageContainerAdmin;
import org.activemq.service.MessageIdentity;
import org.activemq.service.QueueList;
import org.activemq.service.QueueListEntry;
import org.activemq.service.QueueMessageContainer;
import org.activemq.service.TransactionManager;
import org.activemq.service.TransactionTask;
import org.activemq.store.MessageStore;
import org.activemq.store.PersistenceAdapter;
import org.activemq.store.RecoveryListener;
/**
* A default implementation of a Durable Queue based
* {@link org.activemq.service.MessageContainer}
* which acts as an adapter between the {@link org.activemq.service.MessageContainerManager}
* requirements and those of the persistent {@link MessageStore} implementations.
*
* @version $Revision: 1.1.1.1 $
*/
public class DurableQueueMessageContainer implements QueueMessageContainer, MessageContainerAdmin {
private static final Log log = LogFactory.getLog(DurableQueueMessageContainer.class);
private MessageStore messageStore;
private String destinationName;
private boolean deadLetterQueue;
/**
* messages to be delivered
*/
private QueueList messagesToBeDelivered;
/**
* messages that have been delivered but not acknowledged
*/
private QueueList deliveredMessages;
public DurableQueueMessageContainer(PersistenceAdapter persistenceAdapter, MessageStore messageStore, String destinationName) {
this(persistenceAdapter, messageStore, destinationName, new DefaultQueueList(), new DefaultQueueList());
}
public DurableQueueMessageContainer(PersistenceAdapter persistenceAdapter, MessageStore messageStore, String destinationName, QueueList messagesToBeDelivered, QueueList deliveredMessages) {
this.messageStore = messageStore;
this.destinationName = destinationName;
this.messagesToBeDelivered = messagesToBeDelivered;
this.deliveredMessages = deliveredMessages;
this.deadLetterQueue = destinationName.startsWith(DeadLetterPolicy.DEAD_LETTER_PREFIX);
}
public String getDestinationName() {
return destinationName;
}
public void addMessage(ActiveMQMessage message) throws JMSException {
messageStore.addMessage(message);
final MessageIdentity answer = message.getJMSMessageIdentity();
// If there is no transaction.. then this executes directly.
TransactionManager.getContexTransaction().addPostCommitTask(new TransactionTask(){
public void execute() throws Throwable {
synchronized( this ) {
messagesToBeDelivered.add(answer);
}
}
});
}
public synchronized void delete(final MessageIdentity messageID, MessageAck ack) throws JMSException {
messageStore.removeMessage(ack);
TransactionManager.getContexTransaction().addPostCommitTask(new TransactionTask(){
public void execute() throws Throwable {
doDelete(messageID);
}
});
}
/**
* @param messageID
* @param storedIdentity
* @throws JMSException
*/
private void doDelete(MessageIdentity messageID) throws JMSException {
MessageIdentity storedIdentity=null;
synchronized( this ) {
QueueListEntry entry = deliveredMessages.getFirstEntry();
while (entry != null) {
MessageIdentity identity = (MessageIdentity) entry.getElement();
if (messageID.equals(identity)) {
deliveredMessages.remove(entry);
storedIdentity=identity;
break;
}
entry = deliveredMessages.getNextEntry(entry);
}
if (storedIdentity==null) {
// maybe the messages have not been delivered yet
// as we are recovering from a previous transaction log
entry = messagesToBeDelivered.getFirstEntry();
while (entry != null) {
MessageIdentity identity = (MessageIdentity) entry.getElement();
if (messageID.equals(identity)) {
messagesToBeDelivered.remove(entry);
storedIdentity=identity;
break;
}
entry = messagesToBeDelivered.getNextEntry(entry);
}
}
}
if (storedIdentity==null) {
log.error("Attempt to acknowledge unknown messageID: " + messageID);
} else {
}
}
public ActiveMQMessage getMessage(MessageIdentity messageID) throws JMSException {
return messageStore.getMessage(messageID);
}
public boolean containsMessage(MessageIdentity messageIdentity) throws JMSException {
/** TODO: make more optimal implementation */
return getMessage(messageIdentity) != null;
}
/**
* Does nothing since when we receive an acknowledgement on a queue
* we can delete the message
*
* @param messageIdentity
*/
public void registerMessageInterest(MessageIdentity messageIdentity) {
}
/**
* Does nothing since when we receive an acknowledgement on a queue
* we can delete the message
*
* @param messageIdentity
* @param ack
*/
public void unregisterMessageInterest(MessageIdentity ack) {
}
public ActiveMQMessage poll() throws JMSException {
ActiveMQMessage message = null;
MessageIdentity messageIdentity = null;
synchronized (this) {
messageIdentity = (MessageIdentity) messagesToBeDelivered.removeFirst();
if (messageIdentity != null) {
deliveredMessages.add(messageIdentity);
}
}
if (messageIdentity != null) {
message = messageStore.getMessage(messageIdentity);
}
return message;
}
public ActiveMQMessage peekNext(MessageIdentity messageID) throws JMSException {
ActiveMQMessage answer = null;
MessageIdentity identity = null;
synchronized( this ) {
if (messageID == null) {
identity = (MessageIdentity) messagesToBeDelivered.getFirst();
}
else {
int index = messagesToBeDelivered.indexOf(messageID);
if (index >= 0 && (index + 1) < messagesToBeDelivered.size()) {
identity = (MessageIdentity) messagesToBeDelivered.get(index + 1);
}
}
}
if (identity != null) {
answer = messageStore.getMessage(identity);
}
return answer;
}
public synchronized void returnMessage(MessageIdentity messageIdentity) throws JMSException {
boolean result = deliveredMessages.remove(messageIdentity);
messagesToBeDelivered.addFirst(messageIdentity);
}
/**
* called to reset dispatch pointers if a new Message Consumer joins
*
* @throws javax.jms.JMSException
*/
public synchronized void reset() throws JMSException {
//new Message Consumer - move all filtered/undispatched messages to front of queue
int count = 0;
MessageIdentity messageIdentity = (MessageIdentity) deliveredMessages.removeFirst();
while (messageIdentity != null) {
messagesToBeDelivered.add(count++, messageIdentity);
messageIdentity = (MessageIdentity) deliveredMessages.removeFirst();
}
}
public synchronized void start() throws JMSException {
final QueueMessageContainer container = this;
messageStore.start();
messageStore.recover(new RecoveryListener() {
public void recoverMessage(MessageIdentity messageIdentity) throws JMSException {
DurableQueueMessageContainer.this.recoverMessageToBeDelivered(messageIdentity);
}
});
}
public synchronized void recoverMessageToBeDelivered(MessageIdentity messageIdentity) throws JMSException {
messagesToBeDelivered.add(messageIdentity);
}
public void stop() throws JMSException {
messageStore.stop();
}
/**
* @see org.activemq.service.MessageContainer#getMessageContainerAdmin()
*/
public MessageContainerAdmin getMessageContainerAdmin() {
return this;
}
/**
* @see org.activemq.service.MessageContainerAdmin#empty()
*/
public void empty() throws JMSException {
messageStore.removeAllMessages();
}
/**
* @see org.activemq.service.QueueMessageContainer#isDeadLetterQueue()
*/
public boolean isDeadLetterQueue() {
return deadLetterQueue;
}
/**
* @see org.activemq.service.QueueMessageContainer#setDeadLetterQueue(boolean)
*/
public void setDeadLetterQueue(boolean value) {
deadLetterQueue = value;
}
}