/**
*
* 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.store.jdbm;
import java.io.IOException;
import javax.jms.JMSException;
import jdbm.btree.BTree;
import jdbm.helper.Tuple;
import jdbm.helper.TupleBrowser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.activemq.AlreadyClosedException;
import org.activemq.message.ActiveMQMessage;
import org.activemq.message.MessageAck;
import org.activemq.service.MessageIdentity;
import org.activemq.service.impl.MessageEntry;
import org.activemq.store.MessageStore;
import org.activemq.store.RecoveryListener;
import org.activemq.util.JMSExceptionHelper;
/**
* @version $Revision: 1.1 $
*/
public class JdbmMessageStore implements MessageStore {
private static final Log log = LogFactory.getLog(JdbmMessageStore.class);
private BTree messageTable;
private BTree orderedIndex;
private long lastSequenceNumber = 0;
public JdbmMessageStore(BTree messageTable, BTree orderedIndex) {
this.messageTable = messageTable;
this.orderedIndex = orderedIndex;
}
public synchronized void addMessage(ActiveMQMessage message) throws JMSException {
if (log.isDebugEnabled()) {
log.debug("Adding message to container: " + message);
}
MessageEntry entry = new MessageEntry(message);
Object sequenceNumber = null;
synchronized (this) {
sequenceNumber = new Long(++lastSequenceNumber);
}
try {
String messageID = message.getJMSMessageID();
getMessageTable().insert(messageID, entry, true);
getOrderedIndex().insert(sequenceNumber, messageID, true);
MessageIdentity answer = message.getJMSMessageIdentity();
answer.setSequenceNumber(sequenceNumber);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to add message: " + message + " in container: " + e, e);
}
}
public synchronized ActiveMQMessage getMessage(MessageIdentity identity) throws JMSException {
String messageID = identity.getMessageID();
ActiveMQMessage message = null;
try {
MessageEntry entry = (MessageEntry) getMessageTable().find(messageID);
if (entry != null) {
message = entry.getMessage();
message.getJMSMessageIdentity().setSequenceNumber(identity.getSequenceNumber());
}
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to get message for messageID: " + messageID + " " + e, e);
}
return message;
}
public synchronized void removeMessage(MessageAck ack) throws JMSException {
removeMessage(ack.getMessageIdentity());
}
public synchronized void removeMessage(MessageIdentity identity) throws JMSException {
if (identity.getMessageID() == null) {
throw new JMSException("Cannot remove message with null messageID for sequence number: " + identity.getSequenceNumber());
}
try {
if( identity.getSequenceNumber()==null )
identity.setSequenceNumber(findSequenceNumber(identity.getMessageID()));
getMessageTable().remove(identity.getMessageID());
getOrderedIndex().remove(identity.getSequenceNumber());
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to delete message for messageID: " + identity.getMessageID() + " " + e, e);
}
catch (IllegalArgumentException e) {
log.warn("Could not find sequence number: " + identity.getSequenceNumber() + " in queue. " + e);
}
}
public synchronized void recover(RecoveryListener listener) throws JMSException {
try {
Tuple tuple = new Tuple();
TupleBrowser iter = getOrderedIndex().browse();
while (iter.getNext(tuple)) {
Long key = (Long) tuple.getKey();
MessageIdentity messageIdentity = null;
if (key != null) {
String messageID = (String) tuple.getValue();
if (messageID != null) {
messageIdentity = new MessageIdentity(messageID, key);
}
}
if (messageIdentity != null) {
listener.recoverMessage(messageIdentity);
}
else {
log.warn("Could not find message for sequenceNumber: " + key);
}
}
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to recover the durable queue store. Reason: " + e, e);
}
}
public synchronized void start() throws JMSException {
try {
// lets iterate through all IDs from the
Tuple tuple = new Tuple();
Long lastSequenceNumber = null;
TupleBrowser iter = getOrderedIndex().browse();
while (iter.getNext(tuple)) {
lastSequenceNumber = (Long) tuple.getKey();
}
if (lastSequenceNumber != null) {
this.lastSequenceNumber = lastSequenceNumber.longValue();
if (log.isDebugEnabled()) {
log.debug("Last sequence number is: " + lastSequenceNumber + " for: " + this);
}
}
else {
if (log.isDebugEnabled()) {
log.debug("Started empty database for: " + this);
}
}
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to find the last sequence number. Reason: " + e, e);
}
}
public synchronized void stop() throws JMSException {
JMSException firstException = closeTable(orderedIndex, null);
firstException = closeTable(messageTable, firstException);
orderedIndex = null;
messageTable = null;
if (firstException != null) {
throw firstException;
}
}
// Implementation methods
//-------------------------------------------------------------------------
protected long getLastSequenceNumber() {
return lastSequenceNumber;
}
protected BTree getMessageTable() throws AlreadyClosedException {
if (messageTable == null) {
throw new AlreadyClosedException("JDBM MessageStore");
}
return messageTable;
}
protected BTree getOrderedIndex() throws AlreadyClosedException {
if (orderedIndex == null) {
throw new AlreadyClosedException("JDBM MessageStore");
}
return orderedIndex;
}
/**
* Looks up the message using the given sequence number
*/
protected ActiveMQMessage getMessageBySequenceNumber(Long sequenceNumber) throws IOException, JMSException {
ActiveMQMessage message = null;
String messageID = (String) getOrderedIndex().find(sequenceNumber);
if (messageID != null) {
message = getMessage(new MessageIdentity(messageID, sequenceNumber));
}
return message;
}
/**
* Finds the sequence number for the given messageID
*
* @param messageID
* @return
*/
protected Object findSequenceNumber(String messageID) throws IOException, AlreadyClosedException {
log.warn("Having to table scan to find the sequence number for messageID: " + messageID);
Tuple tuple = new Tuple();
TupleBrowser iter = getOrderedIndex().browse();
while (iter.getNext(tuple)) {
Object value = tuple.getValue();
if (messageID.equals(value)) {
return tuple.getKey();
}
}
return null;
}
protected JMSException closeTable(BTree table, JMSException firstException) {
table = null;
return null;
}
public void removeAllMessages() throws JMSException {
try {
Tuple tuple = new Tuple();
TupleBrowser iter = getOrderedIndex().browse();
while (iter.getNext(tuple)) {
Object sequenceID = tuple.getKey();
MessageIdentity messageIdentity = null;
if (sequenceID != null) {
String messageID = (String) tuple.getValue();
if (messageID != null) {
getMessageTable().remove(messageID);
}
getOrderedIndex().remove(sequenceID);
}
}
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Failed to delete all messages in table: " + e, e);
}
}
}