/**
*
* 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.bdb;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryCursor;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.Transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.activemq.message.ActiveMQMessage;
import org.codehaus.activemq.message.ConsumerInfo;
import org.codehaus.activemq.message.MessageAck;
import org.codehaus.activemq.message.WireFormat;
import org.codehaus.activemq.service.MessageIdentity;
import org.codehaus.activemq.service.SubscriberEntry;
import org.codehaus.activemq.service.Subscription;
import org.codehaus.activemq.store.TopicMessageStore;
import org.codehaus.activemq.util.JMSExceptionHelper;
import javax.jms.JMSException;
import java.io.IOException;
/**
* @version $Revision: 1.2 $
*/
public class BDbTopicMessageStore extends BDbMessageStore implements TopicMessageStore {
private static final Log log = LogFactory.getLog(BDbTopicMessageStore.class);
private Database subscriptionDatabase;
public BDbTopicMessageStore(Database database, SecondaryDatabase secondaryDatabase, SecondaryConfig secondaryConfig, SequenceNumberCreator sequenceNumberCreator, WireFormat wireFormat, Database subscriptionDatabase) {
super(database, secondaryDatabase, secondaryConfig, sequenceNumberCreator, wireFormat);
this.subscriptionDatabase = subscriptionDatabase;
}
public void incrementMessageCount(MessageIdentity messageId) {
/** TODO */
}
public void decrementMessageCountAndMaybeDelete(MessageIdentity messageIdentity, MessageAck ack) {
/** TODO */
}
public void setLastAcknowledgedMessageIdentity(Subscription subscription, MessageIdentity messageIdentity) throws JMSException {
checkClosed();
try {
doSetLastAcknowledgedMessageIdentity(subscription, messageIdentity);
}
catch (DatabaseException e) {
throw JMSExceptionHelper.newJMSException("Failed to update last acknowledge messageID for : "
+ messageIdentity + ". Reason: " + e, e);
}
}
public void recoverSubscription(Subscription subscription, MessageIdentity lastDispatchedMessage) throws JMSException {
checkClosed();
SecondaryCursor cursor = null;
try {
DatabaseEntry lastAckKey = getLastAcknowledgedMessageID(subscription, lastDispatchedMessage);
if (lastAckKey != null) {
cursor = getSecondaryDatabase().openSecondaryCursor(BDbHelper.getTransaction(), getCursorConfig());
DatabaseEntry valueEntry = new DatabaseEntry();
OperationStatus status = cursor.getSearchKey(lastAckKey, valueEntry, LockMode.DEFAULT);
if (status != OperationStatus.SUCCESS) {
log.error("Could not find the last acknowledged record for: " + subscription + ". Status: " + status);
}
else {
while (true) {
// lets pass straight over the first entry, which we've already ack'd
status = cursor.getNext(lastAckKey, valueEntry, LockMode.DEFAULT);
if (status != OperationStatus.SUCCESS) {
if (status != OperationStatus.NOTFOUND) {
log.warn("Strange result when iterating to end of collection: " + status);
}
break;
}
ActiveMQMessage message = extractMessage(valueEntry);
subscription.addMessage(getContainer(), message);
}
}
}
}
catch (DatabaseException e) {
throw JMSExceptionHelper.newJMSException("Unable to recover topic subscription for: "
+ subscription + ". Reason: " + e, e);
}
catch (IOException e) {
throw JMSExceptionHelper.newJMSException("Unable to recover topic subscription for: "
+ subscription + ". Reason: " + e, e);
}
finally {
if (cursor != null) {
try {
cursor.close();
}
catch (DatabaseException e) {
log.warn("Caught exception closing cursor: " + e, e);
}
}
}
}
public MessageIdentity getLastestMessageIdentity() throws JMSException {
checkClosed();
SecondaryCursor cursor = null;
try {
cursor = getSecondaryDatabase().openSecondaryCursor(BDbHelper.getTransaction(), getCursorConfig());
DatabaseEntry keyEntry = new DatabaseEntry();
DatabaseEntry valueEntry = new DatabaseEntry();
OperationStatus status = cursor.getLast(keyEntry, valueEntry, LockMode.DEFAULT);
if (status == OperationStatus.SUCCESS) {
if (log.isDebugEnabled()) {
log.debug("Loaded last sequence number of: " + BDbHelper.longFromBytes(keyEntry.getData()));
}
return new MessageIdentity(null, keyEntry);
}
else if (status != OperationStatus.NOTFOUND) {
log.error("Could not find the last sequence number. Status: " + status);
}
return null;
}
catch (DatabaseException e) {
throw JMSExceptionHelper.newJMSException("Unable to load the last sequence number. Reason: " + e, e);
}
finally {
if (cursor != null) {
try {
cursor.close();
}
catch (DatabaseException e) {
log.warn("Caught exception closing cursor: " + e, e);
}
}
}
}
public SubscriberEntry getSubscriberEntry(ConsumerInfo info) throws JMSException {
return null; /** TODO */
}
public void setSubscriberEntry(ConsumerInfo info, SubscriberEntry subscriberEntry) throws JMSException {
/** TODO */
}
public synchronized void stop() throws JMSException {
JMSException firstException = BDbPersistenceAdapter.closeDatabase(subscriptionDatabase, null);
subscriptionDatabase = null;
super.stop();
if (firstException != null) {
throw JMSExceptionHelper.newJMSException("Unable to close the subscription database: " + firstException, firstException);
}
}
// Implementation methods
//-------------------------------------------------------------------------
protected DatabaseEntry getLastAcknowledgedMessageID(Subscription subscription, MessageIdentity lastDispatchedMessage) throws DatabaseException {
DatabaseEntry key = createKey(subscription.getPersistentKey());
DatabaseEntry value = new DatabaseEntry();
OperationStatus status = subscriptionDatabase.get(null, key, value, null);
if (status == OperationStatus.SUCCESS) {
return value;
}
else if (status == OperationStatus.NOTFOUND) {
// we need to insert the new entry
if (lastDispatchedMessage != null) {
return doSetLastAcknowledgedMessageIdentity(subscription, lastDispatchedMessage);
}
}
else {
log.warn("Unexpected status return from querying lastAcknowledgeSequenceNumber for: " + subscription + " status: " + status);
}
return null;
}
protected DatabaseEntry doSetLastAcknowledgedMessageIdentity(Subscription subscription, MessageIdentity messageIdentity) throws DatabaseException {
Transaction transaction = BDbHelper.getTransaction();
DatabaseEntry key = createKey(subscription.getPersistentKey());
DatabaseEntry value = getSequenceNumberKey(messageIdentity);
subscriptionDatabase.put(transaction, key, value);
return value;
}
}