/**
*
* Copyright 2004 Hiram Chirino
* 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.store.journal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.jms.JMSException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.journal.RecordLocation;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.QueueMessageContainer;
import org.codehaus.activemq.store.MessageStore;
import org.codehaus.activemq.store.cache.CacheMessageStore;
import org.codehaus.activemq.store.cache.CacheMessageStoreAware;
import org.codehaus.activemq.util.Callback;
import org.codehaus.activemq.util.TransactionTemplate;
/**
* A MessageStore that uses a Journal to store it's messages.
*
* @version $Revision: 1.7 $
*/
public class JournalMessageStore implements MessageStore, CacheMessageStoreAware {
private static final Log log = LogFactory.getLog(JournalMessageStore.class);
private final static class AckData {
private final RecordLocation location;
private final MessageAck ack;
AckData(MessageAck ack, RecordLocation location) {
this.ack = ack;
this.location = location;
}
}
private final JournalPersistenceAdapter peristenceAdapter;
private final MessageStore longTermStore;
private final String destinationName;
private final TransactionTemplate transactionTemplate;
private HashMap addedMessageLocations = new HashMap();
private ArrayList removedMessageLocations = new ArrayList();
/** A MessageStore that we can use to retreive messages quickly. */
private MessageStore cacheMessageStore = this;
private boolean sync = true;
public JournalMessageStore(JournalPersistenceAdapter adapter, MessageStore checkpointStore, String destinationName, boolean sync) {
this.peristenceAdapter = adapter;
this.longTermStore = checkpointStore;
this.destinationName = destinationName;
this.sync=sync;
this.transactionTemplate = new TransactionTemplate(adapter);
}
/**
* Not synchronized since the Journal has better throughput if you increase
* the number of conncurrent writes that it is doing.
*/
public MessageIdentity addMessage(ActiveMQMessage message) throws JMSException {
boolean sync = message.isReceiptRequired();
RecordLocation location = peristenceAdapter.writePacket(destinationName, message, sync);
synchronized(this) {
addedMessageLocations.put(message.getJMSMessageIdentity(), location);
}
// Update the messageIdentity sequence number so that we can reteive the message
// from the journal at a later time.
MessageIdentity messageIdentity = message.getJMSMessageIdentity();
messageIdentity.setSequenceNumber(location);
return messageIdentity;
}
/**
*/
public void removeMessage(MessageIdentity identity, MessageAck ack)
throws JMSException {
RecordLocation ackLocation = peristenceAdapter.writePacket(destinationName, ack, sync);
synchronized(this) {
RecordLocation addLocation = (RecordLocation) addedMessageLocations.remove(identity);
if( addLocation==null ) {
removedMessageLocations.add(new AckData(ack, ackLocation));
}
}
}
/**
* @return
* @throws JMSException
*/
public RecordLocation checkpoint() throws JMSException {
final RecordLocation rc[] = new RecordLocation[]{null};
// swap out the message hashmaps..
final ArrayList addedMessageIdentitys;
final ArrayList removedMessageLocations;
synchronized(this) {
addedMessageIdentitys = new ArrayList(this.addedMessageLocations.keySet());
removedMessageLocations = this.removedMessageLocations;
this.removedMessageLocations = new ArrayList();
}
transactionTemplate.run(new Callback() {
public void execute() throws Throwable {
// Checkpoint the added messages.
Iterator iterator = addedMessageIdentitys.iterator();
while (iterator.hasNext()) {
MessageIdentity identity = (MessageIdentity) iterator.next();
ActiveMQMessage msg = getCacheMessage(identity);
longTermStore.addMessage(msg);
synchronized(this) {
RecordLocation location = (RecordLocation)addedMessageLocations.remove(identity);
if( rc[0]==null || rc[0].compareTo(location)<0 ) {
rc[0] = location;
}
}
}
// Checkpoint the removed messages.
iterator = removedMessageLocations.iterator();
while (iterator.hasNext()) {
AckData data = (AckData)iterator.next();
longTermStore.removeMessage(data.ack.getMessageIdentity(),data.ack);
if( rc[0]==null || rc[0].compareTo(data.location)<0 ) {
rc[0] = data.location;
}
}
}
});
return rc[0];
}
private ActiveMQMessage getCacheMessage(MessageIdentity identity) throws JMSException {
return cacheMessageStore.getMessage(identity);
}
/**
*
*/
public ActiveMQMessage getMessage(MessageIdentity identity) throws JMSException {
ActiveMQMessage answer=null;
Object location = identity.getSequenceNumber();
if( location==null ) {
// The sequence number may not have been set but it may still be in the journal.
synchronized(this) {
location = addedMessageLocations.get(identity);
}
}
// Do we have a Journal sequence number?
if(location!=null && location instanceof RecordLocation) {
answer = (ActiveMQMessage) peristenceAdapter.readPacket((RecordLocation)location);
if( answer !=null )
return answer;
}
// If all else fails try the long term message store.
return longTermStore.getMessage(identity);
}
/**
* Replays the checkpointStore first as those messages are the oldest ones,
* then messages are replayed from the transaction log and then the cache is
* updated.
*
* @param container
* @throws JMSException
*/
public synchronized void recover(final QueueMessageContainer container)
throws JMSException {
longTermStore.recover(container);
}
public void start() throws JMSException {
longTermStore.start();
}
public void stop() throws JMSException {
longTermStore.stop();
}
/**
* @return Returns the longTermStore.
*/
public MessageStore getLongTermStore() {
return longTermStore;
}
/**
* @see org.codehaus.activemq.store.cache.CacheMessageStoreAware#setCacheMessageStore(org.codehaus.activemq.store.cache.CacheMessageStore)
*/
public void setCacheMessageStore(CacheMessageStore store) {
cacheMessageStore = store;
// Propagate the setCacheMessageStore method call to the longTermStore if possible.
if( longTermStore instanceof CacheMessageStoreAware ) {
((CacheMessageStoreAware)longTermStore).setCacheMessageStore(store);
}
}
}