/**
*
* 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.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.QueueList;
import org.codehaus.activemq.service.QueueListEntry;
import org.codehaus.activemq.service.QueueMessageContainer;
import org.codehaus.activemq.store.MessageStore;
import org.codehaus.activemq.store.PersistenceAdapter;
import org.codehaus.activemq.util.Callback;
import org.codehaus.activemq.util.TransactionTemplate;
import javax.jms.JMSException;
/**
* A default implemenation of a Durable Queue based
* {@link org.codehaus.activemq.service.MessageContainer}
* which acts as an adapter between the {@link org.codehaus.activemq.service.MessageContainerManager}
* requirements and those of the persistent {@link MessageStore} implementations.
*
* @version $Revision: 1.19 $
*/
public class DurableQueueMessageContainer implements QueueMessageContainer {
private static final Log log = LogFactory.getLog(DurableQueueMessageContainer.class);
private MessageStore messageStore;
private String destinationName;
/**
* messages to be delivered
*/
private QueueList messagesToBeDelivered;
/**
* messages that have been delivered but not acknowledged
*/
private QueueList deliveredMessages;
private PersistenceAdapter persistenceAdapter;
private TransactionTemplate transactionTemplate;
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.persistenceAdapter = persistenceAdapter;
this.messageStore = messageStore;
this.destinationName = destinationName;
this.messagesToBeDelivered = messagesToBeDelivered;
this.deliveredMessages = deliveredMessages;
this.transactionTemplate = new TransactionTemplate(persistenceAdapter);
}
public String getDestinationName() {
return destinationName;
}
public MessageIdentity addMessage(ActiveMQMessage message) throws JMSException {
MessageIdentity answer = messageStore.addMessage(message);
synchronized( this ) {
messagesToBeDelivered.add(answer);
}
return answer;
}
public synchronized void delete(MessageIdentity messageID, MessageAck ack) throws JMSException {
// lets find the cached identity as it has the sequence number
// attached to it
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 {
messageStore.removeMessage(storedIdentity, ack);
}
}
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 messageIdentity, MessageAck 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;
transactionTemplate.run(new Callback() {
public void execute() throws Throwable {
messageStore.start();
messageStore.recover(container);
}
});
}
public synchronized void recoverMessageToBeDelivered(MessageIdentity messageIdentity) throws JMSException {
messagesToBeDelivered.add(messageIdentity);
}
public void stop() throws JMSException {
messageStore.stop();
}
}