diff options
| author | Kim van der Riet <kpvdr@apache.org> | 2012-05-04 15:39:19 +0000 |
|---|---|---|
| committer | Kim van der Riet <kpvdr@apache.org> | 2012-05-04 15:39:19 +0000 |
| commit | 633c33f224f3196f3f9bd80bd2e418d8143fea06 (patch) | |
| tree | 1391da89470593209466df68c0b40b89c14963b1 /java/bdbstore | |
| parent | c73f9286ebff93a6c8dbc29cf05e258c4b55c976 (diff) | |
| download | qpid-python-633c33f224f3196f3f9bd80bd2e418d8143fea06.tar.gz | |
QPID-3858: Updated branch - merged from trunk r.1333987
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/asyncstore@1334037 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java/bdbstore')
63 files changed, 6335 insertions, 5015 deletions
diff --git a/java/bdbstore/bin/backup.sh b/java/bdbstore/bin/backup.sh index 0fa1d57392..f3386e79dc 100755 --- a/java/bdbstore/bin/backup.sh +++ b/java/bdbstore/bin/backup.sh @@ -34,7 +34,7 @@ if [ -z "${QPID_HOME}" ]; then export QPID_HOME=`cd ${WHEREAMI}/../ && pwd` fi -VERSION=0.15 +VERSION=0.17 # BDB's je JAR expected to be found in lib/opt LIBS="${QPID_HOME}/lib/opt/*:${QPID_HOME}/lib/qpid-bdbstore-${VERSION}.jar:${QPID_HOME}/lib/qpid-all.jar" diff --git a/java/bdbstore/bin/storeUpgrade.sh b/java/bdbstore/bin/storeUpgrade.sh deleted file mode 100755 index 4c13f8d178..0000000000 --- a/java/bdbstore/bin/storeUpgrade.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -# Parse arguements taking all - prefixed args as JAVA_OPTS -declare -a ARGS -for arg in "$@"; do - if [[ $arg == -java:* ]]; then - JAVA_OPTS="${JAVA_OPTS}-`echo $arg|cut -d ':' -f 2` " - else - ARGS[${#ARGS[@]}]="$arg" - fi -done - -if [ -z "${QPID_HOME}" ]; then - WHEREAMI=`dirname "$0"` - export QPID_HOME=`cd ${WHEREAMI}/../ && pwd` -fi - -VERSION=0.15 - -# BDB's je JAR expected to be found in lib/opt -LIBS="$QPID_HOME/lib/opt/*:$QPID_HOME/lib/qpid-bdbstore-${VERSION}.jar:$QPID_HOME/lib/qpid-all.jar" - -java -Xms256m -Dlog4j.configuration=BDBStoreUpgrade.log4j.xml -Xmx256m -Damqj.logging.level=warn ${JAVA_OPTS} -cp "${LIBS}" org.apache.qpid.server.store.berkeleydb.BDBStoreUpgrade "${ARGS[@]}" diff --git a/java/bdbstore/build.xml b/java/bdbstore/build.xml index af7c108aa9..7e55b41b28 100644 --- a/java/bdbstore/build.xml +++ b/java/bdbstore/build.xml @@ -18,7 +18,7 @@ --> <project name="bdbstore" default="build"> <property name="module.depends" value="common broker" /> - <property name="module.test.depends" value="test client common/test broker/test management/common perftests systests" /> + <property name="module.test.depends" value="test client common/test broker/test management/common systests" /> <property name="module.genpom" value="true"/> <import file="../module.xml" /> @@ -81,4 +81,11 @@ http://www.oracle.com/technetwork/database/berkeleydb/downloads/jeoslicense-0868 <fileset dir="src/test/resources/upgrade"/> </copy> </target> + + <target name="precompile-tests"> + <mkdir dir="${module.test.resources}"/> + <copy todir="${module.test.resources}"> + <fileset dir="src/test/resources"/> + </copy> + </target> </project> diff --git a/java/bdbstore/etc/scripts/bdbtest.sh b/java/bdbstore/etc/scripts/bdbtest.sh deleted file mode 100755 index d53481c393..0000000000 --- a/java/bdbstore/etc/scripts/bdbtest.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -# - -if [ -z "$QPID_HOME" ]; then - export QPID_HOME=$(dirname $(dirname $(readlink -f $0))) - export PATH=${PATH}:${QPID_HOME}/bin -fi - -# Parse arguements taking all - prefixed args as JAVA_OPTS -for arg in "$@"; do - if [[ $arg == -java:* ]]; then - JAVA_OPTS="${JAVA_OPTS}-`echo $arg|cut -d ':' -f 2` " - else - ARGS="${ARGS}$arg " - fi -done - -VERSION=0.15 - -# Set classpath to include Qpid jar with all required jars in manifest -QPID_LIBS=$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/qpid-junit-toolkit-$VERSION.jar:$QPID_HOME/lib/junit-3.8.1.jar:$QPID_HOME/lib/log4j-1.2.12.jar:$QPID_HOME/lib/qpid-systests-$VERSION.jar:$QPID_HOME/lib/qpid-perftests-$VERSION.jar:$QPID_HOME/lib/slf4j-log4j12-1.6.1.jar - -# Set other variables used by the qpid-run script before calling -export JAVA=java JAVA_MEM=-Xmx256m QPID_CLASSPATH=$QPID_LIBS - -. qpid-run -Dlog4j.configuration=perftests.log4j -Dbadger.level=warn -Damqj.test.logging.level=warn -Damqj.logging.level=warn ${JAVA_OPTS} org.apache.qpid.ping.PingDurableClient -o $QPID_WORK/results ${ARGS} diff --git a/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml b/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml deleted file mode 100644 index 4d71963ea7..0000000000 --- a/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0"?> -<!-- - - - - Licensed to the Apache Software Foundation (ASF) under one - - or more contributor license agreements. See the NOTICE file - - distributed with this work for additional information - - regarding copyright ownership. The ASF licenses this file - - to you 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. - - - --> -<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> -<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> - - <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> - - <layout class="org.apache.log4j.PatternLayout"> - <param name="ConversionPattern" value="%d %-5p - %m%n"/> - </layout> - </appender> - - <category name="org.apache.qpid.server.store.berkeleydb.BDBStoreUpgrade"> - <priority value="info"/> - </category> - - <!-- Only show errors from the BDB Store --> - <category name="org.apache.qpid.server.store.berkeleydb.berkeleydb.BDBMessageStore"> - <priority value="error"/> - </category> - - <!-- Provide warnings to standard output --> - <category name="org.apache.qpid"> - <priority value="error"/> - </category> - - <!-- Log all info events to file --> - <root> - <priority value="info"/> - <appender-ref ref="STDOUT"/> - </root> - -</log4j:configuration> diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java index 354dba559c..2186597380 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java @@ -33,7 +33,7 @@ public class AMQShortStringEncoding public static AMQShortString readShortString(TupleInput tupleInput) { - int length = (int) tupleInput.readShort(); + int length = tupleInput.readShort(); if (length < 0) { return null; diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java new file mode 100644 index 0000000000..fb1d7c5265 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java @@ -0,0 +1,1825 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb; + +import com.sleepycat.bind.tuple.ByteBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Cursor; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.je.LockConflictException; +import com.sleepycat.je.LockMode; +import com.sleepycat.je.OperationStatus; +import com.sleepycat.je.TransactionConfig; +import java.io.File; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.federation.Bridge; +import org.apache.qpid.server.federation.BrokerLink; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler.BindingRecoveryHandler; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler.ExchangeRecoveryHandler; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler.QueueRecoveryHandler; +import org.apache.qpid.server.store.ConfiguredObjectHelper; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.Event; +import org.apache.qpid.server.store.EventListener; +import org.apache.qpid.server.store.EventManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; +import org.apache.qpid.server.store.State; +import org.apache.qpid.server.store.StateManager; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.StoredMemoryMessage; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler.QueueEntryRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.store.ConfiguredObjectRecord; +import org.apache.qpid.server.store.berkeleydb.entry.PreparedTransaction; +import org.apache.qpid.server.store.berkeleydb.entry.QueueEntryKey; +import org.apache.qpid.server.store.berkeleydb.entry.Xid; +import org.apache.qpid.server.store.berkeleydb.tuple.ConfiguredObjectBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.ContentBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.MessageMetaDataBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.PreparedTransactionBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.QueueEntryBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.StringMapBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.UUIDTupleBinding; +import org.apache.qpid.server.store.berkeleydb.tuple.XidBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.Upgrader; + +public abstract class AbstractBDBMessageStore implements MessageStore +{ + private static final Logger LOGGER = Logger.getLogger(AbstractBDBMessageStore.class); + + private static final int LOCK_RETRY_ATTEMPTS = 5; + + public static final int VERSION = 6; + + public static final String ENVIRONMENT_PATH_PROPERTY = "environment-path"; + + private Environment _environment; + + private String CONFIGURED_OBJECTS = "CONFIGURED_OBJECTS"; + private String MESSAGEMETADATADB_NAME = "MESSAGE_METADATA"; + private String MESSAGECONTENTDB_NAME = "MESSAGE_CONTENT"; + private String DELIVERYDB_NAME = "QUEUE_ENTRIES"; + private String BRIDGEDB_NAME = "BRIDGES"; + private String LINKDB_NAME = "LINKS"; + private String XIDDB_NAME = "XIDS"; + + private Database _configuredObjectsDb; + private Database _messageMetaDataDb; + private Database _messageContentDb; + private Database _deliveryDb; + private Database _bridgeDb; + private Database _linkDb; + private Database _xidDb; + + /* ======= + * Schema: + * ======= + * + * Queue: + * name(AMQShortString) - name(AMQShortString), owner(AMQShortString), + * arguments(FieldTable encoded as binary), exclusive (boolean) + * + * Exchange: + * name(AMQShortString) - name(AMQShortString), typeName(AMQShortString), autodelete (boolean) + * + * Binding: + * exchangeName(AMQShortString), queueName(AMQShortString), routingKey(AMQShortString), + * arguments (FieldTable encoded as binary) - 0 (zero) + * + * QueueEntry: + * queueName(AMQShortString), messageId (long) - 0 (zero) + * + * Message (MetaData): + * messageId (long) - bodySize (integer), metaData (MessageMetaData encoded as binary) + * + * Message (Content): + * messageId (long), byteOffset (integer) - dataLength(integer), data(binary) + */ + + private final AtomicLong _messageId = new AtomicLong(0); + + protected final StateManager _stateManager; + + protected TransactionConfig _transactionConfig = new TransactionConfig(); + + private MessageStoreRecoveryHandler _messageRecoveryHandler; + + private TransactionLogRecoveryHandler _tlogRecoveryHandler; + + private ConfigurationRecoveryHandler _configRecoveryHandler; + + private final EventManager _eventManager = new EventManager(); + private String _storeLocation; + + private ConfiguredObjectHelper _configuredObjectHelper = new ConfiguredObjectHelper(); + + public AbstractBDBMessageStore() + { + _stateManager = new StateManager(_eventManager); + } + + public void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration storeConfiguration) throws Exception + { + _stateManager.attainState(State.CONFIGURING); + + _configRecoveryHandler = recoveryHandler; + + configure(name,storeConfiguration); + + + + } + + public void configureMessageStore(String name, + MessageStoreRecoveryHandler messageRecoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler, + Configuration storeConfiguration) throws Exception + { + _messageRecoveryHandler = messageRecoveryHandler; + _tlogRecoveryHandler = tlogRecoveryHandler; + + _stateManager.attainState(State.CONFIGURED); + } + + public void activate() throws Exception + { + _stateManager.attainState(State.RECOVERING); + + recoverConfig(_configRecoveryHandler); + recoverMessages(_messageRecoveryHandler); + recoverQueueEntries(_tlogRecoveryHandler); + + _stateManager.attainState(State.ACTIVE); + } + + public org.apache.qpid.server.store.Transaction newTransaction() + { + return new BDBTransaction(); + } + + + /** + * Called after instantiation in order to configure the message store. + * + * @param name The name of the virtual host using this store + * @return whether a new store environment was created or not (to indicate whether recovery is necessary) + * + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + public void configure(String name, Configuration storeConfig) throws Exception + { + final String storeLocation = storeConfig.getString(ENVIRONMENT_PATH_PROPERTY, + System.getProperty("QPID_WORK") + File.separator + "bdbstore" + File.separator + name); + + File environmentPath = new File(storeLocation); + if (!environmentPath.exists()) + { + if (!environmentPath.mkdirs()) + { + throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. " + + "Ensure the path is correct and that the permissions are correct."); + } + } + + _storeLocation = storeLocation; + + LOGGER.info("Configuring BDB message store"); + + setupStore(environmentPath, name); + } + + /** + * Move the store state from INITIAL to ACTIVE without actually recovering. + * + * This is required if you do not want to perform recovery of the store data + * + * @throws AMQStoreException if the store is not in the correct state + */ + void startWithNoRecover() throws AMQStoreException + { + _stateManager.attainState(State.CONFIGURING); + _stateManager.attainState(State.CONFIGURED); + _stateManager.attainState(State.RECOVERING); + _stateManager.attainState(State.ACTIVE); + } + + protected void setupStore(File storePath, String name) throws DatabaseException, AMQStoreException + { + _environment = createEnvironment(storePath); + + new Upgrader(_environment, name).upgradeIfNecessary(); + + openDatabases(); + } + + protected Environment createEnvironment(File environmentPath) throws DatabaseException + { + LOGGER.info("BDB message store using environment path " + environmentPath.getAbsolutePath()); + EnvironmentConfig envConfig = new EnvironmentConfig(); + // This is what allows the creation of the store if it does not already exist. + envConfig.setAllowCreate(true); + envConfig.setTransactional(true); + envConfig.setConfigParam("je.lock.nLockTables", "7"); + + // Added to help diagnosis of Deadlock issue + // http://www.oracle.com/technology/products/berkeley-db/faq/je_faq.html#23 + if (Boolean.getBoolean("qpid.bdb.lock.debug")) + { + envConfig.setConfigParam("je.txn.deadlockStackTrace", "true"); + envConfig.setConfigParam("je.txn.dumpLocks", "true"); + } + + // Set transaction mode + _transactionConfig.setReadCommitted(true); + + //This prevents background threads running which will potentially update the store. + envConfig.setReadOnly(false); + try + { + return new Environment(environmentPath, envConfig); + } + catch (DatabaseException de) + { + if (de.getMessage().contains("Environment.setAllowCreate is false")) + { + //Allow the creation this time + envConfig.setAllowCreate(true); + if (_environment != null ) + { + _environment.cleanLog(); + _environment.close(); + } + return new Environment(environmentPath, envConfig); + } + else + { + throw de; + } + } + } + + public Environment getEnvironment() + { + return _environment; + } + + private void openDatabases() throws DatabaseException + { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + + //This is required if we are wanting read only access. + dbConfig.setReadOnly(false); + + _configuredObjectsDb = openDatabase(CONFIGURED_OBJECTS, dbConfig); + _messageMetaDataDb = openDatabase(MESSAGEMETADATADB_NAME, dbConfig); + _messageContentDb = openDatabase(MESSAGECONTENTDB_NAME, dbConfig); + _deliveryDb = openDatabase(DELIVERYDB_NAME, dbConfig); + _linkDb = openDatabase(LINKDB_NAME, dbConfig); + _bridgeDb = openDatabase(BRIDGEDB_NAME, dbConfig); + _xidDb = openDatabase(XIDDB_NAME, dbConfig); + } + + private Database openDatabase(final String dbName, final DatabaseConfig dbConfig) + { + // if opening read-only and the database doesn't exist, then you can't create it + return dbConfig.getReadOnly() && !_environment.getDatabaseNames().contains(dbName) + ? null + : _environment.openDatabase(null, dbName, dbConfig); + } + + /** + * Called to close and cleanup any resources used by the message store. + * + * @throws Exception If the close fails. + */ + public void close() throws Exception + { + if (_stateManager.isInState(State.ACTIVE) || _stateManager.isInState(State.QUIESCED)) + { + _stateManager.stateTransition(State.ACTIVE, State.CLOSING); + + closeInternal(); + + _stateManager.stateTransition(State.CLOSING, State.CLOSED); + } + } + + protected void closeInternal() throws Exception + { + if (_messageMetaDataDb != null) + { + LOGGER.info("Closing message metadata database"); + _messageMetaDataDb.close(); + } + + if (_messageContentDb != null) + { + LOGGER.info("Closing message content database"); + _messageContentDb.close(); + } + + if (_configuredObjectsDb != null) + { + LOGGER.info("Closing configurable objects database"); + _configuredObjectsDb.close(); + } + + if (_deliveryDb != null) + { + LOGGER.info("Close delivery database"); + _deliveryDb.close(); + } + + if (_bridgeDb != null) + { + LOGGER.info("Close bridge database"); + _bridgeDb.close(); + } + + if (_linkDb != null) + { + LOGGER.info("Close link database"); + _linkDb.close(); + } + + + if (_xidDb != null) + { + LOGGER.info("Close xid database"); + _xidDb.close(); + } + + closeEnvironment(); + + } + + private void closeEnvironment() throws DatabaseException + { + if (_environment != null) + { + // Clean the log before closing. This makes sure it doesn't contain + // redundant data. Closing without doing this means the cleaner may not + // get a chance to finish. + _environment.cleanLog(); + _environment.close(); + } + } + + + private void recoverConfig(ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException + { + try + { + List<ConfiguredObjectRecord> configuredObjects = loadConfiguredObjects(); + QueueRecoveryHandler qrh = recoveryHandler.begin(this); + _configuredObjectHelper.recoverQueues(qrh, configuredObjects); + + ExchangeRecoveryHandler erh = qrh.completeQueueRecovery(); + _configuredObjectHelper.recoverExchanges(erh, configuredObjects); + + BindingRecoveryHandler brh = erh.completeExchangeRecovery(); + _configuredObjectHelper.recoverBindings(brh, configuredObjects); + + ConfigurationRecoveryHandler.BrokerLinkRecoveryHandler lrh = brh.completeBindingRecovery(); + recoverBrokerLinks(lrh); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + + } + + private List<ConfiguredObjectRecord> loadConfiguredObjects() throws DatabaseException + { + Cursor cursor = null; + List<ConfiguredObjectRecord> results = new ArrayList<ConfiguredObjectRecord>(); + try + { + cursor = _configuredObjectsDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + ConfiguredObjectRecord configuredObject = ConfiguredObjectBinding.getInstance().entryToObject(value); + UUID id = UUIDTupleBinding.getInstance().entryToObject(key); + configuredObject.setId(id); + results.add(configuredObject); + } + + } + finally + { + closeCursorSafely(cursor); + } + return results; + } + + private void closeCursorSafely(Cursor cursor) + { + if (cursor != null) + { + cursor.close(); + } + } + + private void recoverBrokerLinks(final ConfigurationRecoveryHandler.BrokerLinkRecoveryHandler lrh) + { + Cursor cursor = null; + + try + { + cursor = _linkDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + UUID id = UUIDTupleBinding.getInstance().entryToObject(key); + long createTime = LongBinding.entryToLong(value); + Map<String,String> arguments = StringMapBinding.getInstance().entryToObject(value); + + ConfigurationRecoveryHandler.BridgeRecoveryHandler brh = lrh.brokerLink(id, createTime, arguments); + + recoverBridges(brh, id); + } + } + finally + { + closeCursorSafely(cursor); + } + + } + + private void recoverBridges(final ConfigurationRecoveryHandler.BridgeRecoveryHandler brh, final UUID linkId) + { + Cursor cursor = null; + + try + { + cursor = _bridgeDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + UUID id = UUIDTupleBinding.getInstance().entryToObject(key); + + UUID parentId = UUIDTupleBinding.getInstance().entryToObject(value); + if(parentId.equals(linkId)) + { + + long createTime = LongBinding.entryToLong(value); + Map<String,String> arguments = StringMapBinding.getInstance().entryToObject(value); + brh.bridge(id,createTime,arguments); + } + } + brh.completeBridgeRecoveryForLink(); + } + finally + { + closeCursorSafely(cursor); + } + + } + + + private void recoverMessages(MessageStoreRecoveryHandler msrh) throws DatabaseException + { + StoredMessageRecoveryHandler mrh = msrh.begin(); + + Cursor cursor = null; + try + { + cursor = _messageMetaDataDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + MessageMetaDataBinding valueBinding = MessageMetaDataBinding.getInstance(); + + long maxId = 0; + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + long messageId = LongBinding.entryToLong(key); + StorableMessageMetaData metaData = valueBinding.entryToObject(value); + + StoredBDBMessage message = new StoredBDBMessage(messageId, metaData, false); + mrh.message(message); + + maxId = Math.max(maxId, messageId); + } + + _messageId.set(maxId); + } + catch (DatabaseException e) + { + LOGGER.error("Database Error: " + e.getMessage(), e); + throw e; + } + finally + { + closeCursorSafely(cursor); + } + } + + private void recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) + throws DatabaseException + { + QueueEntryRecoveryHandler qerh = recoveryHandler.begin(this); + + ArrayList<QueueEntryKey> entries = new ArrayList<QueueEntryKey>(); + + Cursor cursor = null; + try + { + cursor = _deliveryDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + QueueEntryKey qek = keyBinding.entryToObject(key); + + entries.add(qek); + } + + try + { + cursor.close(); + } + finally + { + cursor = null; + } + + for(QueueEntryKey entry : entries) + { + UUID queueId = entry.getQueueId(); + long messageId = entry.getMessageId(); + qerh.queueEntry(queueId, messageId); + } + } + catch (DatabaseException e) + { + LOGGER.error("Database Error: " + e.getMessage(), e); + throw e; + } + finally + { + closeCursorSafely(cursor); + } + + TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh = qerh.completeQueueEntryRecovery(); + + cursor = null; + try + { + cursor = _xidDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + XidBinding keyBinding = XidBinding.getInstance(); + PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); + DatabaseEntry value = new DatabaseEntry(); + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + Xid xid = keyBinding.entryToObject(key); + PreparedTransaction preparedTransaction = valueBinding.entryToObject(value); + dtxrh.dtxRecord(xid.getFormat(),xid.getGlobalId(),xid.getBranchId(), + preparedTransaction.getEnqueues(),preparedTransaction.getDequeues()); + } + + } + catch (DatabaseException e) + { + LOGGER.error("Database Error: " + e.getMessage(), e); + throw e; + } + finally + { + closeCursorSafely(cursor); + } + + + dtxrh.completeDtxRecordRecovery(); + } + + public void removeMessage(long messageId, boolean sync) throws AMQStoreException + { + + boolean complete = false; + com.sleepycat.je.Transaction tx = null; + + Random rand = null; + int attempts = 0; + try + { + do + { + tx = null; + try + { + tx = _environment.beginTransaction(null, null); + + //remove the message meta data from the store + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Removing message id " + messageId); + } + + + OperationStatus status = _messageMetaDataDb.delete(tx, key); + if (status == OperationStatus.NOTFOUND) + { + LOGGER.info("Message not found (attempt to remove failed - probably application initiated rollback) " + + messageId); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleted metadata for message " + messageId); + } + + //now remove the content data from the store if there is any. + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + LongBinding.longToEntry(messageId, contentKeyEntry); + _messageContentDb.delete(tx, contentKeyEntry); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleted content for message " + messageId); + } + + commit(tx, sync); + complete = true; + tx = null; + } + catch (LockConflictException e) + { + try + { + if(tx != null) + { + tx.abort(); + } + } + catch(DatabaseException e2) + { + LOGGER.warn("Unable to abort transaction after LockConflictExcption", e2); + // rethrow the original log conflict exception, the secondary exception should already have + // been logged. + throw e; + } + + + LOGGER.warn("Lock timeout exception. Retrying (attempt " + + (attempts+1) + " of "+ LOCK_RETRY_ATTEMPTS +") " + e); + + if(++attempts < LOCK_RETRY_ATTEMPTS) + { + if(rand == null) + { + rand = new Random(); + } + + try + { + Thread.sleep(500l + (long)(500l * rand.nextDouble())); + } + catch (InterruptedException e1) + { + + } + } + else + { + // rethrow the lock conflict exception since we could not solve by retrying + throw e; + } + } + } + while(!complete); + } + catch (DatabaseException e) + { + LOGGER.error("Unexpected BDB exception", e); + + if (tx != null) + { + try + { + tx.abort(); + tx = null; + } + catch (DatabaseException e1) + { + throw new AMQStoreException("Error aborting transaction " + e1, e1); + } + } + + throw new AMQStoreException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); + } + finally + { + if (tx != null) + { + try + { + tx.abort(); + tx = null; + } + catch (DatabaseException e1) + { + throw new AMQStoreException("Error aborting transaction " + e1, e1); + } + } + } + } + + /** + * @see DurableConfigurationStore#createExchange(Exchange) + */ + public void createExchange(Exchange exchange) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + ConfiguredObjectRecord configuredObject = _configuredObjectHelper.createExchangeConfiguredObject(exchange); + storeConfiguredObjectEntry(configuredObject); + } + } + + /** + * @see DurableConfigurationStore#removeExchange(Exchange) + */ + public void removeExchange(Exchange exchange) throws AMQStoreException + { + UUID id = exchange.getId(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void removeExchange(String name = " + exchange.getName() + ", uuid = " + id + "): called"); + } + OperationStatus status = removeConfiguredObject(id); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Exchange " + exchange.getName() + " with id " + id + " not found"); + } + } + + + /** + * @see DurableConfigurationStore#bindQueue(Binding) + */ + public void bindQueue(Binding binding) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + ConfiguredObjectRecord configuredObject = _configuredObjectHelper.createBindingConfiguredObject(binding); + storeConfiguredObjectEntry(configuredObject); + } + } + + /** + * @see DurableConfigurationStore#unbindQueue(Binding) + */ + public void unbindQueue(Binding binding) + throws AMQStoreException + { + UUID id = binding.getId(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void unbindQueue(Binding binding = " + binding + ", uuid = " + id + "): called"); + } + + OperationStatus status = removeConfiguredObject(id); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Binding " + binding + " not found"); + } + } + + /** + * @see DurableConfigurationStore#createQueue(AMQQueue) + */ + public void createQueue(AMQQueue queue) throws AMQStoreException + { + createQueue(queue, null); + } + + /** + * @see DurableConfigurationStore#createQueue(AMQQueue, FieldTable) + */ + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void createQueue(AMQQueue queue(" + queue.getName() + "), queue id" + queue.getId() + + ", arguments=" + arguments + "): called"); + } + ConfiguredObjectRecord configuredObject = _configuredObjectHelper.createQueueConfiguredObject(queue, arguments); + storeConfiguredObjectEntry(configuredObject); + } + } + + /** + * Updates the specified queue in the persistent store, IF it is already present. If the queue + * is not present in the store, it will not be added. + * + * @param queue The queue to update the entry for. + * @throws AMQStoreException If the operation fails for any reason. + */ + public void updateQueue(final AMQQueue queue) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Updating queue: " + queue.getName()); + } + + try + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); + keyBinding.objectToEntry(queue.getId(), key); + + DatabaseEntry value = new DatabaseEntry(); + DatabaseEntry newValue = new DatabaseEntry(); + ConfiguredObjectBinding configuredObjectBinding = ConfiguredObjectBinding.getInstance(); + + OperationStatus status = _configuredObjectsDb.get(null, key, value, LockMode.DEFAULT); + if (status == OperationStatus.SUCCESS) + { + ConfiguredObjectRecord queueRecord = configuredObjectBinding.entryToObject(value); + ConfiguredObjectRecord newQueueRecord = _configuredObjectHelper.updateQueueConfiguredObject(queue, queueRecord); + + // write the updated entry to the store + configuredObjectBinding.objectToEntry(newQueueRecord, newValue); + status = _configuredObjectsDb.put(null, key, newValue); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error updating queue details within the store: " + status); + } + } + else if (status != OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Error finding queue details within the store: " + status); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error updating queue details within the store: " + e,e); + } + } + + /** + * Removes the specified queue from the persistent store. + * + * @param queue The queue to remove. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + public void removeQueue(final AMQQueue queue) throws AMQStoreException + { + UUID id = queue.getId(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void removeQueue(AMQShortString name = " + queue.getName() + ", uuid = " + id + "): called"); + } + + OperationStatus status = removeConfiguredObject(id); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Queue " + queue.getName() + " with id " + id + " not found"); + } + } + + public void createBrokerLink(final BrokerLink link) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding.getInstance().objectToEntry(link.getId(), key); + + DatabaseEntry value = new DatabaseEntry(); + LongBinding.longToEntry(link.getCreateTime(),value); + StringMapBinding.getInstance().objectToEntry(link.getArguments(), value); + + try + { + _linkDb.put(null, key, value); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error writing Link " + link + + " to database: " + e.getMessage(), e); + } + } + } + + public void deleteBrokerLink(final BrokerLink link) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding.getInstance().objectToEntry(link.getId(), key); + try + { + OperationStatus status = _linkDb.delete(null, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Link " + link + " not found"); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error deleting the Link " + link + " from database: " + e.getMessage(), e); + } + } + + public void createBridge(final Bridge bridge) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding.getInstance().objectToEntry(bridge.getId(), key); + + DatabaseEntry value = new DatabaseEntry(); + UUIDTupleBinding.getInstance().objectToEntry(bridge.getLink().getId(),value); + LongBinding.longToEntry(bridge.getCreateTime(),value); + StringMapBinding.getInstance().objectToEntry(bridge.getArguments(), value); + + try + { + _bridgeDb.put(null, key, value); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error writing Bridge " + bridge + + " to database: " + e.getMessage(), e); + } + + } + } + + public void deleteBridge(final Bridge bridge) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding.getInstance().objectToEntry(bridge.getId(), key); + try + { + OperationStatus status = _bridgeDb.delete(null, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Bridge " + bridge + " not found"); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error deleting the Bridge " + bridge + " from database: " + e.getMessage(), e); + } + } + + /** + * Places a message onto a specified queue, in a given transaction. + * + * @param tx The transaction for the operation. + * @param queue The the queue to place the message on. + * @param messageId The message to enqueue. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + public void enqueueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, + long messageId) throws AMQStoreException + { + + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + QueueEntryKey dd = new QueueEntryKey(queue.getId(), messageId); + keyBinding.objectToEntry(dd, key); + DatabaseEntry value = new DatabaseEntry(); + ByteBinding.byteToEntry((byte) 0, value); + + try + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Enqueuing message " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() + + " [Transaction" + tx + "]"); + } + _deliveryDb.put(tx, key, value); + } + catch (DatabaseException e) + { + LOGGER.error("Failed to enqueue: " + e.getMessage(), e); + throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + queue.getId() + + " to database", e); + } + } + + /** + * Extracts a message from a specified queue, in a given transaction. + * + * @param tx The transaction for the operation. + * @param queue The queue to take the message from. + * @param messageId The message to dequeue. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public void dequeueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, + long messageId) throws AMQStoreException + { + + DatabaseEntry key = new DatabaseEntry(); + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + QueueEntryKey queueEntryKey = new QueueEntryKey(queue.getId(), messageId); + UUID id = queue.getId(); + keyBinding.objectToEntry(queueEntryKey, key); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Dequeue message id " + messageId + " from queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + + try + { + + OperationStatus status = _deliveryDb.delete(tx, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + else if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Unable to remove message with id " + messageId + " on queue" + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Removed message " + messageId + " on queue " + + (queue instanceof AMQQueue ? ((AMQQueue) queue).getName() + " with id " : "") + id + + " from delivery db"); + + } + } + catch (DatabaseException e) + { + + LOGGER.error("Failed to dequeue message " + messageId + ": " + e.getMessage(), e); + LOGGER.error(tx); + + throw new AMQStoreException("Error accessing database while dequeuing message: " + e.getMessage(), e); + } + } + + + private void recordXid(com.sleepycat.je.Transaction txn, + long format, + byte[] globalId, + byte[] branchId, + org.apache.qpid.server.store.Transaction.Record[] enqueues, + org.apache.qpid.server.store.Transaction.Record[] dequeues) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + Xid xid = new Xid(format, globalId, branchId); + XidBinding keyBinding = XidBinding.getInstance(); + keyBinding.objectToEntry(xid,key); + + DatabaseEntry value = new DatabaseEntry(); + PreparedTransaction preparedTransaction = new PreparedTransaction(enqueues, dequeues); + PreparedTransactionBinding valueBinding = new PreparedTransactionBinding(); + valueBinding.objectToEntry(preparedTransaction, value); + + try + { + _xidDb.put(txn, key, value); + } + catch (DatabaseException e) + { + LOGGER.error("Failed to write xid: " + e.getMessage(), e); + throw new AMQStoreException("Error writing xid to database", e); + } + } + + private void removeXid(com.sleepycat.je.Transaction txn, long format, byte[] globalId, byte[] branchId) + throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + Xid xid = new Xid(format, globalId, branchId); + XidBinding keyBinding = XidBinding.getInstance(); + + keyBinding.objectToEntry(xid, key); + + + try + { + + OperationStatus status = _xidDb.delete(txn, key); + if (status == OperationStatus.NOTFOUND) + { + throw new AMQStoreException("Unable to find xid"); + } + else if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Unable to remove xid"); + } + + } + catch (DatabaseException e) + { + + LOGGER.error("Failed to remove xid ", e); + LOGGER.error(txn); + + throw new AMQStoreException("Error accessing database while removing xid: " + e.getMessage(), e); + } + } + + /** + * Commits all operations performed within a given transaction. + * + * @param tx The transaction to commit all operations for. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + private StoreFuture commitTranImpl(final com.sleepycat.je.Transaction tx, boolean syncCommit) throws AMQStoreException + { + if (tx == null) + { + throw new AMQStoreException("Fatal internal error: transactional is null at commitTran"); + } + + StoreFuture result; + try + { + result = commit(tx, syncCommit); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("commitTranImpl completed for [Transaction:" + tx + "]"); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error commit tx: " + e.getMessage(), e); + } + + return result; + } + + /** + * Abandons all operations performed within a given transaction. + * + * @param tx The transaction to abandon. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + public void abortTran(final com.sleepycat.je.Transaction tx) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("abortTran called for [Transaction:" + tx + "]"); + } + + try + { + tx.abort(); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e); + } + } + + /** + * Primarily for testing purposes. + * + * @param queueName + * + * @return a list of message ids for messages enqueued for a particular queue + */ + List<Long> getEnqueuedMessages(UUID queueId) throws AMQStoreException + { + Cursor cursor = null; + try + { + cursor = _deliveryDb.openCursor(null, null); + + DatabaseEntry key = new DatabaseEntry(); + + QueueEntryKey dd = new QueueEntryKey(queueId, 0); + + QueueEntryBinding keyBinding = QueueEntryBinding.getInstance(); + keyBinding.objectToEntry(dd, key); + + DatabaseEntry value = new DatabaseEntry(); + + LinkedList<Long> messageIds = new LinkedList<Long>(); + + OperationStatus status = cursor.getSearchKeyRange(key, value, LockMode.DEFAULT); + dd = keyBinding.entryToObject(key); + + while ((status == OperationStatus.SUCCESS) && dd.getQueueId().equals(queueId)) + { + + messageIds.add(dd.getMessageId()); + status = cursor.getNext(key, value, LockMode.DEFAULT); + if (status == OperationStatus.SUCCESS) + { + dd = keyBinding.entryToObject(key); + } + } + + return messageIds; + } + catch (DatabaseException e) + { + throw new AMQStoreException("Database error: " + e.getMessage(), e); + } + finally + { + if (cursor != null) + { + try + { + cursor.close(); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error closing cursor: " + e.getMessage(), e); + } + } + } + } + + /** + * Return a valid, currently unused message id. + * + * @return A fresh message id. + */ + public long getNewMessageId() + { + return _messageId.incrementAndGet(); + } + + /** + * Stores a chunk of message data. + * + * @param tx The transaction for the operation. + * @param messageId The message to store the data for. + * @param offset The offset of the data chunk in the message. + * @param contentBody The content of the data chunk. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + protected void addContent(final com.sleepycat.je.Transaction tx, long messageId, int offset, + ByteBuffer contentBody) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + ContentBinding messageBinding = ContentBinding.getInstance(); + messageBinding.objectToEntry(contentBody.array(), value); + try + { + OperationStatus status = _messageContentDb.put(tx, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error adding content for message id " + messageId + ": " + status); + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Storing content for message " + messageId + "[Transaction" + tx + "]"); + + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); + } + } + + /** + * Stores message meta-data. + * + * @param tx The transaction for the operation. + * @param messageId The message to store the data for. + * @param messageMetaData The message meta data to store. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + private void storeMetaData(final com.sleepycat.je.Transaction tx, long messageId, + StorableMessageMetaData messageMetaData) + throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public void storeMetaData(Txn tx = " + tx + ", Long messageId = " + + messageId + ", MessageMetaData messageMetaData = " + messageMetaData + "): called"); + } + + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + + MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); + messageBinding.objectToEntry(messageMetaData, value); + try + { + _messageMetaDataDb.put(tx, key, value); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Storing message metadata for message id " + messageId + "[Transaction" + tx + "]"); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error writing message metadata with id " + messageId + " to database: " + e.getMessage(), e); + } + } + + /** + * Retrieves message meta-data. + * + * @param messageId The message to get the meta-data for. + * + * @return The message meta data. + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public StorableMessageMetaData getMessageMetaData(long messageId) throws AMQStoreException + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("public MessageMetaData getMessageMetaData(Long messageId = " + + messageId + "): called"); + } + + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + DatabaseEntry value = new DatabaseEntry(); + MessageMetaDataBinding messageBinding = MessageMetaDataBinding.getInstance(); + + try + { + OperationStatus status = _messageMetaDataDb.get(null, key, value, LockMode.READ_UNCOMMITTED); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Metadata not found for message with id " + messageId); + } + + StorableMessageMetaData mdd = messageBinding.entryToObject(value); + + return mdd; + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error reading message metadata for message with id " + messageId + ": " + e.getMessage(), e); + } + } + + /** + * Fills the provided ByteBuffer with as much content for the specified message as possible, starting + * from the specified offset in the message. + * + * @param messageId The message to get the data for. + * @param offset The offset of the data within the message. + * @param dst The destination of the content read back + * + * @return The number of bytes inserted into the destination + * + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + public int getContent(long messageId, int offset, ByteBuffer dst) throws AMQStoreException + { + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + LongBinding.longToEntry(messageId, contentKeyEntry); + DatabaseEntry value = new DatabaseEntry(); + ContentBinding contentTupleBinding = ContentBinding.getInstance(); + + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Message Id: " + messageId + " Getting content body from offset: " + offset); + } + + try + { + + int written = 0; + OperationStatus status = _messageContentDb.get(null, contentKeyEntry, value, LockMode.READ_UNCOMMITTED); + if (status == OperationStatus.SUCCESS) + { + byte[] dataAsBytes = contentTupleBinding.entryToObject(value); + int size = dataAsBytes.length; + if (offset > size) + { + throw new RuntimeException("Offset " + offset + " is greater than message size " + size + + " for message id " + messageId + "!"); + + } + + written = size - offset; + if(written > dst.remaining()) + { + written = dst.remaining(); + } + + dst.put(dataAsBytes, offset, written); + } + return written; + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error getting AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); + } + } + + public boolean isPersistent() + { + return true; + } + + public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData) + { + if(metaData.isPersistent()) + { + return (StoredMessage<T>) new StoredBDBMessage(getNewMessageId(), metaData); + } + else + { + return new StoredMemoryMessage(getNewMessageId(), metaData); + } + } + + //Package getters for the various databases used by the Store + + Database getMetaDataDb() + { + return _messageMetaDataDb; + } + + Database getContentDb() + { + return _messageContentDb; + } + + Database getDeliveryDb() + { + return _deliveryDb; + } + + /** + * Makes the specified configured object persistent. + * + * @param configuredObject Details of the configured object to store. + * @throws AMQStoreException If the operation fails for any reason. + */ + private void storeConfiguredObjectEntry(ConfiguredObjectRecord configuredObject) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding keyBinding = UUIDTupleBinding.getInstance(); + keyBinding.objectToEntry(configuredObject.getId(), key); + + DatabaseEntry value = new DatabaseEntry(); + ConfiguredObjectBinding queueBinding = ConfiguredObjectBinding.getInstance(); + + queueBinding.objectToEntry(configuredObject, value); + try + { + OperationStatus status = _configuredObjectsDb.put(null, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new AMQStoreException("Error writing configured object " + configuredObject + " to database: " + + status); + } + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error writing configured object " + configuredObject + + " to database: " + e.getMessage(), e); + } + } + } + + private OperationStatus removeConfiguredObject(UUID id) throws AMQStoreException + { + DatabaseEntry key = new DatabaseEntry(); + UUIDTupleBinding uuidBinding = UUIDTupleBinding.getInstance(); + uuidBinding.objectToEntry(id, key); + try + { + return _configuredObjectsDb.delete(null, key); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error deleting of configured object with id " + id + " from database", e); + } + } + + protected abstract StoreFuture commit(com.sleepycat.je.Transaction tx, boolean syncCommit) throws DatabaseException; + + + private class StoredBDBMessage implements StoredMessage<StorableMessageMetaData> + { + + private final long _messageId; + private volatile SoftReference<StorableMessageMetaData> _metaDataRef; + + private StorableMessageMetaData _metaData; + private volatile SoftReference<byte[]> _dataRef; + private byte[] _data; + + StoredBDBMessage(long messageId, StorableMessageMetaData metaData) + { + this(messageId, metaData, true); + } + + + StoredBDBMessage(long messageId, + StorableMessageMetaData metaData, boolean persist) + { + try + { + _messageId = messageId; + _metaData = metaData; + + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + + } + catch (DatabaseException e) + { + throw new RuntimeException(e); + } + + } + + public StorableMessageMetaData getMetaData() + { + StorableMessageMetaData metaData = _metaDataRef.get(); + if(metaData == null) + { + try + { + metaData = AbstractBDBMessageStore.this.getMessageMetaData(_messageId); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + } + + return metaData; + } + + public long getMessageNumber() + { + return _messageId; + } + + public void addContent(int offsetInMessage, java.nio.ByteBuffer src) + { + src = src.slice(); + + if(_data == null) + { + _data = new byte[src.remaining()]; + _dataRef = new SoftReference<byte[]>(_data); + src.duplicate().get(_data); + } + else + { + byte[] oldData = _data; + _data = new byte[oldData.length + src.remaining()]; + _dataRef = new SoftReference<byte[]>(_data); + + System.arraycopy(oldData,0,_data,0,oldData.length); + src.duplicate().get(_data, oldData.length, src.remaining()); + } + + } + + public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) + { + byte[] data = _dataRef == null ? null : _dataRef.get(); + if(data != null) + { + int length = Math.min(dst.remaining(), data.length - offsetInMessage); + dst.put(data, offsetInMessage, length); + return length; + } + else + { + try + { + return AbstractBDBMessageStore.this.getContent(_messageId, offsetInMessage, dst); + } + catch (AMQStoreException e) + { + // TODO maybe should throw a checked exception, or at least log before throwing + throw new RuntimeException(e); + } + } + } + + public ByteBuffer getContent(int offsetInMessage, int size) + { + byte[] data = _dataRef == null ? null : _dataRef.get(); + if(data != null) + { + return ByteBuffer.wrap(data,offsetInMessage,size); + } + else + { + ByteBuffer buf = ByteBuffer.allocate(size); + getContent(offsetInMessage, buf); + buf.position(0); + return buf; + } + } + + synchronized void store(com.sleepycat.je.Transaction txn) + { + + if(_metaData != null) + { + try + { + _dataRef = new SoftReference<byte[]>(_data); + AbstractBDBMessageStore.this.storeMetaData(txn, _messageId, _metaData); + AbstractBDBMessageStore.this.addContent(txn, _messageId, 0, + _data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data)); + } + catch(DatabaseException e) + { + throw new RuntimeException(e); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + catch (RuntimeException e) + { + LOGGER.error("RuntimeException during store", e); + throw e; + } + finally + { + _metaData = null; + _data = null; + } + } + } + + public synchronized StoreFuture flushToStore() + { + if(_metaData != null) + { + com.sleepycat.je.Transaction txn = _environment.beginTransaction(null, null); + store(txn); + AbstractBDBMessageStore.this.commit(txn,true); + + } + return StoreFuture.IMMEDIATE_FUTURE; + } + + public void remove() + { + try + { + AbstractBDBMessageStore.this.removeMessage(_messageId, false); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + } + + private class BDBTransaction implements org.apache.qpid.server.store.Transaction + { + private com.sleepycat.je.Transaction _txn; + + private BDBTransaction() + { + try + { + _txn = _environment.beginTransaction(null, null); + } + catch (DatabaseException e) + { + throw new RuntimeException(e); + } + } + + public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + if(message.getStoredMessage() instanceof StoredBDBMessage) + { + ((StoredBDBMessage)message.getStoredMessage()).store(_txn); + } + + AbstractBDBMessageStore.this.enqueueMessage(_txn, queue, message.getMessageNumber()); + } + + public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + AbstractBDBMessageStore.this.dequeueMessage(_txn, queue, message.getMessageNumber()); + } + + public void commitTran() throws AMQStoreException + { + AbstractBDBMessageStore.this.commitTranImpl(_txn, true); + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + return AbstractBDBMessageStore.this.commitTranImpl(_txn, false); + } + + public void abortTran() throws AMQStoreException + { + AbstractBDBMessageStore.this.abortTran(_txn); + } + + public void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException + { + AbstractBDBMessageStore.this.removeXid(_txn, format, globalId, branchId); + } + + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, + Record[] dequeues) throws AMQStoreException + { + AbstractBDBMessageStore.this.recordXid(_txn, format, globalId, branchId, enqueues, dequeues); + } + } + + @Override + public void addEventListener(EventListener eventListener, Event... events) + { + _eventManager.addEventListener(eventListener, events); + } + + @Override + public String getStoreLocation() + { + return _storeLocation; + } +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java index 29f2a2f2fb..9f7eb4bfd9 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java @@ -20,76 +20,20 @@ */ package org.apache.qpid.server.store.berkeleydb; -import com.sleepycat.bind.EntryBinding; -import com.sleepycat.bind.tuple.ByteBinding; -import com.sleepycat.bind.tuple.IntegerBinding; -import com.sleepycat.bind.tuple.LongBinding; -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.je.CheckpointConfig; -import com.sleepycat.je.Cursor; -import com.sleepycat.je.Database; -import com.sleepycat.je.DatabaseConfig; -import com.sleepycat.je.DatabaseEntry; -import com.sleepycat.je.DatabaseException; -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; -import com.sleepycat.je.LockConflictException; -import com.sleepycat.je.LockMode; -import com.sleepycat.je.OperationStatus; -import com.sleepycat.je.TransactionConfig; -import org.apache.commons.configuration.Configuration; -import org.apache.log4j.Logger; - -import org.apache.qpid.AMQStoreException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.exchange.Exchange; -import org.apache.qpid.server.federation.Bridge; -import org.apache.qpid.server.federation.BrokerLink; -import org.apache.qpid.server.logging.LogSubject; -import org.apache.qpid.server.logging.actors.CurrentActor; -import org.apache.qpid.server.logging.messages.ConfigStoreMessages; -import org.apache.qpid.server.logging.messages.MessageStoreMessages; -import org.apache.qpid.server.logging.messages.TransactionLogMessages; -import org.apache.qpid.server.message.EnqueableMessage; -import org.apache.qpid.server.queue.AMQQueue; -import org.apache.qpid.server.store.ConfigurationRecoveryHandler; -import org.apache.qpid.server.store.ConfigurationRecoveryHandler.BindingRecoveryHandler; -import org.apache.qpid.server.store.ConfigurationRecoveryHandler.ExchangeRecoveryHandler; -import org.apache.qpid.server.store.ConfigurationRecoveryHandler.QueueRecoveryHandler; -import org.apache.qpid.server.store.DurableConfigurationStore; -import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.store.MessageStoreRecoveryHandler; -import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; -import org.apache.qpid.server.store.StorableMessageMetaData; -import org.apache.qpid.server.store.StoredMemoryMessage; -import org.apache.qpid.server.store.StoredMessage; -import org.apache.qpid.server.store.TransactionLogRecoveryHandler; -import org.apache.qpid.server.store.TransactionLogRecoveryHandler.QueueEntryRecoveryHandler; -import org.apache.qpid.server.store.TransactionLogResource; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_5; -import org.apache.qpid.server.store.berkeleydb.records.BindingRecord; -import org.apache.qpid.server.store.berkeleydb.records.ExchangeRecord; -import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; -import org.apache.qpid.server.store.berkeleydb.tuples.BindingTupleBindingFactory; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageContentKeyTB_5; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageMetaDataTupleBindingFactory; -import org.apache.qpid.server.store.berkeleydb.tuples.QueueEntryTB; -import org.apache.qpid.server.store.berkeleydb.tuples.QueueTupleBindingFactory; - import java.io.File; -import java.lang.ref.SoftReference; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import java.util.Queue; -import java.util.Random; -import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; + +import com.sleepycat.je.CheckpointConfig; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; /** * BDBMessageStore implements a persistent {@link MessageStore} using the BDB high performance log. @@ -99,331 +43,23 @@ import java.util.concurrent.atomic.AtomicLong; * exchanges. <tr><td> Store and remove messages. <tr><td> Bind and unbind queues to exchanges. <tr><td> Enqueue and * dequeue messages to queues. <tr><td> Generate message identifiers. </table> */ -@SuppressWarnings({"unchecked"}) -public class BDBMessageStore implements MessageStore, DurableConfigurationStore +public class BDBMessageStore extends AbstractBDBMessageStore { - private static final Logger _log = Logger.getLogger(BDBMessageStore.class); - - private static final int LOCK_RETRY_ATTEMPTS = 5; - - static final int DATABASE_FORMAT_VERSION = 5; - private static final String DATABASE_FORMAT_VERSION_PROPERTY = "version"; - public static final String ENVIRONMENT_PATH_PROPERTY = "environment-path"; - - private Environment _environment; - - private String MESSAGEMETADATADB_NAME = "messageMetaDataDb"; - private String MESSAGECONTENTDB_NAME = "messageContentDb"; - private String QUEUEBINDINGSDB_NAME = "queueBindingsDb"; - private String DELIVERYDB_NAME = "deliveryDb"; - private String EXCHANGEDB_NAME = "exchangeDb"; - private String QUEUEDB_NAME = "queueDb"; - private String BRIDGEDB_NAME = "bridges"; - private String LINKDB_NAME = "links"; - - private Database _messageMetaDataDb; - private Database _messageContentDb; - private Database _queueBindingsDb; - private Database _deliveryDb; - private Database _exchangeDb; - private Database _queueDb; - private Database _bridgeDb; - private Database _linkDb; - - /* ======= - * Schema: - * ======= - * - * Queue: - * name(AMQShortString) - name(AMQShortString), owner(AMQShortString), - * arguments(FieldTable encoded as binary), exclusive (boolean) - * - * Exchange: - * name(AMQShortString) - name(AMQShortString), typeName(AMQShortString), autodelete (boolean) - * - * Binding: - * exchangeName(AMQShortString), queueName(AMQShortString), routingKey(AMQShortString), - * arguments (FieldTable encoded as binary) - 0 (zero) - * - * QueueEntry: - * queueName(AMQShortString), messageId (long) - 0 (zero) - * - * Message (MetaData): - * messageId (long) - bodySize (integer), metaData (MessageMetaData encoded as binary) - * - * Message (Content): - * messageId (long), byteOffset (integer) - dataLength(integer), data(binary) - */ - - private LogSubject _logSubject; - - private final AtomicLong _messageId = new AtomicLong(0); + private static final Logger LOGGER = Logger.getLogger(BDBMessageStore.class); private final CommitThread _commitThread = new CommitThread("Commit-Thread"); - // Factory Classes to create the TupleBinding objects that reflect the version instance of this BDBStore - private MessageMetaDataTupleBindingFactory _metaDataTupleBindingFactory; - private QueueTupleBindingFactory _queueTupleBindingFactory; - private BindingTupleBindingFactory _bindingTupleBindingFactory; - - /** The data version this store should run with */ - private int _version; - private enum State - { - INITIAL, - CONFIGURING, - CONFIGURED, - RECOVERING, - STARTED, - CLOSING, - CLOSED - } - - private State _state = State.INITIAL; - - private TransactionConfig _transactionConfig = new TransactionConfig(); - - private boolean _readOnly = false; - - private boolean _configured; - - - public BDBMessageStore() - { - this(DATABASE_FORMAT_VERSION); - } - - public BDBMessageStore(int version) - { - _version = version; - } - - private void setDatabaseNames(int version) - { - if (version > 1) - { - MESSAGEMETADATADB_NAME += "_v" + version; - - MESSAGECONTENTDB_NAME += "_v" + version; - - QUEUEDB_NAME += "_v" + version; - - DELIVERYDB_NAME += "_v" + version; - - EXCHANGEDB_NAME += "_v" + version; - - QUEUEBINDINGSDB_NAME += "_v" + version; - - LINKDB_NAME += "_v" + version; - - BRIDGEDB_NAME += "_v" + version; - } - } - - public void configureConfigStore(String name, - ConfigurationRecoveryHandler recoveryHandler, - Configuration storeConfiguration, - LogSubject logSubject) throws Exception - { - CurrentActor.get().message(logSubject, ConfigStoreMessages.CREATED(this.getClass().getName())); - - if(!_configured) - { - _logSubject = logSubject; - configure(name,storeConfiguration); - _configured = true; - stateTransition(State.CONFIGURING, State.CONFIGURED); - } - - recover(recoveryHandler); - stateTransition(State.RECOVERING, State.STARTED); - } - - public void configureMessageStore(String name, - MessageStoreRecoveryHandler recoveryHandler, - Configuration storeConfiguration, - LogSubject logSubject) throws Exception - { - CurrentActor.get().message(logSubject, MessageStoreMessages.CREATED(this.getClass().getName())); - - if(!_configured) - { - _logSubject = logSubject; - configure(name,storeConfiguration); - _configured = true; - stateTransition(State.CONFIGURING, State.CONFIGURED); - } - - recoverMessages(recoveryHandler); - } - - public void configureTransactionLog(String name, TransactionLogRecoveryHandler recoveryHandler, - Configuration storeConfiguration, LogSubject logSubject) throws Exception - { - CurrentActor.get().message(logSubject, TransactionLogMessages.CREATED(this.getClass().getName())); - - - if(!_configured) - { - _logSubject = logSubject; - configure(name,storeConfiguration); - _configured = true; - stateTransition(State.CONFIGURING, State.CONFIGURED); - } - - recoverQueueEntries(recoveryHandler); - - - } - - public org.apache.qpid.server.store.MessageStore.Transaction newTransaction() - { - return new BDBTransaction(); - } - - - /** - * Called after instantiation in order to configure the message store. - * - * @param name The name of the virtual host using this store - * @return whether a new store environment was created or not (to indicate whether recovery is necessary) - * - * @throws Exception If any error occurs that means the store is unable to configure itself. - */ - public boolean configure(String name, Configuration storeConfig) throws Exception - { - File environmentPath = new File(storeConfig.getString(ENVIRONMENT_PATH_PROPERTY, - System.getProperty("QPID_WORK") + "/bdbstore/" + name)); - if (!environmentPath.exists()) - { - if (!environmentPath.mkdirs()) - { - throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. " - + "Ensure the path is correct and that the permissions are correct."); - } - } - - CurrentActor.get().message(_logSubject, MessageStoreMessages.STORE_LOCATION(environmentPath.getAbsolutePath())); - - _version = storeConfig.getInt(DATABASE_FORMAT_VERSION_PROPERTY, DATABASE_FORMAT_VERSION); - - return configure(environmentPath, false); - } - - /** - * @param environmentPath location for the store to be created in/recovered from - * @param readonly if true then don't allow modifications to an existing store, and don't create a new store if none exists - * @return whether or not a new store environment was created - * @throws AMQStoreException - * @throws DatabaseException - */ - protected boolean configure(File environmentPath, boolean readonly) throws AMQStoreException, DatabaseException - { - _readOnly = readonly; - stateTransition(State.INITIAL, State.CONFIGURING); - - _log.info("Configuring BDB message store"); - - createTupleBindingFactories(_version); - - setDatabaseNames(_version); - - return setupStore(environmentPath, readonly); - } - - private void createTupleBindingFactories(int version) - { - _bindingTupleBindingFactory = new BindingTupleBindingFactory(version); - _queueTupleBindingFactory = new QueueTupleBindingFactory(version); - _metaDataTupleBindingFactory = new MessageMetaDataTupleBindingFactory(version); - } - - /** - * Move the store state from CONFIGURING to STARTED. - * - * This is required if you do not want to perform recovery of the store data - * - * @throws AMQStoreException if the store is not in the correct state - */ - public void start() throws AMQStoreException - { - stateTransition(State.CONFIGURING, State.STARTED); - } - - private boolean setupStore(File storePath, boolean readonly) throws DatabaseException, AMQStoreException - { - checkState(State.CONFIGURING); - - boolean newEnvironment = createEnvironment(storePath, readonly); - - verifyVersionByTables(); - - openDatabases(readonly); - - if (!readonly) - { - _commitThread.start(); - } - - return newEnvironment; - } - - private void verifyVersionByTables() throws DatabaseException - { - for (String s : _environment.getDatabaseNames()) - { - int versionIndex = s.indexOf("_v"); - - // lack of _v index suggests DB is v1 - // so if _version is not v1 then error - if (versionIndex == -1) - { - if (_version != 1) - { - closeEnvironment(); - throw new IllegalArgumentException("Error: Unable to load BDBStore as version " + _version - + ". Store on disk contains version 1 data."); - } - else // DB is v1 and _version is v1 - { - continue; - } - } - - // Otherwise Check Versions - int version = Integer.parseInt(s.substring(versionIndex + 2)); - - if (version != _version) - { - closeEnvironment(); - throw new IllegalArgumentException("Error: Unable to load BDBStore as version " + _version - + ". Store on disk contains version " + version + " data."); - } - } - } - - private synchronized void stateTransition(State requiredState, State newState) throws AMQStoreException + @Override + protected void setupStore(File storePath, String name) throws DatabaseException, AMQStoreException { - if (_state != requiredState) - { - throw new AMQStoreException("Cannot transition to the state: " + newState + "; need to be in state: " + requiredState - + "; currently in state: " + _state); - } - - _state = newState; - } + super.setupStore(storePath, name); - private void checkState(State requiredState) throws AMQStoreException - { - if (_state != requiredState) - { - throw new AMQStoreException("Unexpected state: " + _state + "; required state: " + requiredState); - } + startCommitThread(); } - private boolean createEnvironment(File environmentPath, boolean readonly) throws DatabaseException + protected Environment createEnvironment(File environmentPath) throws DatabaseException { - _log.info("BDB message store using environment path " + environmentPath.getAbsolutePath()); + LOGGER.info("BDB message store using environment path " + environmentPath.getAbsolutePath()); EnvironmentConfig envConfig = new EnvironmentConfig(); // This is what allows the creation of the store if it does not already exist. envConfig.setAllowCreate(true); @@ -442,11 +78,10 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore _transactionConfig.setReadCommitted(true); //This prevents background threads running which will potentially update the store. - envConfig.setReadOnly(readonly); + envConfig.setReadOnly(false); try { - _environment = new Environment(environmentPath, envConfig); - return false; + return new Environment(environmentPath, envConfig); } catch (DatabaseException de) { @@ -454,14 +89,7 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore { //Allow the creation this time envConfig.setAllowCreate(true); - if (_environment != null ) - { - _environment.cleanLog(); - _environment.close(); - } - _environment = new Environment(environmentPath, envConfig); - - return true; + return new Environment(environmentPath, envConfig); } else { @@ -470,1491 +98,18 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore } } - private void openDatabases(boolean readonly) throws DatabaseException - { - DatabaseConfig dbConfig = new DatabaseConfig(); - dbConfig.setTransactional(true); - dbConfig.setAllowCreate(true); - - //This is required if we are wanting read only access. - dbConfig.setReadOnly(readonly); - - _messageMetaDataDb = openDatabase(MESSAGEMETADATADB_NAME, dbConfig); - _queueDb = openDatabase(QUEUEDB_NAME, dbConfig); - _exchangeDb = openDatabase(EXCHANGEDB_NAME, dbConfig); - _queueBindingsDb = openDatabase(QUEUEBINDINGSDB_NAME, dbConfig); - _messageContentDb = openDatabase(MESSAGECONTENTDB_NAME, dbConfig); - _deliveryDb = openDatabase(DELIVERYDB_NAME, dbConfig); - _linkDb = openDatabase(LINKDB_NAME, dbConfig); - _bridgeDb = openDatabase(BRIDGEDB_NAME, dbConfig); - - - } - - private Database openDatabase(final String dbName, final DatabaseConfig dbConfig) - { - // if opening read-only and the database doesn't exist, then you can't create it - return dbConfig.getReadOnly() && !_environment.getDatabaseNames().contains(dbName) - ? null - : _environment.openDatabase(null, dbName, dbConfig); - } - - /** - * Called to close and cleanup any resources used by the message store. - * - * @throws Exception If the close fails. - */ - public void close() throws Exception - { - if (_state != State.STARTED) - { - return; - } - - _state = State.CLOSING; - - _commitThread.close(); - _commitThread.join(); - - if (_messageMetaDataDb != null) - { - _log.info("Closing message metadata database"); - _messageMetaDataDb.close(); - } - - if (_messageContentDb != null) - { - _log.info("Closing message content database"); - _messageContentDb.close(); - } - - if (_exchangeDb != null) - { - _log.info("Closing exchange database"); - _exchangeDb.close(); - } - - if (_queueBindingsDb != null) - { - _log.info("Closing bindings database"); - _queueBindingsDb.close(); - } - - if (_queueDb != null) - { - _log.info("Closing queue database"); - _queueDb.close(); - } - - if (_deliveryDb != null) - { - _log.info("Close delivery database"); - _deliveryDb.close(); - } - - if (_bridgeDb != null) - { - _log.info("Close bridge database"); - _bridgeDb.close(); - } - - if (_linkDb != null) - { - _log.info("Close link database"); - _linkDb.close(); - } - - closeEnvironment(); - - _state = State.CLOSED; - - CurrentActor.get().message(_logSubject,MessageStoreMessages.CLOSED()); - } - - private void closeEnvironment() throws DatabaseException - { - if (_environment != null) - { - if(!_readOnly) - { - // Clean the log before closing. This makes sure it doesn't contain - // redundant data. Closing without doing this means the cleaner may not - // get a chance to finish. - _environment.cleanLog(); - } - _environment.close(); - } - } - - - public void recover(ConfigurationRecoveryHandler recoveryHandler) throws AMQStoreException - { - stateTransition(State.CONFIGURED, State.RECOVERING); - - CurrentActor.get().message(_logSubject,MessageStoreMessages.RECOVERY_START()); - - try - { - QueueRecoveryHandler qrh = recoveryHandler.begin(this); - loadQueues(qrh); - - ExchangeRecoveryHandler erh = qrh.completeQueueRecovery(); - loadExchanges(erh); - - BindingRecoveryHandler brh = erh.completeExchangeRecovery(); - recoverBindings(brh); - - ConfigurationRecoveryHandler.BrokerLinkRecoveryHandler lrh = brh.completeBindingRecovery(); - recoverBrokerLinks(lrh); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); - } - - } - - private void loadQueues(QueueRecoveryHandler qrh) throws DatabaseException - { - Cursor cursor = null; - - try - { - cursor = _queueDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding binding = _queueTupleBindingFactory.getInstance(); - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - QueueRecord queueRecord = (QueueRecord) binding.entryToObject(value); - - String queueName = queueRecord.getNameShortString() == null ? null : - queueRecord.getNameShortString().asString(); - String owner = queueRecord.getOwner() == null ? null : - queueRecord.getOwner().asString(); - boolean exclusive = queueRecord.isExclusive(); - - FieldTable arguments = queueRecord.getArguments(); - - qrh.queue(queueName, owner, exclusive, arguments); - } - - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - } - - - private void loadExchanges(ExchangeRecoveryHandler erh) throws DatabaseException - { - Cursor cursor = null; - - try - { - cursor = _exchangeDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding binding = new ExchangeTB(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - ExchangeRecord exchangeRec = (ExchangeRecord) binding.entryToObject(value); - - String exchangeName = exchangeRec.getNameShortString() == null ? null : - exchangeRec.getNameShortString().asString(); - String type = exchangeRec.getType() == null ? null : - exchangeRec.getType().asString(); - boolean autoDelete = exchangeRec.isAutoDelete(); - - erh.exchange(exchangeName, type, autoDelete); - } - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - } - - private void recoverBindings(BindingRecoveryHandler brh) throws DatabaseException - { - Cursor cursor = null; - try - { - cursor = _queueBindingsDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding<BindingRecord> binding = _bindingTupleBindingFactory.getInstance(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - //yes, this is retrieving all the useful information from the key only. - //For table compatibility it shall currently be left as is - BindingRecord bindingRecord = binding.entryToObject(key); - - String exchangeName = bindingRecord.getExchangeName() == null ? null : - bindingRecord.getExchangeName().asString(); - String queueName = bindingRecord.getQueueName() == null ? null : - bindingRecord.getQueueName().asString(); - String routingKey = bindingRecord.getRoutingKey() == null ? null : - bindingRecord.getRoutingKey().asString(); - ByteBuffer argumentsBB = (bindingRecord.getArguments() == null ? null : - java.nio.ByteBuffer.wrap(bindingRecord.getArguments().getDataAsBytes())); - - brh.binding(exchangeName, queueName, routingKey, argumentsBB); - } - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - } - - - private void recoverBrokerLinks(final ConfigurationRecoveryHandler.BrokerLinkRecoveryHandler lrh) - { - Cursor cursor = null; - - try - { - cursor = _linkDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - UUID id = UUIDTupleBinding.getInstance().entryToObject(key); - long createTime = LongBinding.entryToLong(value); - Map<String,String> arguments = StringMapBinding.getInstance().entryToObject(value); - - ConfigurationRecoveryHandler.BridgeRecoveryHandler brh = lrh.brokerLink(id, createTime, arguments); - - recoverBridges(brh, id); - } - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - } - - private void recoverBridges(final ConfigurationRecoveryHandler.BridgeRecoveryHandler brh, final UUID linkId) - { - Cursor cursor = null; - - try - { - cursor = _bridgeDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - UUID id = UUIDTupleBinding.getInstance().entryToObject(key); - - UUID parentId = UUIDTupleBinding.getInstance().entryToObject(value); - if(parentId.equals(linkId)) - { - - long createTime = LongBinding.entryToLong(value); - Map<String,String> arguments = StringMapBinding.getInstance().entryToObject(value); - brh.bridge(id,createTime,arguments); - } - } - brh.completeBridgeRecoveryForLink(); - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - } - - - private void recoverMessages(MessageStoreRecoveryHandler msrh) throws DatabaseException - { - StoredMessageRecoveryHandler mrh = msrh.begin(); - - Cursor cursor = null; - try - { - cursor = _messageMetaDataDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - EntryBinding valueBinding = _metaDataTupleBindingFactory.getInstance(); - - long maxId = 0; - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - long messageId = LongBinding.entryToLong(key); - StorableMessageMetaData metaData = (StorableMessageMetaData) valueBinding.entryToObject(value); - - StoredBDBMessage message = new StoredBDBMessage(messageId, metaData, false); - mrh.message(message); - - maxId = Math.max(maxId, messageId); - } - - _messageId.set(maxId); - } - catch (DatabaseException e) - { - _log.error("Database Error: " + e.getMessage(), e); - throw e; - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - } - - private void recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) - throws DatabaseException - { - QueueEntryRecoveryHandler qerh = recoveryHandler.begin(this); - - ArrayList<QueueEntryKey> entries = new ArrayList<QueueEntryKey>(); - - Cursor cursor = null; - try - { - cursor = _deliveryDb.openCursor(null, null); - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new QueueEntryTB(); - - DatabaseEntry value = new DatabaseEntry(); - - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - QueueEntryKey qek = (QueueEntryKey) keyBinding.entryToObject(key); - - entries.add(qek); - } - - try - { - cursor.close(); - } - finally - { - cursor = null; - } - - for(QueueEntryKey entry : entries) - { - AMQShortString queueName = entry.getQueueName(); - long messageId = entry.getMessageId(); - - qerh.queueEntry(queueName.asString(),messageId); - } - } - catch (DatabaseException e) - { - _log.error("Database Error: " + e.getMessage(), e); - throw e; - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - - qerh.completeQueueEntryRecovery(); - } - - /** - * Removes the specified message from the store. - * - * @param messageId Identifies the message to remove. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void removeMessage(long messageId) throws AMQStoreException - { - removeMessage(messageId, true); - } - public void removeMessage(long messageId, boolean sync) throws AMQStoreException - { - - boolean complete = false; - com.sleepycat.je.Transaction tx = null; - - Random rand = null; - int attempts = 0; - try - { - do - { - tx = null; - try - { - tx = _environment.beginTransaction(null, null); - - //remove the message meta data from the store - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - - if (_log.isDebugEnabled()) - { - _log.debug("Removing message id " + messageId); - } - - - OperationStatus status = _messageMetaDataDb.delete(tx, key); - if (status == OperationStatus.NOTFOUND) - { - _log.info("Message not found (attempt to remove failed - probably application initiated rollback) " + - messageId); - } - - if (_log.isDebugEnabled()) - { - _log.debug("Deleted metadata for message " + messageId); - } - - //now remove the content data from the store if there is any. - - - - int offset = 0; - do - { - DatabaseEntry contentKeyEntry = new DatabaseEntry(); - MessageContentKey_5 mck = new MessageContentKey_5(messageId,offset); - TupleBinding<MessageContentKey> contentKeyTupleBinding = new MessageContentKeyTB_5(); - contentKeyTupleBinding.objectToEntry(mck, contentKeyEntry); - //Use a partial record for the value to prevent retrieving the - //data itself as we only need the key to identify what to remove. - DatabaseEntry value = new DatabaseEntry(); - value.setPartial(0, 4, true); - - status = _messageContentDb.get(null,contentKeyEntry, value, LockMode.READ_COMMITTED); - - if(status == OperationStatus.SUCCESS) - { - - offset += IntegerBinding.entryToInt(value); - _messageContentDb.delete(tx, contentKeyEntry); - if (_log.isDebugEnabled()) - { - _log.debug("Deleted content chunk offset " + mck.getOffset() + " for message " + messageId); - } - } - } - while (status == OperationStatus.SUCCESS); - - commit(tx, sync); - complete = true; - tx = null; - } - catch (LockConflictException e) - { - try - { - if(tx != null) - { - tx.abort(); - } - } - catch(DatabaseException e2) - { - _log.warn("Unable to abort transaction after LockConflictExcption", e2); - // rethrow the original log conflict exception, the secondary exception should already have - // been logged. - throw e; - } - - - _log.warn("Lock timeout exception. Retrying (attempt " - + (attempts+1) + " of "+ LOCK_RETRY_ATTEMPTS +") " + e); - - if(++attempts < LOCK_RETRY_ATTEMPTS) - { - if(rand == null) - { - rand = new Random(); - } - - try - { - Thread.sleep(500l + (long)(500l * rand.nextDouble())); - } - catch (InterruptedException e1) - { - - } - } - else - { - // rethrow the lock conflict exception since we could not solve by retrying - throw e; - } - } - } - while(!complete); - } - catch (DatabaseException e) - { - _log.error("Unexpected BDB exception", e); - - if (tx != null) - { - try - { - tx.abort(); - tx = null; - } - catch (DatabaseException e1) - { - throw new AMQStoreException("Error aborting transaction " + e1, e1); - } - } - - throw new AMQStoreException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); - } - finally - { - if (tx != null) - { - try - { - tx.abort(); - tx = null; - } - catch (DatabaseException e1) - { - throw new AMQStoreException("Error aborting transaction " + e1, e1); - } - } - } - } - - /** - * @see DurableConfigurationStore#createExchange(Exchange) - */ - public void createExchange(Exchange exchange) throws AMQStoreException - { - if (_state != State.RECOVERING) - { - ExchangeRecord exchangeRec = new ExchangeRecord(exchange.getNameShortString(), - exchange.getTypeShortString(), exchange.isAutoDelete()); - - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new AMQShortStringTB(); - keyBinding.objectToEntry(exchange.getNameShortString(), key); - - DatabaseEntry value = new DatabaseEntry(); - TupleBinding exchangeBinding = new ExchangeTB(); - exchangeBinding.objectToEntry(exchangeRec, value); - - try - { - _exchangeDb.put(null, key, value); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing Exchange with name " + exchange.getName() + " to database: " + e.getMessage(), e); - } - } - } - - /** - * @see DurableConfigurationStore#removeExchange(Exchange) - */ - public void removeExchange(Exchange exchange) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new AMQShortStringTB(); - keyBinding.objectToEntry(exchange.getNameShortString(), key); - try - { - OperationStatus status = _exchangeDb.delete(null, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Exchange " + exchange.getName() + " not found"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing deleting with name " + exchange.getName() + " from database: " + e.getMessage(), e); - } - } - - - /** - * @see DurableConfigurationStore#bindQueue(Exchange, AMQShortString, AMQQueue, FieldTable) - */ - public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException - { - bindQueue(new BindingRecord(exchange.getNameShortString(), queue.getNameShortString(), routingKey, args)); - } - - protected void bindQueue(final BindingRecord bindingRecord) throws AMQStoreException - { - if (_state != State.RECOVERING) - { - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = _bindingTupleBindingFactory.getInstance(); - - keyBinding.objectToEntry(bindingRecord, key); - - //yes, this is writing out 0 as a value and putting all the - //useful info into the key, don't ask me why. For table - //compatibility it shall currently be left as is - DatabaseEntry value = new DatabaseEntry(); - ByteBinding.byteToEntry((byte) 0, value); - - try - { - _queueBindingsDb.put(null, key, value); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing binding for AMQQueue with name " + bindingRecord.getQueueName() + " to exchange " - + bindingRecord.getExchangeName() + " to database: " + e.getMessage(), e); - } - } - } - - /** - * @see DurableConfigurationStore#unbindQueue(Exchange, AMQShortString, AMQQueue, FieldTable) - */ - public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) - throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = _bindingTupleBindingFactory.getInstance(); - keyBinding.objectToEntry(new BindingRecord(exchange.getNameShortString(), queue.getNameShortString(), routingKey, args), key); - - try - { - OperationStatus status = _queueBindingsDb.delete(null, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Queue binding for queue with name " + queue.getName() + " to exchange " - + exchange.getName() + " not found"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error deleting queue binding for queue with name " + queue.getName() + " to exchange " - + exchange.getName() + " from database: " + e.getMessage(), e); - } - } - - /** - * @see DurableConfigurationStore#createQueue(AMQQueue) - */ - public void createQueue(AMQQueue queue) throws AMQStoreException - { - createQueue(queue, null); - } - - /** - * @see DurableConfigurationStore#createQueue(AMQQueue, FieldTable) - */ - public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException - { - if (_log.isDebugEnabled()) - { - _log.debug("public void createQueue(AMQQueue queue(" + queue.getName() + ") = " + queue + "): called"); - } - - QueueRecord queueRecord= new QueueRecord(queue.getNameShortString(), - queue.getOwner(), queue.isExclusive(), arguments); - - createQueue(queueRecord); - } - - /** - * Makes the specified queue persistent. - * - * Only intended for direct use during store upgrades. - * - * @param queueRecord Details of the queue to store. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - protected void createQueue(QueueRecord queueRecord) throws AMQStoreException - { - if (_state != State.RECOVERING) - { - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new AMQShortStringTB(); - keyBinding.objectToEntry(queueRecord.getNameShortString(), key); - - DatabaseEntry value = new DatabaseEntry(); - TupleBinding queueBinding = _queueTupleBindingFactory.getInstance(); - - queueBinding.objectToEntry(queueRecord, value); - try - { - _queueDb.put(null, key, value); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing AMQQueue with name " + queueRecord.getNameShortString().asString() - + " to database: " + e.getMessage(), e); - } - } - } - - /** - * Updates the specified queue in the persistent store, IF it is already present. If the queue - * is not present in the store, it will not be added. - * - * NOTE: Currently only updates the exclusivity. - * - * @param queue The queue to update the entry for. - * @throws AMQStoreException If the operation fails for any reason. - */ - public void updateQueue(final AMQQueue queue) throws AMQStoreException - { - if (_log.isDebugEnabled()) - { - _log.debug("Updating queue: " + queue.getName()); - } - - try - { - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new AMQShortStringTB(); - keyBinding.objectToEntry(queue.getNameShortString(), key); - - DatabaseEntry value = new DatabaseEntry(); - DatabaseEntry newValue = new DatabaseEntry(); - TupleBinding queueBinding = _queueTupleBindingFactory.getInstance(); - - OperationStatus status = _queueDb.get(null, key, value, LockMode.DEFAULT); - if(status == OperationStatus.SUCCESS) - { - //read the existing record and apply the new exclusivity setting - QueueRecord queueRecord = (QueueRecord) queueBinding.entryToObject(value); - queueRecord.setExclusive(queue.isExclusive()); - - //write the updated entry to the store - queueBinding.objectToEntry(queueRecord, newValue); - - _queueDb.put(null, key, newValue); - } - else if(status != OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Error updating queue details within the store: " + status); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error updating queue details within the store: " + e,e); - } - } - - /** - * Removes the specified queue from the persistent store. - * - * @param queue The queue to remove. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void removeQueue(final AMQQueue queue) throws AMQStoreException - { - AMQShortString name = queue.getNameShortString(); - - if (_log.isDebugEnabled()) - { - _log.debug("public void removeQueue(AMQShortString name = " + name + "): called"); - } - - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new AMQShortStringTB(); - keyBinding.objectToEntry(name, key); - try - { - OperationStatus status = _queueDb.delete(null, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Queue " + name + " not found"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing deleting with name " + name + " from database: " + e.getMessage(), e); - } - } - - public void createBrokerLink(final BrokerLink link) throws AMQStoreException - { - if (_state != State.RECOVERING) - { - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding.getInstance().objectToEntry(link.getId(), key); - - DatabaseEntry value = new DatabaseEntry(); - LongBinding.longToEntry(link.getCreateTime(),value); - StringMapBinding.getInstance().objectToEntry(link.getArguments(), value); - - try - { - _linkDb.put(null, key, value); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing Link " + link - + " to database: " + e.getMessage(), e); - } - } - } - - public void deleteBrokerLink(final BrokerLink link) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding.getInstance().objectToEntry(link.getId(), key); - try - { - OperationStatus status = _linkDb.delete(null, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Link " + link + " not found"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error deleting the Link " + link + " from database: " + e.getMessage(), e); - } - } - - public void createBridge(final Bridge bridge) throws AMQStoreException - { - if (_state != State.RECOVERING) - { - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding.getInstance().objectToEntry(bridge.getId(), key); - - DatabaseEntry value = new DatabaseEntry(); - UUIDTupleBinding.getInstance().objectToEntry(bridge.getLink().getId(),value); - LongBinding.longToEntry(bridge.getCreateTime(),value); - StringMapBinding.getInstance().objectToEntry(bridge.getArguments(), value); - - try - { - _bridgeDb.put(null, key, value); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing Bridge " + bridge - + " to database: " + e.getMessage(), e); - } - - } - } - - public void deleteBridge(final Bridge bridge) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - UUIDTupleBinding.getInstance().objectToEntry(bridge.getId(), key); - try - { - OperationStatus status = _bridgeDb.delete(null, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Bridge " + bridge + " not found"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error deleting the Bridge " + bridge + " from database: " + e.getMessage(), e); - } - } - - /** - * Places a message onto a specified queue, in a given transaction. - * - * @param tx The transaction for the operation. - * @param queue The the queue to place the message on. - * @param messageId The message to enqueue. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void enqueueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, - long messageId) throws AMQStoreException - { - AMQShortString name = AMQShortString.valueOf(queue.getResourceName()); - - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new QueueEntryTB(); - QueueEntryKey dd = new QueueEntryKey(name, messageId); - keyBinding.objectToEntry(dd, key); - DatabaseEntry value = new DatabaseEntry(); - ByteBinding.byteToEntry((byte) 0, value); - - try - { - if (_log.isDebugEnabled()) - { - _log.debug("Enqueuing message " + messageId + " on queue " + name + " [Transaction" + tx + "]"); - } - _deliveryDb.put(tx, key, value); - } - catch (DatabaseException e) - { - _log.error("Failed to enqueue: " + e.getMessage(), e); - throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " + name - + " to database", e); - } - } - - /** - * Extracts a message from a specified queue, in a given transaction. - * - * @param tx The transaction for the operation. - * @param queue The name queue to take the message from. - * @param messageId The message to dequeue. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public void dequeueMessage(final com.sleepycat.je.Transaction tx, final TransactionLogResource queue, - long messageId) throws AMQStoreException - { - AMQShortString name = new AMQShortString(queue.getResourceName()); - - DatabaseEntry key = new DatabaseEntry(); - EntryBinding keyBinding = new QueueEntryTB(); - QueueEntryKey dd = new QueueEntryKey(name, messageId); - - keyBinding.objectToEntry(dd, key); - - if (_log.isDebugEnabled()) - { - _log.debug("Dequeue message id " + messageId); - } - - try - { - - OperationStatus status = _deliveryDb.delete(tx, key); - if (status == OperationStatus.NOTFOUND) - { - throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + name); - } - else if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Unable to remove message with id " + messageId + " on queue " + name); - } - - if (_log.isDebugEnabled()) - { - _log.debug("Removed message " + messageId + ", " + name + " from delivery db"); - - } - } - catch (DatabaseException e) - { - - _log.error("Failed to dequeue message " + messageId + ": " + e.getMessage(), e); - _log.error(tx); - - throw new AMQStoreException("Error accessing database while dequeuing message: " + e.getMessage(), e); - } - } - - /** - * Commits all operations performed within a given transaction. - * - * @param tx The transaction to commit all operations for. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - private StoreFuture commitTranImpl(final com.sleepycat.je.Transaction tx, boolean syncCommit) throws AMQStoreException - { - if (tx == null) - { - throw new AMQStoreException("Fatal internal error: transactional is null at commitTran"); - } - - StoreFuture result; - try - { - result = commit(tx, syncCommit); - - if (_log.isDebugEnabled()) - { - _log.debug("commitTranImpl completed for [Transaction:" + tx + "]"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error commit tx: " + e.getMessage(), e); - } - - return result; - } - - /** - * Abandons all operations performed within a given transaction. - * - * @param tx The transaction to abandon. - * - * @throws AMQStoreException If the operation fails for any reason. - */ - public void abortTran(final com.sleepycat.je.Transaction tx) throws AMQStoreException - { - if (_log.isDebugEnabled()) - { - _log.debug("abortTran called for [Transaction:" + tx + "]"); - } - - try - { - tx.abort(); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e); - } - } - - /** - * Primarily for testing purposes. - * - * @param queueName - * - * @return a list of message ids for messages enqueued for a particular queue - */ - List<Long> getEnqueuedMessages(AMQShortString queueName) throws AMQStoreException - { - Cursor cursor = null; - try - { - cursor = _deliveryDb.openCursor(null, null); - - DatabaseEntry key = new DatabaseEntry(); - - QueueEntryKey dd = new QueueEntryKey(queueName, 0); - - EntryBinding keyBinding = new QueueEntryTB(); - keyBinding.objectToEntry(dd, key); - - DatabaseEntry value = new DatabaseEntry(); - - LinkedList<Long> messageIds = new LinkedList<Long>(); - - OperationStatus status = cursor.getSearchKeyRange(key, value, LockMode.DEFAULT); - dd = (QueueEntryKey) keyBinding.entryToObject(key); - - while ((status == OperationStatus.SUCCESS) && dd.getQueueName().equals(queueName)) - { - - messageIds.add(dd.getMessageId()); - status = cursor.getNext(key, value, LockMode.DEFAULT); - if (status == OperationStatus.SUCCESS) - { - dd = (QueueEntryKey) keyBinding.entryToObject(key); - } - } - - return messageIds; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Database error: " + e.getMessage(), e); - } - finally - { - if (cursor != null) - { - try - { - cursor.close(); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error closing cursor: " + e.getMessage(), e); - } - } - } - } - - /** - * Return a valid, currently unused message id. - * - * @return A fresh message id. - */ - public long getNewMessageId() - { - return _messageId.incrementAndGet(); - } - - /** - * Stores a chunk of message data. - * - * @param tx The transaction for the operation. - * @param messageId The message to store the data for. - * @param offset The offset of the data chunk in the message. - * @param contentBody The content of the data chunk. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - protected void addContent(final com.sleepycat.je.Transaction tx, long messageId, int offset, - ByteBuffer contentBody) throws AMQStoreException - { - DatabaseEntry key = new DatabaseEntry(); - TupleBinding<MessageContentKey> keyBinding = new MessageContentKeyTB_5(); - keyBinding.objectToEntry(new MessageContentKey_5(messageId, offset), key); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding<ByteBuffer> messageBinding = new ContentTB(); - messageBinding.objectToEntry(contentBody, value); - try - { - OperationStatus status = _messageContentDb.put(tx, key, value); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Error adding content chunk offset" + offset + " for message id " + messageId + ": " - + status); - } - - if (_log.isDebugEnabled()) - { - _log.debug("Storing content chunk offset" + offset + " for message " + messageId + "[Transaction" + tx + "]"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); - } - } - - /** - * Stores message meta-data. - * - * @param tx The transaction for the operation. - * @param messageId The message to store the data for. - * @param messageMetaData The message meta data to store. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - private void storeMetaData(final com.sleepycat.je.Transaction tx, long messageId, - StorableMessageMetaData messageMetaData) - throws AMQStoreException - { - if (_log.isDebugEnabled()) - { - _log.debug("public void storeMetaData(Txn tx = " + tx + ", Long messageId = " - + messageId + ", MessageMetaData messageMetaData = " + messageMetaData + "): called"); - } - - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - DatabaseEntry value = new DatabaseEntry(); - - TupleBinding messageBinding = _metaDataTupleBindingFactory.getInstance(); - messageBinding.objectToEntry(messageMetaData, value); - try - { - _messageMetaDataDb.put(tx, key, value); - if (_log.isDebugEnabled()) - { - _log.debug("Storing message metadata for message id " + messageId + "[Transaction" + tx + "]"); - } - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing message metadata with id " + messageId + " to database: " + e.getMessage(), e); - } - } - - /** - * Retrieves message meta-data. - * - * @param messageId The message to get the meta-data for. - * - * @return The message meta data. - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public StorableMessageMetaData getMessageMetaData(long messageId) throws AMQStoreException - { - if (_log.isDebugEnabled()) - { - _log.debug("public MessageMetaData getMessageMetaData(Long messageId = " - + messageId + "): called"); - } - - DatabaseEntry key = new DatabaseEntry(); - LongBinding.longToEntry(messageId, key); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding messageBinding = _metaDataTupleBindingFactory.getInstance(); - - try - { - OperationStatus status = _messageMetaDataDb.get(null, key, value, LockMode.READ_UNCOMMITTED); - if (status != OperationStatus.SUCCESS) - { - throw new AMQStoreException("Metadata not found for message with id " + messageId); - } - - StorableMessageMetaData mdd = (StorableMessageMetaData) messageBinding.entryToObject(value); - - return mdd; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error reading message metadata for message with id " + messageId + ": " + e.getMessage(), e); - } - } - - /** - * Fills the provided ByteBuffer with as much content for the specified message as possible, starting - * from the specified offset in the message. - * - * @param messageId The message to get the data for. - * @param offset The offset of the data within the message. - * @param dst The destination of the content read back - * - * @return The number of bytes inserted into the destination - * - * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. - */ - public int getContent(long messageId, int offset, ByteBuffer dst) throws AMQStoreException - { - DatabaseEntry contentKeyEntry = new DatabaseEntry(); - - //Start from 0 offset and search for the starting chunk. - MessageContentKey_5 mck = new MessageContentKey_5(messageId, 0); - TupleBinding<MessageContentKey> contentKeyTupleBinding = new MessageContentKeyTB_5(); - contentKeyTupleBinding.objectToEntry(mck, contentKeyEntry); - DatabaseEntry value = new DatabaseEntry(); - TupleBinding<ByteBuffer> contentTupleBinding = new ContentTB(); - - if (_log.isDebugEnabled()) - { - _log.debug("Message Id: " + messageId + " Getting content body from offset: " + offset); - } - - int written = 0; - int seenSoFar = 0; - - Cursor cursor = null; - try - { - cursor = _messageContentDb.openCursor(null, null); - - OperationStatus status = cursor.getSearchKeyRange(contentKeyEntry, value, LockMode.READ_UNCOMMITTED); - - while (status == OperationStatus.SUCCESS) - { - mck = (MessageContentKey_5) contentKeyTupleBinding.entryToObject(contentKeyEntry); - long id = mck.getMessageId(); - - if(id != messageId) - { - //we have exhausted all chunks for this message id, break - break; - } - - int offsetInMessage = mck.getOffset(); - ByteBuffer buf = (ByteBuffer) contentTupleBinding.entryToObject(value); - - final int size = (int) buf.limit(); - - seenSoFar += size; - - if(seenSoFar >= offset) - { - byte[] dataAsBytes = buf.array(); - - int posInArray = offset + written - offsetInMessage; - int count = size - posInArray; - if(count > dst.remaining()) - { - count = dst.remaining(); - } - dst.put(dataAsBytes,posInArray,count); - written+=count; - - if(dst.remaining() == 0) - { - break; - } - } - - status = cursor.getNext(contentKeyEntry, value, LockMode.RMW); - } - - return written; - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); - } - finally - { - if(cursor != null) - { - try - { - cursor.close(); - } - catch (DatabaseException e) - { - throw new AMQStoreException("Error writing AMQMessage with id " + messageId + " to database: " + e.getMessage(), e); - } - } - } - } - - public boolean isPersistent() - { - return true; - } - - public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData) - { - if(metaData.isPersistent()) - { - return new StoredBDBMessage(getNewMessageId(), metaData); - } - else - { - return new StoredMemoryMessage(getNewMessageId(), metaData); - } - } - - - //protected getters for the TupleBindingFactories - - protected QueueTupleBindingFactory getQueueTupleBindingFactory() - { - return _queueTupleBindingFactory; - } - - protected BindingTupleBindingFactory getBindingTupleBindingFactory() - { - return _bindingTupleBindingFactory; - } - - protected MessageMetaDataTupleBindingFactory getMetaDataTupleBindingFactory() - { - return _metaDataTupleBindingFactory; - } - - //Package getters for the various databases used by the Store - - Database getMetaDataDb() - { - return _messageMetaDataDb; - } - - Database getContentDb() - { - return _messageContentDb; - } - - Database getQueuesDb() - { - return _queueDb; - } - - Database getDeliveryDb() - { - return _deliveryDb; - } - Database getExchangesDb() - { - return _exchangeDb; - } - Database getBindingsDb() + @Override + protected void closeInternal() throws Exception { - return _queueBindingsDb; - } + stopCommitThread(); - void visitMetaDataDb(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_messageMetaDataDb, visitor); + super.closeInternal(); } - void visitContentDb(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_messageContentDb, visitor); - } - - void visitQueues(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_queueDb, visitor); - } - - void visitDelivery(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_deliveryDb, visitor); - } - - void visitExchanges(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_exchangeDb, visitor); - } - - void visitBindings(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - visitDatabase(_queueBindingsDb, visitor); - } - - /** - * Generic visitDatabase allows iteration through the specified database. - * - * @param database The database to visit - * @param visitor The visitor to give each entry to. - * - * @throws DatabaseException If there is a problem with the Database structure - * @throws AMQStoreException If there is a problem with the Database contents - */ - void visitDatabase(Database database, DatabaseVisitor visitor) throws DatabaseException, AMQStoreException - { - Cursor cursor = database.openCursor(null, null); - - try - { - DatabaseEntry key = new DatabaseEntry(); - DatabaseEntry value = new DatabaseEntry(); - while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) - { - visitor.visit(key, value); - } - } - finally - { - if (cursor != null) - { - cursor.close(); - } - } - } - - private StoreFuture commit(com.sleepycat.je.Transaction tx, boolean syncCommit) throws DatabaseException + @Override + protected StoreFuture commit(com.sleepycat.je.Transaction tx, boolean syncCommit) throws DatabaseException { tx.commitNoSync(); @@ -1964,11 +119,17 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore return commitFuture; } - public void startCommitThread() + private void startCommitThread() { _commitThread.start(); } + private void stopCommitThread() throws InterruptedException + { + _commitThread.close(); + _commitThread.join(); + } + private static final class BDBCommitFuture implements StoreFuture { private final CommitThread _commitThread; @@ -1986,9 +147,9 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore public synchronized void complete() { - if (_log.isDebugEnabled()) + if (LOGGER.isDebugEnabled()) { - _log.debug("public synchronized void complete(): called (Transaction = " + _tx + ")"); + LOGGER.debug("public synchronized void complete(): called (Transaction = " + _tx + ")"); } _complete = true; @@ -2009,7 +170,7 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore if(!_syncCommit) { - _log.debug("CommitAsync was requested, returning immediately."); + LOGGER.debug("CommitAsync was requested, returning immediately."); return; } @@ -2051,7 +212,7 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore * continuing, but it is the responsibility of this thread to tell the commit operations when they have been * completed by calling back on their {@link BDBCommitFuture#complete()} and {@link BDBCommitFuture#abort} methods. * - * <p/><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collarations </table> + * <p/><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations </table> */ private class CommitThread extends Thread { @@ -2104,7 +265,7 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore try { - _environment.flushLog(true); + getEnvironment().flushLog(true); for(int i = 0; i < size; i++) { @@ -2152,239 +313,4 @@ public class BDBMessageStore implements MessageStore, DurableConfigurationStore } } - - private class StoredBDBMessage implements StoredMessage - { - - private final long _messageId; - private volatile SoftReference<StorableMessageMetaData> _metaDataRef; - - private StorableMessageMetaData _metaData; - private volatile SoftReference<byte[]> _dataRef; - private byte[] _data; - - StoredBDBMessage(long messageId, StorableMessageMetaData metaData) - { - this(messageId, metaData, true); - } - - - StoredBDBMessage(long messageId, - StorableMessageMetaData metaData, boolean persist) - { - try - { - _messageId = messageId; - _metaData = metaData; - - _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); - - } - catch (DatabaseException e) - { - throw new RuntimeException(e); - } - - } - - public StorableMessageMetaData getMetaData() - { - StorableMessageMetaData metaData = _metaDataRef.get(); - if(metaData == null) - { - try - { - metaData = BDBMessageStore.this.getMessageMetaData(_messageId); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); - } - - return metaData; - } - - public long getMessageNumber() - { - return _messageId; - } - - public void addContent(int offsetInMessage, java.nio.ByteBuffer src) - { - src = src.slice(); - - if(_data == null) - { - _data = new byte[src.remaining()]; - _dataRef = new SoftReference<byte[]>(_data); - src.duplicate().get(_data); - } - else - { - byte[] oldData = _data; - _data = new byte[oldData.length + src.remaining()]; - _dataRef = new SoftReference<byte[]>(_data); - - System.arraycopy(oldData,0,_data,0,oldData.length); - src.duplicate().get(_data, oldData.length, src.remaining()); - } - - } - - public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) - { - byte[] data = _dataRef == null ? null : _dataRef.get(); - if(data != null) - { - int length = Math.min(dst.remaining(), data.length - offsetInMessage); - dst.put(data, offsetInMessage, length); - return length; - } - else - { - try - { - return BDBMessageStore.this.getContent(_messageId, offsetInMessage, dst); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - } - } - - public ByteBuffer getContent(int offsetInMessage, int size) - { - byte[] data = _dataRef == null ? null : _dataRef.get(); - if(data != null) - { - return ByteBuffer.wrap(data,offsetInMessage,size); - } - else - { - ByteBuffer buf = ByteBuffer.allocate(size); - getContent(offsetInMessage, buf); - buf.position(0); - return buf; - } - } - - synchronized void store(com.sleepycat.je.Transaction txn) - { - - if(_metaData != null) - { - try - { - _dataRef = new SoftReference<byte[]>(_data); - BDBMessageStore.this.storeMetaData(txn, _messageId, _metaData); - BDBMessageStore.this.addContent(txn, _messageId, 0, - _data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data)); - } - catch(DatabaseException e) - { - throw new RuntimeException(e); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - catch (RuntimeException e) - { - e.printStackTrace(); - throw e; - } - finally - { - _metaData = null; - _data = null; - } - } - } - - public synchronized StoreFuture flushToStore() - { - if(_metaData != null) - { - com.sleepycat.je.Transaction txn = _environment.beginTransaction(null, null); - store(txn); - BDBMessageStore.this.commit(txn,true); - - } - return IMMEDIATE_FUTURE; - } - - public void remove() - { - try - { - BDBMessageStore.this.removeMessage(_messageId, false); - } - catch (AMQStoreException e) - { - throw new RuntimeException(e); - } - } - } - - private class BDBTransaction implements Transaction - { - private com.sleepycat.je.Transaction _txn; - - private BDBTransaction() - { - try - { - _txn = _environment.beginTransaction(null, null); - } - catch (DatabaseException e) - { - throw new RuntimeException(e); - } - } - - public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException - { - if(message.getStoredMessage() instanceof StoredBDBMessage) - { - ((StoredBDBMessage)message.getStoredMessage()).store(_txn); - } - - BDBMessageStore.this.enqueueMessage(_txn, queue, message.getMessageNumber()); - } - - public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException - { - BDBMessageStore.this.dequeueMessage(_txn, queue, message.getMessageNumber()); - } - - public void enqueueMessage(TransactionLogResource queue, long messageId) throws AMQStoreException - { - BDBMessageStore.this.enqueueMessage(_txn, queue, messageId); - } - - public void dequeueMessage(TransactionLogResource queue, long messageId) throws AMQStoreException - { - BDBMessageStore.this.dequeueMessage(_txn, queue, messageId); - - } - - public void commitTran() throws AMQStoreException - { - BDBMessageStore.this.commitTranImpl(_txn, true); - } - - public StoreFuture commitTranAsync() throws AMQStoreException - { - return BDBMessageStore.this.commitTranImpl(_txn, false); - } - - public void abortTran() throws AMQStoreException - { - BDBMessageStore.this.abortTran(_txn); - } - } - } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java index c274fdec8c..7e5ef3f94c 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java @@ -1,5 +1,4 @@ /* - * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -20,30 +19,22 @@ */ package org.apache.qpid.server.store.berkeleydb; -import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MessageStoreFactory; -public class QueueEntryKey +public class BDBMessageStoreFactory implements MessageStoreFactory { - private AMQShortString _queueName; - private long _messageId; - - public QueueEntryKey(AMQShortString queueName, long messageId) + @Override + public MessageStore createMessageStore() { - _queueName = queueName; - _messageId = messageId; + return new BDBMessageStore(); } - - public AMQShortString getQueueName() - { - return _queueName; - } - - - public long getMessageId() + @Override + public String getStoreClassName() { - return _messageId; + return BDBMessageStore.class.getSimpleName(); } } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java deleted file mode 100644 index 817ba2a5f5..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java +++ /dev/null @@ -1,1299 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.je.Database; -import com.sleepycat.je.DatabaseEntry; -import com.sleepycat.je.DatabaseException; -import com.sleepycat.je.Environment; -import com.sleepycat.je.EnvironmentConfig; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionBuilder; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.apache.commons.cli.PosixParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.qpid.AMQException; -import org.apache.qpid.AMQStoreException; -import org.apache.qpid.common.AMQPFilterTypes; -import org.apache.qpid.exchange.ExchangeDefaults; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.logging.NullRootMessageLogger; -import org.apache.qpid.server.logging.actors.BrokerActor; -import org.apache.qpid.server.logging.actors.CurrentActor; -import org.apache.qpid.server.message.MessageMetaData; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_4; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_5; -import org.apache.qpid.server.store.berkeleydb.records.BindingRecord; -import org.apache.qpid.server.store.berkeleydb.records.ExchangeRecord; -import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageContentKeyTB_4; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageContentKeyTB_5; -import org.apache.qpid.server.store.berkeleydb.tuples.QueueEntryTB; -import org.apache.qpid.util.FileUtils; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.ByteBuffer; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - -/** - * This is a simple BerkeleyDB Store upgrade tool that will upgrade a V4 Store to a V5 Store. - * - * Currently upgrade is fixed from v4 -> v5 - * - * Improvments: - * - Add List BDBMessageStore.getDatabases(); This can the be iterated to guard against new DBs being added. - * - A version in the store would allow automated upgrade or later with more available versions interactive upgrade. - * - Add process logging and disable all Store and Qpid logging. - */ -public class BDBStoreUpgrade -{ - private static final Logger _logger = LoggerFactory.getLogger(BDBStoreUpgrade.class); - /** The Store Directory that needs upgrading */ - private File _fromDir; - /** The Directory that will be made to contain the upgraded store */ - private File _toDir; - /** The Directory that will be made to backup the original store if required */ - private File _backupDir; - - /** The Old Store */ - private BDBMessageStore _oldMessageStore; - /** The New Store */ - private BDBMessageStore _newMessageStore; - /** The file ending that is used by BDB Store Files */ - private static final String BDB_FILE_ENDING = ".jdb"; - - static final Options _options = new Options(); - private static CommandLine _commandLine; - private boolean _interactive; - private boolean _force; - - private static final String VERSION = "3.0"; - private static final String USER_ABORTED_PROCESS = "User aborted process"; - private static final int LOWEST_SUPPORTED_STORE_VERSION = 4; - private static final String PREVIOUS_STORE_VERSION_UNSUPPORTED = "Store upgrade from version {0} is not supported." - + " You must first run the previous store upgrade tool."; - private static final String FOLLOWING_STORE_VERSION_UNSUPPORTED = "Store version {0} is newer than this tool supports. " - + "You must use a newer version of the store upgrade tool"; - private static final String STORE_ALREADY_UPGRADED = "Store has already been upgraded to version {0}."; - - private static final String OPTION_INPUT_SHORT = "i"; - private static final String OPTION_INPUT = "input"; - private static final String OPTION_OUTPUT_SHORT = "o"; - private static final String OPTION_OUTPUT = "output"; - private static final String OPTION_BACKUP_SHORT = "b"; - private static final String OPTION_BACKUP = "backup"; - private static final String OPTION_QUIET_SHORT = "q"; - private static final String OPTION_QUIET = "quiet"; - private static final String OPTION_FORCE_SHORT = "f"; - private static final String OPTION_FORCE = "force"; - private boolean _inplace = false; - - public BDBStoreUpgrade(String fromDir, String toDir, String backupDir, boolean interactive, boolean force) - { - _interactive = interactive; - _force = force; - - _fromDir = new File(fromDir); - if (!_fromDir.exists()) - { - throw new IllegalArgumentException("BDBStore path '" + fromDir + "' could not be read. " - + "Ensure the path is correct and that the permissions are correct."); - } - - if (!isDirectoryAStoreDir(_fromDir)) - { - throw new IllegalArgumentException("Specified directory '" + fromDir + "' does not contain a valid BDBMessageStore."); - } - - if (toDir == null) - { - _inplace = true; - _toDir = new File(fromDir+"-Inplace"); - } - else - { - _toDir = new File(toDir); - } - - if (_toDir.exists()) - { - if (_interactive) - { - if (toDir == null) - { - System.out.println("Upgrading in place:" + fromDir); - } - else - { - System.out.println("Upgrade destination: '" + toDir + "'"); - } - - if (userInteract("Upgrade destination exists do you wish to replace it?")) - { - if (!FileUtils.delete(_toDir, true)) - { - throw new IllegalArgumentException("Unable to remove upgrade destination '" + _toDir + "'"); - } - } - else - { - throw new IllegalArgumentException("Upgrade destination '" + _toDir + "' already exists. "); - } - } - else - { - if (_force) - { - if (!FileUtils.delete(_toDir, true)) - { - throw new IllegalArgumentException("Unable to remove upgrade destination '" + _toDir + "'"); - } - } - else - { - throw new IllegalArgumentException("Upgrade destination '" + _toDir + "' already exists. "); - } - } - } - - if (!_toDir.mkdirs()) - { - throw new IllegalArgumentException("Upgrade destination '" + _toDir + "' could not be created. " - + "Ensure the path is correct and that the permissions are correct."); - } - - if (backupDir != null) - { - if (backupDir.equals("")) - { - _backupDir = new File(_fromDir.getAbsolutePath().toString() + "-Backup"); - } - else - { - _backupDir = new File(backupDir); - } - } - else - { - _backupDir = null; - } - } - - private static String ANSWER_OPTIONS = " Yes/No/Abort? "; - private static String ANSWER_NO = "no"; - private static String ANSWER_N = "n"; - private static String ANSWER_YES = "yes"; - private static String ANSWER_Y = "y"; - private static String ANSWER_ABORT = "abort"; - private static String ANSWER_A = "a"; - - /** - * Interact with the user via System.in and System.out. If the user wishes to Abort then a RuntimeException is thrown. - * Otherwise the method will return based on their response true=yes false=no. - * - * @param message Message to print out - * - * @return boolean response from user if they wish to proceed - */ - private boolean userInteract(String message) - { - System.out.print(message + ANSWER_OPTIONS); - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - - String input = ""; - try - { - input = br.readLine(); - } - catch (IOException e) - { - input = ""; - } - - if (input.equalsIgnoreCase(ANSWER_Y) || input.equalsIgnoreCase(ANSWER_YES)) - { - return true; - } - else - { - if (input.equalsIgnoreCase(ANSWER_N) || input.equalsIgnoreCase(ANSWER_NO)) - { - return false; - } - else - { - if (input.equalsIgnoreCase(ANSWER_A) || input.equalsIgnoreCase(ANSWER_ABORT)) - { - throw new RuntimeException(USER_ABORTED_PROCESS); - } - } - } - - return userInteract(message); - } - - /** - * Upgrade a Store of a specified version to the latest version. - * - * @param version the version of the current store - * - * @throws Exception - */ - public void upgradeFromVersion(int version) throws Exception - { - upgradeFromVersion(version, _fromDir, _toDir, _backupDir, _force, - _inplace); - } - - /** - * Upgrade a Store of a specified version to the latest version. - * - * @param version the version of the current store - * @param fromDir the directory with the old Store - * @param toDir the directrory to hold the newly Upgraded Store - * @param backupDir the directrory to backup to if required - * @param force suppress all questions - * @param inplace replace the from dir with the upgraded result in toDir - * - * @throws Exception due to Virtualhost/MessageStore.close() being - * rather poor at exception handling - * @throws DatabaseException if there is a problem with the store formats - * @throws AMQException if there is an issue creating Qpid data structures - */ - public void upgradeFromVersion(int version, File fromDir, File toDir, - File backupDir, boolean force, - boolean inplace) throws Exception - { - _logger.info("Located store to upgrade at '" + fromDir + "'"); - - // Verify user has created a backup, giving option to perform backup - if (_interactive) - { - if (!userInteract("Have you performed a DB backup of this store.")) - { - File backup; - if (backupDir == null) - { - backup = new File(fromDir.getAbsolutePath().toString() + "-Backup"); - } - else - { - backup = backupDir; - } - - if (userInteract("Do you wish to perform a DB backup now? " + - "(Store will be backed up to '" + backup.getName() + "')")) - { - performDBBackup(fromDir, backup, force); - } - else - { - if (!userInteract("Are you sure wish to proceed with DB migration without backup? " + - "(For more details of the consequences check the Qpid/BDB Message Store Wiki).")) - { - throw new IllegalArgumentException("Upgrade stopped at user request as no DB Backup performed."); - } - } - } - else - { - if (!inplace) - { - _logger.info("Upgrade will create a new store at '" + toDir + "'"); - } - - _logger.info("Using the contents in the Message Store '" + fromDir + "'"); - - if (!userInteract("Do you wish to proceed?")) - { - throw new IllegalArgumentException("Upgrade stopped as did not wish to proceed"); - } - } - } - else - { - if (backupDir != null) - { - performDBBackup(fromDir, backupDir, force); - } - } - - CurrentActor.set(new BrokerActor(new NullRootMessageLogger())); - - //Create a new messageStore - _newMessageStore = new BDBMessageStore(); - _newMessageStore.configure(toDir, false); - _newMessageStore.start(); - - try - { - //Load the old MessageStore - switch (version) - { - default: - case 4: - _oldMessageStore = new BDBMessageStore(4); - _oldMessageStore.configure(fromDir, true); - _oldMessageStore.start(); - upgradeFromVersion_4(); - break; - case 3: - case 2: - case 1: - throw new IllegalArgumentException(MessageFormat.format(PREVIOUS_STORE_VERSION_UNSUPPORTED, - new Object[] { Integer.toString(version) })); - } - } - finally - { - _newMessageStore.close(); - if (_oldMessageStore != null) - { - _oldMessageStore.close(); - } - // if we are running inplace then swap fromDir and toDir - if (inplace) - { - // Remove original copy - if (FileUtils.delete(fromDir, true)) - { - // Rename upgraded store - toDir.renameTo(fromDir); - } - else - { - throw new RuntimeException("Unable to upgrade inplace as " + - "unable to delete source '" - +fromDir+"', Store upgrade " + - "successfully performed to :" - +toDir); - } - } - } - } - - private void upgradeFromVersion_4() throws AMQException, DatabaseException - { - _logger.info("Starting store upgrade from version 4"); - - //Migrate _exchangeDb - _logger.info("Exchanges"); - - moveContents(_oldMessageStore.getExchangesDb(), _newMessageStore.getExchangesDb(), "Exchange"); - - - TopicExchangeDiscoverer exchangeListVisitor = new TopicExchangeDiscoverer(); - _oldMessageStore.visitExchanges(exchangeListVisitor); - - //Inspect the bindings to gather a list of queues which are probably durable subscriptions, i.e. those - //which have a colon in their name and are bound to the Topic exchanges above - DurableSubDiscoverer durSubQueueListVisitor = - new DurableSubDiscoverer(exchangeListVisitor.getTopicExchanges(), - _oldMessageStore.getBindingTupleBindingFactory().getInstance()); - _oldMessageStore.visitBindings(durSubQueueListVisitor); - - final List<AMQShortString> durableSubQueues = durSubQueueListVisitor.getDurableSubQueues(); - - - //Migrate _queueBindingsDb - _logger.info("Queue Bindings"); - BindingsVisitor bindingsVisitor = new BindingsVisitor(durableSubQueues, - _oldMessageStore.getBindingTupleBindingFactory().getInstance(), _newMessageStore); - _oldMessageStore.visitBindings(bindingsVisitor); - logCount(bindingsVisitor.getVisitedCount(), "Queue Binding"); - - //Migrate _queueDb - _logger.info("Queues"); - - // hold the list of existing queue names - - final TupleBinding<QueueRecord> queueTupleBinding = _oldMessageStore.getQueueTupleBindingFactory().getInstance(); - - QueueVisitor queueVisitor = new QueueVisitor(queueTupleBinding, durableSubQueues, _newMessageStore); - _oldMessageStore.visitQueues(queueVisitor); - final List<AMQShortString> existingQueues = queueVisitor.getExistingQueues(); - - logCount(queueVisitor.getVisitedCount(), "Queue"); - - - // Look for persistent messages stored for non-durable queues - _logger.info("Checking for messages previously sent to non-durable queues"); - - // delivery DB visitor to check message delivery and identify non existing queues - final QueueEntryTB queueEntryTB = new QueueEntryTB(); - MessageDeliveryCheckVisitor messageDeliveryCheckVisitor = - new MessageDeliveryCheckVisitor(queueEntryTB, queueVisitor.getExistingQueues()); - _oldMessageStore.visitDelivery(messageDeliveryCheckVisitor); - - final Set<Long> queueMessages = messageDeliveryCheckVisitor.getQueueMessages(); - - if (messageDeliveryCheckVisitor.getPhantomMessageQueues().isEmpty()) - { - _logger.info("No such messages were found"); - } - else - { - _logger.info("Found " + messageDeliveryCheckVisitor.getVisitedCount()+ " such messages in total"); - - for (Entry<String, HashSet<Long>> phantomQueue : messageDeliveryCheckVisitor.getPhantomMessageQueues().entrySet()) - { - String queueName = phantomQueue.getKey(); - HashSet<Long> messages = phantomQueue.getValue(); - - _logger.info(MessageFormat.format("There are {0} messages which were previously delivered to non-durable queue ''{1}''",messages.size(), queueName)); - - boolean createQueue; - if(!_interactive) - { - createQueue = true; - _logger.info("Running in batch-mode, marking queue as durable to ensure retention of the messages."); - } - else - { - createQueue = userInteract("Do you want to make this queue durable?\n" - + "NOTE: Answering No will result in these messages being discarded!"); - } - - if (createQueue) - { - for (Long messageId : messages) - { - queueMessages.add(messageId); - } - AMQShortString name = new AMQShortString(queueName); - existingQueues.add(name); - QueueRecord record = new QueueRecord(name, null, false, null); - _newMessageStore.createQueue(record); - } - } - } - - - //Migrate _messageMetaDataDb - _logger.info("Message MetaData"); - - final Database newMetaDataDB = _newMessageStore.getMetaDataDb(); - - MetaDataVisitor metaDataVisitor = new MetaDataVisitor(queueMessages, newMetaDataDB, - _oldMessageStore.getMetaDataTupleBindingFactory().getInstance(), - _newMessageStore.getMetaDataTupleBindingFactory().getInstance()); - _oldMessageStore.visitMetaDataDb(metaDataVisitor); - - logCount(metaDataVisitor.getVisitedCount(), "Message MetaData"); - - - //Migrate _messageContentDb - _logger.info("Message Contents"); - final Database newContentDB = _newMessageStore.getContentDb(); - - final TupleBinding<MessageContentKey> oldContentKeyTupleBinding = new MessageContentKeyTB_4(); - final TupleBinding<MessageContentKey> newContentKeyTupleBinding = new MessageContentKeyTB_5(); - final TupleBinding contentTB = new ContentTB(); - - DatabaseVisitor contentVisitor = new ContentVisitor(oldContentKeyTupleBinding, queueMessages, - contentTB, newContentKeyTupleBinding, newContentDB); - _oldMessageStore.visitContentDb(contentVisitor); - - logCount(contentVisitor.getVisitedCount(), "Message Content"); - - - //Migrate _deliveryDb - _logger.info("Delivery Records"); - final Database deliveryDb =_newMessageStore.getDeliveryDb(); - DatabaseVisitor deliveryDbVisitor = new DeliveryDbVisitor(queueEntryTB, existingQueues, deliveryDb); - _oldMessageStore.visitDelivery(deliveryDbVisitor); - logCount(contentVisitor.getVisitedCount(), "Delivery Record"); - } - - /** - * Log the specified count for item in a user friendly way. - * - * @param count of items to log - * @param item description of what is being logged. - */ - private void logCount(int count, String item) - { - _logger.info(" " + count + " " + item + " " + (count == 1 ? "entry" : "entries")); - } - - /** - * @param oldDatabase The old MessageStoreDB to perform the visit on - * @param newDatabase The new MessageStoreDB to copy the data to. - * @param contentName The string name of the content for display purposes. - * - * @throws AMQException Due to createQueue thorwing AMQException - * @throws DatabaseException If there is a problem with the loading of the data - */ - private void moveContents(Database oldDatabase, final Database newDatabase, String contentName) throws AMQException, DatabaseException - { - - DatabaseVisitor moveVisitor = new DatabaseVisitor() - { - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - incrementCount(); - newDatabase.put(null, key, value); - } - }; - - _oldMessageStore.visitDatabase(oldDatabase, moveVisitor); - - logCount(moveVisitor.getVisitedCount(), contentName); - } - - private static void usage() - { - System.out.println("usage: BDBStoreUpgrade:\n [-h|--help] [-q|--quiet] [-f|--force] [-b|--backup <Path to backup-db>] " + - "-i|--input <Path to input-db> [-o|--output <Path to upgraded-db>]"); - } - - private static void help() - { - System.out.println("usage: BDBStoreUpgrade:"); - System.out.println("Required:"); - for (Object obj : _options.getOptions()) - { - Option option = (Option) obj; - if (option.isRequired()) - { - System.out.println("-" + option.getOpt() + "|--" + option.getLongOpt() + "\t\t-\t" + option.getDescription()); - } - } - - System.out.println("\nOptions:"); - for (Object obj : _options.getOptions()) - { - Option option = (Option) obj; - if (!option.isRequired()) - { - System.out.println("--" + option.getLongOpt() + "|-" + option.getOpt() + "\t\t-\t" + option.getDescription()); - } - } - } - - static boolean isDirectoryAStoreDir(File directory) - { - if (directory.isFile()) - { - return false; - } - - for (File file : directory.listFiles()) - { - if (file.isFile()) - { - if (file.getName().endsWith(BDB_FILE_ENDING)) - { - return true; - } - } - } - return false; - } - - static File[] discoverDBStores(File fromDir) - { - if (!fromDir.exists()) - { - throw new IllegalArgumentException("'" + fromDir + "' does not exist unable to upgrade."); - } - - // Ensure we are given a directory - if (fromDir.isFile()) - { - throw new IllegalArgumentException("'" + fromDir + "' is not a directory unable to upgrade."); - } - - // Check to see if we have been given a single directory - if (isDirectoryAStoreDir(fromDir)) - { - return new File[]{fromDir}; - } - - // Check to see if we have been give a directory containing stores. - List<File> stores = new LinkedList<File>(); - - for (File directory : fromDir.listFiles()) - { - if (directory.isDirectory()) - { - if (isDirectoryAStoreDir(directory)) - { - stores.add(directory); - } - } - } - - return stores.toArray(new File[stores.size()]); - } - - private static void performDBBackup(File source, File backup, boolean force) throws Exception - { - if (backup.exists()) - { - if (force) - { - _logger.info("Backup location exists. Forced to remove."); - FileUtils.delete(backup, true); - } - else - { - throw new IllegalArgumentException("Unable to perform backup a backup already exists."); - } - } - - try - { - _logger.info("Backing up '" + source + "' to '" + backup + "'"); - FileUtils.copyRecursive(source, backup); - } - catch (FileNotFoundException e) - { - //Throwing IAE here as this will be reported as a Backup not started - throw new IllegalArgumentException("Unable to perform backup:" + e.getMessage()); - } - catch (FileUtils.UnableToCopyException e) - { - //Throwing exception here as this will be reported as a Failed Backup - throw new Exception("Unable to perform backup due to:" + e.getMessage()); - } - } - - public static void main(String[] args) throws ParseException - { - setOptions(_options); - - final Options helpOptions = new Options(); - setHelpOptions(helpOptions); - - //Display help - boolean displayHelp = false; - try - { - if (new PosixParser().parse(helpOptions, args).hasOption("h")) - { - showHelp(); - } - } - catch (ParseException pe) - { - displayHelp = true; - } - - //Parse commandline for required arguments - try - { - _commandLine = new PosixParser().parse(_options, args); - } - catch (ParseException mae) - { - if (displayHelp) - { - showHelp(); - } - else - { - fatalError(mae.getMessage()); - } - } - - String fromDir = _commandLine.getOptionValue(OPTION_INPUT_SHORT); - String toDir = _commandLine.getOptionValue(OPTION_OUTPUT_SHORT); - String backupDir = _commandLine.getOptionValue(OPTION_BACKUP_SHORT); - - if (backupDir == null && _commandLine.hasOption(OPTION_BACKUP_SHORT)) - { - backupDir = ""; - } - - //Attempt to locate possible Store to upgrade on input path - File[] stores = new File[0]; - try - { - stores = discoverDBStores(new File(fromDir)); - } - catch (IllegalArgumentException iae) - { - fatalError(iae.getMessage()); - } - - boolean interactive = !_commandLine.hasOption(OPTION_QUIET_SHORT); - boolean force = _commandLine.hasOption(OPTION_FORCE_SHORT); - - try{ - for (File store : stores) - { - - // if toDir is null then we are upgrading inplace so we don't need - // to provide an upgraded toDir when upgrading multiple stores. - if (toDir == null || - // Check to see if we are upgrading a store specified in - // fromDir or if the directories are nested. - (stores.length > 0 - && stores[0].toString().length() == fromDir.length())) - { - upgrade(store, toDir, backupDir, interactive, force); - } - else - { - // Add the extra part of path from store to the toDir - upgrade(store, toDir + File.separator + store.toString().substring(fromDir.length()), backupDir, interactive, force); - } - } - } - catch (RuntimeException re) - { - if (!(USER_ABORTED_PROCESS).equals(re.getMessage())) - { - re.printStackTrace(); - _logger.error("Upgrade Failed: " + re.getMessage()); - } - else - { - _logger.error("Upgrade stopped : User aborted"); - } - } - - } - - @SuppressWarnings("static-access") - private static void setOptions(Options options) - { - Option input = - OptionBuilder.isRequired().hasArg().withDescription("Location (Path) of store to upgrade.").withLongOpt(OPTION_INPUT) - .create(OPTION_INPUT_SHORT); - - Option output = - OptionBuilder.hasArg().withDescription("Location (Path) for the upgraded-db to be written.").withLongOpt(OPTION_OUTPUT) - .create(OPTION_OUTPUT_SHORT); - - Option quiet = new Option(OPTION_QUIET_SHORT, OPTION_QUIET, false, "Disable interactive options."); - - Option force = new Option(OPTION_FORCE_SHORT, OPTION_FORCE, false, "Force upgrade removing any existing upgrade target."); - Option backup = - OptionBuilder.hasOptionalArg().withDescription("Location (Path) for the backup-db to be written.").withLongOpt(OPTION_BACKUP) - .create(OPTION_BACKUP_SHORT); - - options.addOption(input); - options.addOption(output); - options.addOption(quiet); - options.addOption(force); - options.addOption(backup); - setHelpOptions(options); - } - - private static void setHelpOptions(Options options) - { - options.addOption(new Option("h", "help", false, "Show this help.")); - } - - static void upgrade(File fromDir, String toDir, String backupDir, boolean interactive, boolean force) - { - - _logger.info("Running BDB Message Store upgrade tool: v" + VERSION); - int version = getStoreVersion(fromDir); - if (!isVersionUpgradable(version)) - { - return; - } - try - { - new BDBStoreUpgrade(fromDir.toString(), toDir, backupDir, interactive, force).upgradeFromVersion(version); - - _logger.info("Upgrade complete."); - } - catch (IllegalArgumentException iae) - { - _logger.error("Upgrade not started due to: " + iae.getMessage()); - } - catch (DatabaseException de) - { - de.printStackTrace(); - _logger.error("Upgrade Failed: " + de.getMessage()); - } - catch (RuntimeException re) - { - if (!(USER_ABORTED_PROCESS).equals(re.getMessage())) - { - re.printStackTrace(); - _logger.error("Upgrade Failed: " + re.getMessage()); - } - else - { - throw re; - } - } - catch (Exception e) - { - e.printStackTrace(); - _logger.error("Upgrade Failed: " + e.getMessage()); - } - } - - /** - * Utility method to verify if store of given version can be upgraded. - * - * @param version - * store version to verify - * @return true if store can be upgraded, false otherwise - */ - protected static boolean isVersionUpgradable(int version) - { - boolean storeUpgradable = false; - if (version == 0) - { - _logger.error("Existing store version is undefined!"); - } - else if (version < LOWEST_SUPPORTED_STORE_VERSION) - { - _logger.error(MessageFormat.format(PREVIOUS_STORE_VERSION_UNSUPPORTED, - new Object[] { Integer.toString(version) })); - } - else if (version == BDBMessageStore.DATABASE_FORMAT_VERSION) - { - _logger.error(MessageFormat.format(STORE_ALREADY_UPGRADED, new Object[] { Integer.toString(version) })); - } - else if (version > BDBMessageStore.DATABASE_FORMAT_VERSION) - { - _logger.error(MessageFormat.format(FOLLOWING_STORE_VERSION_UNSUPPORTED, - new Object[] { Integer.toString(version) })); - } - else - { - _logger.info("Existing store version is " + version); - storeUpgradable = true; - } - return storeUpgradable; - } - - /** - * Detects existing store version by checking list of database in store - * environment - * - * @param fromDir - * store folder - * @return version - */ - public static int getStoreVersion(File fromDir) - { - int version = 0; - EnvironmentConfig envConfig = new EnvironmentConfig(); - envConfig.setAllowCreate(false); - envConfig.setTransactional(false); - envConfig.setReadOnly(true); - Environment environment = null; - try - { - - environment = new Environment(fromDir, envConfig); - List<String> databases = environment.getDatabaseNames(); - for (String name : databases) - { - if (name.startsWith("exchangeDb")) - { - if (name.startsWith("exchangeDb_v")) - { - version = Integer.parseInt(name.substring(12)); - } - else - { - version = 1; - } - break; - } - } - } - catch (Exception e) - { - _logger.error("Failure to open existing database: " + e.getMessage()); - } - finally - { - if (environment != null) - { - try - { - environment.close(); - } - catch (Exception e) - { - // ignoring. It should never happen. - } - } - } - return version; - } - - private static void fatalError(String message) - { - System.out.println(message); - usage(); - System.exit(1); - } - - private static void showHelp() - { - help(); - System.exit(0); - } - - private static class TopicExchangeDiscoverer extends DatabaseVisitor - { - private final List<AMQShortString> topicExchanges = new ArrayList<AMQShortString>(); - private final TupleBinding exchangeTB = new ExchangeTB(); - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - ExchangeRecord exchangeRec = (ExchangeRecord) exchangeTB.entryToObject(value); - AMQShortString type = exchangeRec.getType(); - - if (ExchangeDefaults.TOPIC_EXCHANGE_CLASS.equals(type)) - { - topicExchanges.add(exchangeRec.getNameShortString()); - } - } - - public List<AMQShortString> getTopicExchanges() - { - return topicExchanges; - } - } - - private static class MessageDeliveryCheckVisitor extends DatabaseVisitor - { - private final QueueEntryTB _queueEntryTB; - private final List<AMQShortString> _existingQueues; - - // track all message delivery to existing queues - private final HashSet<Long> _queueMessages = new HashSet<Long>(); - - // hold all non existing queues and their messages IDs - private final HashMap<String, HashSet<Long>> _phantomMessageQueues = new HashMap<String, HashSet<Long>>(); - - - - public MessageDeliveryCheckVisitor(QueueEntryTB queueEntryTB, List<AMQShortString> existingQueues) - { - _queueEntryTB = queueEntryTB; - _existingQueues = existingQueues; - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - QueueEntryKey entryKey = (QueueEntryKey) _queueEntryTB.entryToObject(key); - Long messageId = entryKey.getMessageId(); - AMQShortString queueName = entryKey.getQueueName(); - if (!_existingQueues.contains(queueName)) - { - String name = queueName.asString(); - HashSet<Long> messages = _phantomMessageQueues.get(name); - if (messages == null) - { - messages = new HashSet<Long>(); - _phantomMessageQueues.put(name, messages); - } - messages.add(messageId); - incrementCount(); - } - else - { - _queueMessages.add(messageId); - } - } - - public HashSet<Long> getQueueMessages() - { - return _queueMessages; - } - - public HashMap<String, HashSet<Long>> getPhantomMessageQueues() - { - return _phantomMessageQueues; - } - } - - private static class ContentVisitor extends DatabaseVisitor - { - private long _prevMsgId; //Initialise to invalid value - private int _bytesSeenSoFar; - private final TupleBinding<MessageContentKey> _oldContentKeyTupleBinding; - private final Set<Long> _queueMessages; - private final TupleBinding _contentTB; - private final TupleBinding<MessageContentKey> _newContentKeyTupleBinding; - private final Database _newContentDB; - - public ContentVisitor(TupleBinding<MessageContentKey> oldContentKeyTupleBinding, Set<Long> queueMessages, TupleBinding contentTB, TupleBinding<MessageContentKey> newContentKeyTupleBinding, Database newContentDB) - { - _oldContentKeyTupleBinding = oldContentKeyTupleBinding; - _queueMessages = queueMessages; - _contentTB = contentTB; - _newContentKeyTupleBinding = newContentKeyTupleBinding; - _newContentDB = newContentDB; - _prevMsgId = -1; - _bytesSeenSoFar = 0; - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - incrementCount(); - - //determine the msgId of the current entry - MessageContentKey_4 contentKey = (MessageContentKey_4) _oldContentKeyTupleBinding.entryToObject(key); - long msgId = contentKey.getMessageId(); - - // ONLY copy data if message is delivered to existing queue - if (!_queueMessages.contains(msgId)) - { - return; - } - //if this is a new message, restart the byte offset count. - if(_prevMsgId != msgId) - { - _bytesSeenSoFar = 0; - } - - //determine the content size - ByteBuffer content = (ByteBuffer) _contentTB.entryToObject(value); - int contentSize = content.limit(); - - //create the new key: id + previously seen data count - MessageContentKey_5 newKey = new MessageContentKey_5(msgId, _bytesSeenSoFar); - DatabaseEntry newKeyEntry = new DatabaseEntry(); - _newContentKeyTupleBinding.objectToEntry(newKey, newKeyEntry); - - DatabaseEntry newValueEntry = new DatabaseEntry(); - _contentTB.objectToEntry(content, newValueEntry); - - _newContentDB.put(null, newKeyEntry, newValueEntry); - - _prevMsgId = msgId; - _bytesSeenSoFar += contentSize; - } - } - - private static class DeliveryDbVisitor extends DatabaseVisitor - { - - private final QueueEntryTB _queueEntryTB; - private final List<AMQShortString> _existingQueues; - private final Database _deliveryDb; - - public DeliveryDbVisitor(QueueEntryTB queueEntryTB, List<AMQShortString> existingQueues, Database deliveryDb) - { - _queueEntryTB = queueEntryTB; - _existingQueues = existingQueues; - _deliveryDb = deliveryDb; - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - incrementCount(); - - // get message id from entry key - QueueEntryKey entryKey = (QueueEntryKey) _queueEntryTB.entryToObject(key); - AMQShortString queueName = entryKey.getQueueName(); - - // ONLY copy data if message queue exists - if (_existingQueues.contains(queueName)) - { - _deliveryDb.put(null, key, value); - } - } - } - - private class DurableSubDiscoverer extends DatabaseVisitor - { - private final List<AMQShortString> _durableSubQueues; - private final TupleBinding<BindingRecord> _bindingTB; - private final List<AMQShortString> _topicExchanges; - - - public DurableSubDiscoverer(List<AMQShortString> topicExchanges, TupleBinding<BindingRecord> bindingTB) - { - _durableSubQueues = new ArrayList<AMQShortString>(); - _bindingTB = bindingTB; - _topicExchanges = topicExchanges; - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - BindingRecord bindingRec = _bindingTB.entryToObject(key); - AMQShortString queueName = bindingRec.getQueueName(); - AMQShortString exchangeName = bindingRec.getExchangeName(); - - if (_topicExchanges.contains(exchangeName) && queueName.asString().contains(":")) - { - _durableSubQueues.add(queueName); - } - } - - public List<AMQShortString> getDurableSubQueues() - { - return _durableSubQueues; - } - } - - private static class QueueVisitor extends DatabaseVisitor - { - private final TupleBinding<QueueRecord> _queueTupleBinding; - private final List<AMQShortString> _durableSubQueues; - private final List<AMQShortString> _existingQueues = new ArrayList<AMQShortString>(); - private final BDBMessageStore _newMessageStore; - - public QueueVisitor(TupleBinding<QueueRecord> queueTupleBinding, - List<AMQShortString> durableSubQueues, - BDBMessageStore newMessageStore) - { - _queueTupleBinding = queueTupleBinding; - _durableSubQueues = durableSubQueues; - _newMessageStore = newMessageStore; - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws AMQStoreException - { - QueueRecord queueRec = _queueTupleBinding.entryToObject(value); - AMQShortString queueName = queueRec.getNameShortString(); - - //if the queue name is in the gathered list then set its exclusivity true - if (_durableSubQueues.contains(queueName)) - { - _logger.info("Marking as possible DurableSubscription backing queue: " + queueName); - queueRec.setExclusive(true); - } - - //The simple call to createQueue with the QueueRecord object is sufficient for a v2->v3 upgrade as - //the extra 'exclusive' property in v3 will be defaulted to false in the record creation. - _newMessageStore.createQueue(queueRec); - - incrementCount(); - _existingQueues.add(queueName); - } - - public List<AMQShortString> getExistingQueues() - { - return _existingQueues; - } - } - - private static class BindingsVisitor extends DatabaseVisitor - { - private final List<AMQShortString> _durableSubQueues; - private final BDBMessageStore _newMessageStore; - private final TupleBinding<BindingRecord> _oldBindingTB; - private AMQShortString _selectorFilterKey; - - public BindingsVisitor(List<AMQShortString> durableSubQueues, - TupleBinding<BindingRecord> oldBindingTB, - BDBMessageStore newMessageStore) - { - _oldBindingTB = oldBindingTB; - _durableSubQueues = durableSubQueues; - _newMessageStore = newMessageStore; - _selectorFilterKey = AMQPFilterTypes.JMS_SELECTOR.getValue(); - } - - public void visit(DatabaseEntry key, DatabaseEntry value) throws AMQStoreException - { - //All the information required in binding entries is actually in the *key* not value. - BindingRecord oldBindingRec = _oldBindingTB.entryToObject(key); - - AMQShortString queueName = oldBindingRec.getQueueName(); - AMQShortString exchangeName = oldBindingRec.getExchangeName(); - AMQShortString routingKey = oldBindingRec.getRoutingKey(); - FieldTable arguments = oldBindingRec.getArguments(); - - //if the queue name is in the gathered list then inspect its binding arguments - if (_durableSubQueues.contains(queueName)) - { - if(arguments == null) - { - arguments = new FieldTable(); - } - - if(!arguments.containsKey(_selectorFilterKey)) - { - //add the empty string (i.e. 'no selector') value for the selector argument - arguments.put(_selectorFilterKey, ""); - } - } - - //create the binding in the new store - _newMessageStore.bindQueue( - new BindingRecord(exchangeName, queueName, routingKey, arguments)); - } - } - - private static class MetaDataVisitor extends DatabaseVisitor - { - private final TupleBinding<Object> _oldMetaDataTupleBinding; - private final TupleBinding<Object> _newMetaDataTupleBinding; - private final Set<Long> _queueMessages; - private final Database _newMetaDataDB; - - public MetaDataVisitor(Set<Long> queueMessages, - Database newMetaDataDB, - TupleBinding<Object> oldMetaDataTupleBinding, - TupleBinding<Object> newMetaDataTupleBinding) - { - _queueMessages = queueMessages; - _newMetaDataDB = newMetaDataDB; - _oldMetaDataTupleBinding = oldMetaDataTupleBinding; - _newMetaDataTupleBinding = newMetaDataTupleBinding; - } - - - public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException - { - incrementCount(); - MessageMetaData metaData = (MessageMetaData) _oldMetaDataTupleBinding.entryToObject(value); - - // get message id - Long messageId = TupleBinding.getPrimitiveBinding(Long.class).entryToObject(key); - - // ONLY copy data if message is delivered to existing queue - if (!_queueMessages.contains(messageId)) - { - return; - } - DatabaseEntry newValue = new DatabaseEntry(); - _newMetaDataTupleBinding.objectToEntry(metaData, newValue); - - _newMetaDataDB.put(null, key, newValue); - } - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java deleted file mode 100644 index ef9f60b2c4..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; - -import java.nio.ByteBuffer; - -public class ContentTB extends TupleBinding -{ - public Object entryToObject(TupleInput tupleInput) - { - - final int size = tupleInput.readInt(); - byte[] underlying = new byte[size]; - tupleInput.readFast(underlying); - return ByteBuffer.wrap(underlying); - } - - public void objectToEntry(Object object, TupleOutput tupleOutput) - { - ByteBuffer src = (ByteBuffer) object; - - src = src.slice(); - - byte[] chunkData = new byte[src.limit()]; - src.duplicate().get(chunkData); - - tupleOutput.writeInt(chunkData.length); - tupleOutput.writeFast(chunkData); - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java deleted file mode 100644 index c6a1372d7e..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb; - -import com.sleepycat.je.DatabaseEntry; -import com.sleepycat.je.DatabaseException; - -import org.apache.qpid.AMQStoreException; - -/** Visitor Interface so that each DatabaseEntry for a database can easily be processed. */ -public abstract class DatabaseVisitor -{ - private int _count; - - abstract public void visit(DatabaseEntry entry, DatabaseEntry value) throws AMQStoreException, DatabaseException; - - public final int getVisitedCount() - { - return _count; - } - - protected final void incrementCount() - { - _count++; - } - - public void resetVisitCount() - { - _count = 0; - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java deleted file mode 100644 index c7a409f8e1..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; -import org.apache.log4j.Logger; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.store.berkeleydb.records.ExchangeRecord; - -public class ExchangeTB extends TupleBinding -{ - private static final Logger _log = Logger.getLogger(ExchangeTB.class); - - public ExchangeTB() - { - } - - public Object entryToObject(TupleInput tupleInput) - { - - AMQShortString name = AMQShortStringEncoding.readShortString(tupleInput); - AMQShortString typeName = AMQShortStringEncoding.readShortString(tupleInput); - - boolean autoDelete = tupleInput.readBoolean(); - - return new ExchangeRecord(name, typeName, autoDelete); - } - - public void objectToEntry(Object object, TupleOutput tupleOutput) - { - ExchangeRecord exchange = (ExchangeRecord) object; - - AMQShortStringEncoding.writeShortString(exchange.getNameShortString(), tupleOutput); - AMQShortStringEncoding.writeShortString(exchange.getType(), tupleOutput); - - tupleOutput.writeBoolean(exchange.isAutoDelete()); - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/PreparedTransaction.java index 30357c97d4..eb5c4677ff 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/PreparedTransaction.java @@ -18,27 +18,29 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.keys; -import org.apache.qpid.server.store.berkeleydb.MessageContentKey; +package org.apache.qpid.server.store.berkeleydb.entry; -public class MessageContentKey_4 extends MessageContentKey +import org.apache.qpid.server.store.Transaction; + +public class PreparedTransaction { - private int _chunkNum; + private final Transaction.Record[] _enqueues; + private final Transaction.Record[] _dequeues; - public MessageContentKey_4(long messageId, int chunkNo) + public PreparedTransaction(Transaction.Record[] enqueues, Transaction.Record[] dequeues) { - super(messageId); - _chunkNum = chunkNo; + _enqueues = enqueues; + _dequeues = dequeues; } - public int getChunk() + public Transaction.Record[] getEnqueues() { - return _chunkNum; + return _enqueues; } - public void setChunk(int chunk) + public Transaction.Record[] getDequeues() { - this._chunkNum = chunk; + return _dequeues; } -}
\ No newline at end of file +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/QueueEntryKey.java index 005e8d4604..e7cf93ff7a 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/QueueEntryKey.java @@ -18,25 +18,28 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb; +package org.apache.qpid.server.store.berkeleydb.entry; -public class MessageContentKey +import java.util.UUID; + +public class QueueEntryKey { + private UUID _queueId; private long _messageId; - - public MessageContentKey(long messageId) + + public QueueEntryKey(UUID queueId, long messageId) { + _queueId = queueId; _messageId = messageId; - } - - - public long getMessageId() + } + + public UUID getQueueId() { - return _messageId; + return _queueId; } - public void setMessageId(long messageId) + public long getMessageId() { - this._messageId = messageId; + return _messageId; } -}
\ No newline at end of file +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/Xid.java index a1a7fe80b5..bed7575f9a 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/Xid.java @@ -18,27 +18,35 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.keys; -import org.apache.qpid.server.store.berkeleydb.MessageContentKey; +package org.apache.qpid.server.store.berkeleydb.entry; -public class MessageContentKey_5 extends MessageContentKey +public class Xid { - private int _offset; - public MessageContentKey_5(long messageId, int chunkNo) + private final long _format; + private final byte[] _globalId; + private final byte[] _branchId; + + public Xid(long format, byte[] globalId, byte[] branchId) + { + _format = format; + _globalId = globalId; + _branchId = branchId; + } + + public long getFormat() { - super(messageId); - _offset = chunkNo; + return _format; } - public int getOffset() + public byte[] getGlobalId() { - return _offset; + return _globalId; } - public void setOffset(int chunk) + public byte[] getBranchId() { - this._offset = chunk; + return _branchId; } -}
\ No newline at end of file +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/BindingRecord.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/BindingRecord.java deleted file mode 100644 index 394a6ea85c..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/BindingRecord.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.records; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; - -public class BindingRecord extends Object -{ - private final AMQShortString _exchangeName; - private final AMQShortString _queueName; - private final AMQShortString _routingKey; - private final FieldTable _arguments; - - public BindingRecord(AMQShortString exchangeName, AMQShortString queueName, AMQShortString routingKey, FieldTable arguments) - { - _exchangeName = exchangeName; - _queueName = queueName; - _routingKey = routingKey; - _arguments = arguments; - } - - - public AMQShortString getExchangeName() - { - return _exchangeName; - } - - public AMQShortString getQueueName() - { - return _queueName; - } - - public AMQShortString getRoutingKey() - { - return _routingKey; - } - - public FieldTable getArguments() - { - return _arguments; - } - -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java deleted file mode 100644 index f20367e33b..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.records; - -import org.apache.qpid.framing.AMQShortString; - -public class ExchangeRecord extends Object -{ - private final AMQShortString _exchangeName; - private final AMQShortString _exchangeType; - private final boolean _autoDelete; - - public ExchangeRecord(AMQShortString exchangeName, AMQShortString exchangeType, boolean autoDelete) - { - _exchangeName = exchangeName; - _exchangeType = exchangeType; - _autoDelete = autoDelete; - } - - public AMQShortString getNameShortString() - { - return _exchangeName; - } - - public AMQShortString getType() - { - return _exchangeType; - } - - public boolean isAutoDelete() - { - return _autoDelete; - } - -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java deleted file mode 100644 index fbe10433ca..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.records; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; - -public class QueueRecord extends Object -{ - private final AMQShortString _queueName; - private final AMQShortString _owner; - private final FieldTable _arguments; - private boolean _exclusive; - - public QueueRecord(AMQShortString queueName, AMQShortString owner, boolean exclusive, FieldTable arguments) - { - _queueName = queueName; - _owner = owner; - _exclusive = exclusive; - _arguments = arguments; - } - - public AMQShortString getNameShortString() - { - return _queueName; - } - - public AMQShortString getOwner() - { - return _owner; - } - - public boolean isExclusive() - { - return _exclusive; - } - - public void setExclusive(boolean exclusive) - { - _exclusive = exclusive; - } - - public FieldTable getArguments() - { - return _arguments; - } - -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBinding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBinding.java new file mode 100644 index 0000000000..8b84a4c9bb --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBinding.java @@ -0,0 +1,37 @@ +package org.apache.qpid.server.store.berkeleydb.tuple; + +import org.apache.qpid.server.store.ConfiguredObjectRecord; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class ConfiguredObjectBinding extends TupleBinding<ConfiguredObjectRecord> +{ + private static final ConfiguredObjectBinding INSTANCE = new ConfiguredObjectBinding(); + + public static ConfiguredObjectBinding getInstance() + { + return INSTANCE; + } + + /** non-public constructor forces getInstance instead */ + private ConfiguredObjectBinding() + { + } + + public ConfiguredObjectRecord entryToObject(TupleInput tupleInput) + { + String type = tupleInput.readString(); + String json = tupleInput.readString(); + ConfiguredObjectRecord configuredObject = new ConfiguredObjectRecord(null, type, json); + return configuredObject; + } + + public void objectToEntry(ConfiguredObjectRecord object, TupleOutput tupleOutput) + { + tupleOutput.writeString(object.getType()); + tupleOutput.writeString(object.getAttributes()); + } + +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ContentBinding.java index 351b5b4f5b..9154ca114a 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ContentBinding.java @@ -18,32 +18,35 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb; +package org.apache.qpid.server.store.berkeleydb.tuple; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; import com.sleepycat.bind.tuple.TupleOutput; -import org.apache.log4j.Logger; -import org.apache.qpid.framing.AMQShortString; - -public class AMQShortStringTB extends TupleBinding +public class ContentBinding extends TupleBinding<byte[]> { - private static final Logger _log = Logger.getLogger(AMQShortStringTB.class); - + private static final ContentBinding INSTANCE = new ContentBinding(); - public AMQShortStringTB() + public static ContentBinding getInstance() { + return INSTANCE; } - public Object entryToObject(TupleInput tupleInput) + /** private constructor forces getInstance instead */ + private ContentBinding() { } + + @Override + public byte[] entryToObject(final TupleInput input) { - return AMQShortStringEncoding.readShortString(tupleInput); + byte[] data = new byte[input.available()]; + input.read(data); + return data; } - public void objectToEntry(Object object, TupleOutput tupleOutput) + @Override + public void objectToEntry(final byte[] data, final TupleOutput output) { - AMQShortStringEncoding.writeShortString((AMQShortString)object, tupleOutput); + output.write(data); } - } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/MessageMetaDataBinding.java index 4e124a03e3..2e6c8d5666 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/MessageMetaDataBinding.java @@ -18,8 +18,11 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.tuple; +import java.nio.ByteBuffer; + +import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; import com.sleepycat.bind.tuple.TupleOutput; @@ -29,17 +32,26 @@ import org.apache.qpid.server.store.StorableMessageMetaData; /** * Handles the mapping to and from message meta data */ -public class MessageMetaDataTB_5 extends MessageMetaDataTB_4 +public class MessageMetaDataBinding extends TupleBinding<StorableMessageMetaData> { + private static final MessageMetaDataBinding INSTANCE = new MessageMetaDataBinding(); + + public static MessageMetaDataBinding getInstance() + { + return INSTANCE; + } + + /** private constructor forces getInstance instead */ + private MessageMetaDataBinding() { } @Override - public Object entryToObject(TupleInput tupleInput) + public StorableMessageMetaData entryToObject(TupleInput tupleInput) { final int bodySize = tupleInput.readInt(); byte[] dataAsBytes = new byte[bodySize]; tupleInput.readFast(dataAsBytes); - java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes); + ByteBuffer buf = ByteBuffer.wrap(dataAsBytes); buf.position(1); buf = buf.slice(); MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]]; @@ -49,14 +61,12 @@ public class MessageMetaDataTB_5 extends MessageMetaDataTB_4 } @Override - public void objectToEntry(Object object, TupleOutput tupleOutput) + public void objectToEntry(StorableMessageMetaData metaData, TupleOutput tupleOutput) { - StorableMessageMetaData metaData = (StorableMessageMetaData) object; - final int bodySize = 1 + metaData.getStorableSize(); byte[] underlying = new byte[bodySize]; underlying[0] = (byte) metaData.getType().ordinal(); - java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(underlying); + ByteBuffer buf = ByteBuffer.wrap(underlying); buf.position(1); buf = buf.slice(); diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/PreparedTransactionBinding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/PreparedTransactionBinding.java new file mode 100644 index 0000000000..09f2c50e2d --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/PreparedTransactionBinding.java @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.tuple; + +import java.util.UUID; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.Transaction; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.store.berkeleydb.entry.PreparedTransaction; + +public class PreparedTransactionBinding extends TupleBinding<PreparedTransaction> +{ + @Override + public PreparedTransaction entryToObject(TupleInput input) + { + Transaction.Record[] enqueues = readRecords(input); + + Transaction.Record[] dequeues = readRecords(input); + + return new PreparedTransaction(enqueues, dequeues); + } + + private Transaction.Record[] readRecords(TupleInput input) + { + Transaction.Record[] records = new Transaction.Record[input.readInt()]; + for(int i = 0; i < records.length; i++) + { + records[i] = new RecordImpl(new UUID(input.readLong(), input.readLong()), input.readLong()); + } + return records; + } + + @Override + public void objectToEntry(PreparedTransaction preparedTransaction, TupleOutput output) + { + writeRecords(preparedTransaction.getEnqueues(), output); + writeRecords(preparedTransaction.getDequeues(), output); + + } + + private void writeRecords(Transaction.Record[] records, TupleOutput output) + { + if(records == null) + { + output.writeInt(0); + } + else + { + output.writeInt(records.length); + for(Transaction.Record record : records) + { + UUID id = record.getQueue().getId(); + output.writeLong(id.getMostSignificantBits()); + output.writeLong(id.getLeastSignificantBits()); + output.writeLong(record.getMessage().getMessageNumber()); + } + } + } + + private static class RecordImpl implements Transaction.Record, TransactionLogResource, EnqueableMessage + { + + private long _messageNumber; + private UUID _queueId; + + public RecordImpl(UUID queueId, long messageNumber) + { + _messageNumber = messageNumber; + _queueId = queueId; + } + + public TransactionLogResource getQueue() + { + return this; + } + + public EnqueableMessage getMessage() + { + return this; + } + + public long getMessageNumber() + { + return _messageNumber; + } + + public boolean isPersistent() + { + return true; + } + + public StoredMessage<?> getStoredMessage() + { + throw new UnsupportedOperationException(); + } + + @Override + public UUID getId() + { + return _queueId; + } + } +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/QueueEntryBinding.java index a4ed25c0ed..22d0ede31f 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/QueueEntryBinding.java @@ -18,29 +18,42 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.tuple; + +import java.util.UUID; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; import com.sleepycat.bind.tuple.TupleOutput; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; -import org.apache.qpid.server.store.berkeleydb.QueueEntryKey; +import org.apache.qpid.server.store.berkeleydb.entry.QueueEntryKey; -public class QueueEntryTB extends TupleBinding<QueueEntryKey> +public class QueueEntryBinding extends TupleBinding<QueueEntryKey> { + + private static final QueueEntryBinding INSTANCE = new QueueEntryBinding(); + + public static QueueEntryBinding getInstance() + { + return INSTANCE; + } + + /** private constructor forces getInstance instead */ + private QueueEntryBinding() { } + public QueueEntryKey entryToObject(TupleInput tupleInput) { - AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); + UUID queueId = new UUID(tupleInput.readLong(), tupleInput.readLong()); long messageId = tupleInput.readLong(); - return new QueueEntryKey(queueName, messageId); + return new QueueEntryKey(queueId, messageId); } public void objectToEntry(QueueEntryKey mk, TupleOutput tupleOutput) { - AMQShortStringEncoding.writeShortString(mk.getQueueName(),tupleOutput); + UUID uuid = mk.getQueueId(); + tupleOutput.writeLong(uuid.getMostSignificantBits()); + tupleOutput.writeLong(uuid.getLeastSignificantBits()); tupleOutput.writeLong(mk.getMessageId()); } }
\ No newline at end of file diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StringMapBinding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/StringMapBinding.java index f8fd39e127..15f31953f4 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StringMapBinding.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/StringMapBinding.java @@ -18,7 +18,7 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb; +package org.apache.qpid.server.store.berkeleydb.tuple; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; @@ -29,9 +29,8 @@ import java.util.Map; public class StringMapBinding extends TupleBinding<Map<String,String>> { - private static final StringMapBinding INSTANCE = new StringMapBinding(); - + public Map<String, String> entryToObject(final TupleInput tupleInput) { int entries = tupleInput.readInt(); @@ -43,7 +42,6 @@ public class StringMapBinding extends TupleBinding<Map<String,String>> return map; } - public void objectToEntry(final Map<String, String> stringStringMap, final TupleOutput tupleOutput) { tupleOutput.writeInt(stringStringMap.size()); diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/UUIDTupleBinding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/UUIDTupleBinding.java index c1a5d473f0..f8657cdd49 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/UUIDTupleBinding.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/UUIDTupleBinding.java @@ -18,7 +18,7 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb; +package org.apache.qpid.server.store.berkeleydb.tuple; import com.sleepycat.bind.tuple.TupleBinding; import com.sleepycat.bind.tuple.TupleInput; @@ -29,7 +29,7 @@ import java.util.UUID; public class UUIDTupleBinding extends TupleBinding<UUID> { private static final UUIDTupleBinding INSTANCE = new UUIDTupleBinding(); - + public UUID entryToObject(final TupleInput tupleInput) { return new UUID(tupleInput.readLong(), tupleInput.readLong()); @@ -38,13 +38,11 @@ public class UUIDTupleBinding extends TupleBinding<UUID> public void objectToEntry(final UUID uuid, final TupleOutput tupleOutput) { tupleOutput.writeLong(uuid.getMostSignificantBits()); - tupleOutput.writeLong(uuid.getLeastSignificantBits()); + tupleOutput.writeLong(uuid.getLeastSignificantBits()); } public static UUIDTupleBinding getInstance() { return INSTANCE; } - - } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/XidBinding.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/XidBinding.java new file mode 100644 index 0000000000..01a5b75fef --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/XidBinding.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.tuple; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +import org.apache.qpid.server.store.berkeleydb.entry.Xid; + +public class XidBinding extends TupleBinding<Xid> +{ + + private static final XidBinding INSTANCE = new XidBinding(); + + public static XidBinding getInstance() + { + return INSTANCE; + } + + /** private constructor forces getInstance instead */ + private XidBinding() { } + + @Override + public Xid entryToObject(TupleInput input) + { + long format = input.readLong(); + byte[] globalId = new byte[input.readInt()]; + input.readFast(globalId); + byte[] branchId = new byte[input.readInt()]; + input.readFast(branchId); + return new Xid(format,globalId,branchId); + } + + @Override + public void objectToEntry(Xid xid, TupleOutput output) + { + output.writeLong(xid.getFormat()); + output.writeInt(xid.getGlobalId() == null ? 0 : xid.getGlobalId().length); + if(xid.getGlobalId() != null) + { + output.write(xid.getGlobalId()); + } + output.writeInt(xid.getBranchId() == null ? 0 : xid.getBranchId().length); + if(xid.getBranchId() != null) + { + output.write(xid.getBranchId()); + } + + } +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java deleted file mode 100644 index c6a5e63bc8..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; -import com.sleepycat.je.DatabaseException; -import org.apache.log4j.Logger; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; -import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; -import org.apache.qpid.server.store.berkeleydb.records.BindingRecord; - -public class BindingTuple_4 extends TupleBinding<BindingRecord> implements BindingTuple -{ - protected static final Logger _log = Logger.getLogger(BindingTuple.class); - - public BindingTuple_4() - { - super(); - } - - public BindingRecord entryToObject(TupleInput tupleInput) - { - AMQShortString exchangeName = AMQShortStringEncoding.readShortString(tupleInput); - AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); - AMQShortString routingKey = AMQShortStringEncoding.readShortString(tupleInput); - - FieldTable arguments; - - // Addition for Version 2 of this table - try - { - arguments = FieldTableEncoding.readFieldTable(tupleInput); - } - catch (DatabaseException e) - { - _log.error("Unable to create binding: " + e, e); - return null; - } - - return new BindingRecord(exchangeName, queueName, routingKey, arguments); - } - - public void objectToEntry(BindingRecord binding, TupleOutput tupleOutput) - { - AMQShortStringEncoding.writeShortString(binding.getExchangeName(), tupleOutput); - AMQShortStringEncoding.writeShortString(binding.getQueueName(), tupleOutput); - AMQShortStringEncoding.writeShortString(binding.getRoutingKey(), tupleOutput); - - // Addition for Version 2 of this table - FieldTableEncoding.writeFieldTable(binding.getArguments(), tupleOutput); - } - -}
\ No newline at end of file diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java deleted file mode 100644 index df857df31a..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; - -import org.apache.qpid.server.store.berkeleydb.MessageContentKey; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_4; - -public class MessageContentKeyTB_4 extends TupleBinding<MessageContentKey> -{ - - public MessageContentKey entryToObject(TupleInput tupleInput) - { - long messageId = tupleInput.readLong(); - int chunk = tupleInput.readInt(); - return new MessageContentKey_4(messageId, chunk); - } - - public void objectToEntry(MessageContentKey object, TupleOutput tupleOutput) - { - final MessageContentKey_4 mk = (MessageContentKey_4) object; - tupleOutput.writeLong(mk.getMessageId()); - tupleOutput.writeInt(mk.getChunk()); - } - -}
\ No newline at end of file diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java deleted file mode 100644 index 17f88e1c2b..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; - -import org.apache.qpid.server.store.berkeleydb.MessageContentKey; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_5; - -public class MessageContentKeyTB_5 extends TupleBinding<MessageContentKey> -{ - public MessageContentKey entryToObject(TupleInput tupleInput) - { - long messageId = tupleInput.readLong(); - int offset = tupleInput.readInt(); - return new MessageContentKey_5(messageId, offset); - } - - public void objectToEntry(MessageContentKey object, TupleOutput tupleOutput) - { - final MessageContentKey_5 mk = (MessageContentKey_5) object; - tupleOutput.writeLong(mk.getMessageId()); - tupleOutput.writeInt(mk.getOffset()); - } - -}
\ No newline at end of file diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java deleted file mode 100644 index 4a320f49c9..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; - -import org.apache.qpid.server.store.berkeleydb.MessageContentKey; - -public class MessageContentKeyTupleBindingFactory extends TupleBindingFactory<MessageContentKey> -{ - public MessageContentKeyTupleBindingFactory(int version) - { - super(version); - } - - public TupleBinding<MessageContentKey> getInstance() - { - switch (getVersion()) - { - default: - case 5: - return new MessageContentKeyTB_5(); - case 4: - return new MessageContentKeyTB_4(); - } - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java deleted file mode 100644 index bdd806bb81..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; -import org.apache.log4j.Logger; - -import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQFrameDecodingException; -import org.apache.qpid.framing.AMQProtocolVersionException; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ContentHeaderBody; -import org.apache.qpid.framing.abstraction.MessagePublishInfo; -import org.apache.qpid.server.message.MessageMetaData; -import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -/** - * Handles the mapping to and from 0-8/0-9 message meta data - */ -public class MessageMetaDataTB_4 extends TupleBinding<Object> -{ - private static final Logger _log = Logger.getLogger(MessageMetaDataTB_4.class); - - public MessageMetaDataTB_4() - { - } - - public Object entryToObject(TupleInput tupleInput) - { - try - { - final MessagePublishInfo publishBody = readMessagePublishInfo(tupleInput); - final ContentHeaderBody contentHeaderBody = readContentHeaderBody(tupleInput); - final int contentChunkCount = tupleInput.readInt(); - - return new MessageMetaData(publishBody, contentHeaderBody, contentChunkCount); - } - catch (Exception e) - { - _log.error("Error converting entry to object: " + e, e); - // annoyingly just have to return null since we cannot throw - return null; - } - } - - public void objectToEntry(Object object, TupleOutput tupleOutput) - { - MessageMetaData message = (MessageMetaData) object; - try - { - writeMessagePublishInfo(message.getMessagePublishInfo(), tupleOutput); - } - catch (AMQException e) - { - // can't do anything else since the BDB interface precludes throwing any exceptions - // in practice we should never get an exception - throw new RuntimeException("Error converting object to entry: " + e, e); - } - writeContentHeader(message.getContentHeaderBody(), tupleOutput); - tupleOutput.writeInt(message.getContentChunkCount()); - } - - private MessagePublishInfo readMessagePublishInfo(TupleInput tupleInput) - { - - final AMQShortString exchange = AMQShortStringEncoding.readShortString(tupleInput); - final AMQShortString routingKey = AMQShortStringEncoding.readShortString(tupleInput); - final boolean mandatory = tupleInput.readBoolean(); - final boolean immediate = tupleInput.readBoolean(); - - return new MessagePublishInfo() - { - - public AMQShortString getExchange() - { - return exchange; - } - - public void setExchange(AMQShortString exchange) - { - - } - - public boolean isImmediate() - { - return immediate; - } - - public boolean isMandatory() - { - return mandatory; - } - - public AMQShortString getRoutingKey() - { - return routingKey; - } - } ; - - } - - private ContentHeaderBody readContentHeaderBody(TupleInput tupleInput) throws AMQFrameDecodingException, AMQProtocolVersionException - { - int bodySize = tupleInput.readInt(); - byte[] underlying = new byte[bodySize]; - tupleInput.readFast(underlying); - - try - { - return ContentHeaderBody.createFromBuffer(new DataInputStream(new ByteArrayInputStream(underlying)), bodySize); - } - catch (IOException e) - { - throw new AMQFrameDecodingException(null, e.getMessage(), e); - } - } - - private void writeMessagePublishInfo(MessagePublishInfo publishBody, TupleOutput tupleOutput) throws AMQException - { - - AMQShortStringEncoding.writeShortString(publishBody.getExchange(), tupleOutput); - AMQShortStringEncoding.writeShortString(publishBody.getRoutingKey(), tupleOutput); - tupleOutput.writeBoolean(publishBody.isMandatory()); - tupleOutput.writeBoolean(publishBody.isImmediate()); - } - - private void writeContentHeader(ContentHeaderBody headerBody, TupleOutput tupleOutput) - { - // write out the content header body - final int bodySize = headerBody.getSize(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(bodySize); - try - { - headerBody.writePayload(new DataOutputStream(baos)); - tupleOutput.writeInt(bodySize); - tupleOutput.writeFast(baos.toByteArray()); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java deleted file mode 100644 index cb742e76a1..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; - -public class MessageMetaDataTupleBindingFactory extends TupleBindingFactory<Object> -{ - public MessageMetaDataTupleBindingFactory(int version) - { - super(version); - } - - public TupleBinding<Object> getInstance() - { - switch (getVersion()) - { - default: - case 5: - return new MessageMetaDataTB_5(); - case 4: - return new MessageMetaDataTB_4(); - } - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java deleted file mode 100644 index a189786885..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; - -import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; - -public class QueueTupleBindingFactory extends TupleBindingFactory<QueueRecord> -{ - - public QueueTupleBindingFactory(int version) - { - super(version); - } - - public TupleBinding<QueueRecord> getInstance() - { - switch (getVersion()) - { - default: - case 5: - return new QueueTuple_5(); - case 4: - return new QueueTuple_4(); - } - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java deleted file mode 100644 index d2ba4dbbca..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; -import com.sleepycat.je.DatabaseException; -import org.apache.log4j.Logger; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; -import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; -import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; - -public class QueueTuple_4 extends TupleBinding<QueueRecord> implements QueueTuple -{ - private static final Logger _logger = Logger.getLogger(QueueTuple_4.class); - - public QueueTuple_4() - { - super(); - } - - public QueueRecord entryToObject(TupleInput tupleInput) - { - try - { - AMQShortString name = AMQShortStringEncoding.readShortString(tupleInput); - AMQShortString owner = AMQShortStringEncoding.readShortString(tupleInput); - // Addition for Version 2 of this table, read the queue arguments - FieldTable arguments = FieldTableEncoding.readFieldTable(tupleInput); - - return new QueueRecord(name, owner, false, arguments); - } - catch (DatabaseException e) - { - _logger.error("Unable to create binding: " + e, e); - return null; - } - - } - - public void objectToEntry(QueueRecord queue, TupleOutput tupleOutput) - { - AMQShortStringEncoding.writeShortString(queue.getNameShortString(), tupleOutput); - AMQShortStringEncoding.writeShortString(queue.getOwner(), tupleOutput); - // Addition for Version 2 of this table, store the queue arguments - FieldTableEncoding.writeFieldTable(queue.getArguments(), tupleOutput); - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java deleted file mode 100644 index c9094a132d..0000000000 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.apache.qpid.server.store.berkeleydb.tuples; - -import com.sleepycat.bind.tuple.TupleInput; -import com.sleepycat.bind.tuple.TupleOutput; -import com.sleepycat.je.DatabaseException; -import org.apache.log4j.Logger; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; -import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; -import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; - -public class QueueTuple_5 extends QueueTuple_4 -{ - private static final Logger _logger = Logger.getLogger(QueueTuple_5.class); - - public QueueTuple_5() - { - super(); - } - - public QueueRecord entryToObject(TupleInput tupleInput) - { - try - { - AMQShortString name = AMQShortStringEncoding.readShortString(tupleInput); - AMQShortString owner = AMQShortStringEncoding.readShortString(tupleInput); - // Addition for Version 2 of this table, read the queue arguments - FieldTable arguments = FieldTableEncoding.readFieldTable(tupleInput); - // Addition for Version 3 of this table, read the queue exclusivity - boolean exclusive = tupleInput.readBoolean(); - - return new QueueRecord(name, owner, exclusive, arguments); - } - catch (DatabaseException e) - { - _logger.error("Unable to create binding: " + e, e); - return null; - } - - } - - public void objectToEntry(QueueRecord queue, TupleOutput tupleOutput) - { - AMQShortStringEncoding.writeShortString(queue.getNameShortString(), tupleOutput); - AMQShortStringEncoding.writeShortString(queue.getOwner(), tupleOutput); - // Addition for Version 2 of this table, store the queue arguments - FieldTableEncoding.writeFieldTable(queue.getArguments(), tupleOutput); - // Addition for Version 3 of this table, store the queue exclusivity - tupleOutput.writeBoolean(queue.isExclusive()); - } -} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractStoreUpgrade.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractStoreUpgrade.java new file mode 100644 index 0000000000..43aa5aa2b4 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractStoreUpgrade.java @@ -0,0 +1,77 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.util.List; + +import org.apache.log4j.Logger; + +import com.sleepycat.je.Database; +import com.sleepycat.je.Environment; +import com.sleepycat.je.Transaction; + +public abstract class AbstractStoreUpgrade implements StoreUpgrade +{ + private static final Logger _logger = Logger.getLogger(AbstractStoreUpgrade.class); + + protected void reportFinished(Environment environment, int version) + { + _logger.info("Completed upgrade to version " + version); + if (_logger.isDebugEnabled()) + { + _logger.debug("Upgraded:"); + reportDatabaseRowCount(environment); + } + } + + private void reportDatabaseRowCount(Environment environment) + { + List<String> databases = environment.getDatabaseNames(); + for (String database : databases) + { + _logger.debug(" " + getRowCount(database, environment) + " rows in " + database); + } + } + + protected void reportStarting(Environment environment, int version) + { + _logger.info("Starting store upgrade from version " + version); + if (_logger.isDebugEnabled()) + { + _logger.debug("Upgrading:"); + reportDatabaseRowCount(environment); + } + } + + private long getRowCount(String databaseName, Environment environment) + { + DatabaseCallable<Long> operation = new DatabaseCallable<Long>() + { + @Override + public Long call(Database sourceDatabase, Database targetDatabase, Transaction transaction) + { + return sourceDatabase.count(); + } + }; + return new DatabaseTemplate(environment, databaseName, null).call(operation); + } + +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorOperation.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorOperation.java new file mode 100644 index 0000000000..925e40ea93 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorOperation.java @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import org.apache.log4j.Logger; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.Transaction; + +public abstract class CursorOperation implements DatabaseRunnable +{ + private static final Logger _logger = Logger.getLogger(CursorOperation.class); + + private CursorTemplate _template; + private long _rowCount; + private long _processedRowCount; + + @Override + public void run(final Database sourceDatabase, final Database targetDatabase, final Transaction transaction) + { + _rowCount = sourceDatabase.count(); + _template = new CursorTemplate(sourceDatabase, transaction, new DatabaseEntryCallback() + { + @Override + public void processEntry(final Database database, final Transaction transaction, final DatabaseEntry key, + final DatabaseEntry value) + { + _processedRowCount++; + CursorOperation.this.processEntry(database, targetDatabase, transaction, key, value); + if (getProcessedCount() % 1000 == 0) + { + _logger.info("Processed " + getProcessedCount() + " records of " + getRowCount() + "."); + } + } + + }); + _template.processEntries(); + } + + public void abort() + { + if (_template != null) + { + _template.abort(); + } + } + + public boolean deleteCurrent() + { + if (_template != null) + { + return _template.deleteCurrent(); + } + return false; + } + + public long getRowCount() + { + return _rowCount; + } + + public long getProcessedCount() + { + return _processedRowCount; + } + + public abstract void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value); + +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorTemplate.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorTemplate.java new file mode 100644 index 0000000000..0b14080486 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorTemplate.java @@ -0,0 +1,75 @@ +package org.apache.qpid.server.store.berkeleydb.upgrade; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +import com.sleepycat.je.Cursor; +import com.sleepycat.je.CursorConfig; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.LockMode; +import com.sleepycat.je.OperationStatus; +import com.sleepycat.je.Transaction; + +public class CursorTemplate +{ + private Database _database; + private Transaction _transaction; + private DatabaseEntryCallback _databaseEntryCallback; + private Cursor _cursor; + private boolean _iterating; + + public CursorTemplate(Database database, Transaction transaction, DatabaseEntryCallback databaseEntryCallback) + { + _database = database; + _transaction = transaction; + _databaseEntryCallback = databaseEntryCallback; + } + + public void processEntries() + { + _cursor = _database.openCursor(_transaction, CursorConfig.READ_COMMITTED); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + + try + { + _iterating = true; + while (_iterating && _cursor.getNext(key, value, LockMode.DEFAULT) == OperationStatus.SUCCESS) + { + _databaseEntryCallback.processEntry(_database, _transaction, key, value); + } + } + finally + { + _cursor.close(); + } + } + + public boolean deleteCurrent() + { + return _cursor.delete() == OperationStatus.SUCCESS; + } + + public void abort() + { + _iterating = false; + } +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseCallable.java index affa9a271d..bf5462ef48 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseCallable.java @@ -18,8 +18,12 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.upgrade; -public interface QueueTuple +import com.sleepycat.je.Database; +import com.sleepycat.je.Transaction; + +public interface DatabaseCallable<T> { + public T call(Database sourceDatabase, Database targetDatabase, Transaction transaction); } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseEntryCallback.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseEntryCallback.java new file mode 100644 index 0000000000..8ac22e5dfb --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseEntryCallback.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.Transaction; + +public interface DatabaseEntryCallback +{ + void processEntry(Database database, Transaction transaction, DatabaseEntry key, DatabaseEntry value); +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseRunnable.java index 97b1398e10..3e9e6a3497 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseRunnable.java @@ -18,23 +18,13 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.upgrade; -import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.je.Database; +import com.sleepycat.je.Transaction; -public abstract class TupleBindingFactory<E> +public interface DatabaseRunnable { - private final int _version; + public void run(Database sourceDatabase, Database targetDatabase, Transaction transaction); - public TupleBindingFactory(int version) - { - _version = version; - } - - public abstract TupleBinding<E> getInstance(); - - public int getVersion() - { - return _version; - } } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplate.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplate.java new file mode 100644 index 0000000000..135158afa4 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplate.java @@ -0,0 +1,114 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import org.apache.log4j.Logger; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.Environment; +import com.sleepycat.je.Transaction; + +public class DatabaseTemplate +{ + private static final Logger _logger = Logger.getLogger(DatabaseTemplate.class); + + private Environment _environment; + private String _sourceDatabaseName; + private String _targetDatabaseName; + private Transaction _parentTransaction; + + public DatabaseTemplate(Environment environment, String sourceDatabaseName, Transaction transaction) + { + this(environment, sourceDatabaseName, null, transaction); + } + + public DatabaseTemplate(Environment environment, String sourceDatabaseName, String targetDatabaseName, + Transaction parentTransaction) + { + _environment = environment; + _sourceDatabaseName = sourceDatabaseName; + _targetDatabaseName = targetDatabaseName; + _parentTransaction = parentTransaction; + } + + public void run(DatabaseRunnable databaseRunnable) + { + DatabaseCallable<Void> callable = runnableToCallable(databaseRunnable); + call(callable); + } + + public <T> T call(DatabaseCallable<T> databaseCallable) + { + Database sourceDatabase = null; + Database targetDatabase = null; + try + { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + + sourceDatabase = _environment.openDatabase(_parentTransaction, _sourceDatabaseName, dbConfig); + + if (_targetDatabaseName != null) + { + targetDatabase = _environment.openDatabase(_parentTransaction, _targetDatabaseName, dbConfig); + } + + return databaseCallable.call(sourceDatabase, targetDatabase, _parentTransaction); + } + finally + { + closeDatabase(sourceDatabase); + closeDatabase(targetDatabase); + } + } + + private DatabaseCallable<Void> runnableToCallable(final DatabaseRunnable databaseRunnable) + { + return new DatabaseCallable<Void>() + { + + @Override + public Void call(Database sourceDatabase, Database targetDatabase, Transaction transaction) + { + databaseRunnable.run(sourceDatabase, targetDatabase, transaction); + return null; + } + }; + } + + private void closeDatabase(Database database) + { + if (database != null) + { + try + { + database.close(); + } + catch (Exception e) + { + _logger.error("Unable to close database", e); + } + } + } + +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java new file mode 100644 index 0000000000..f73e2e5d78 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import org.apache.qpid.AMQStoreException; + +public interface StoreUpgrade +{ + void performUpgrade(Environment environment, UpgradeInteractionHandler handler, String virtualHostName) + throws DatabaseException, AMQStoreException; +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4To5.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4To5.java new file mode 100644 index 0000000000..49e5e700c4 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4To5.java @@ -0,0 +1,915 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.AMQProtocolVersionException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; + +import com.sleepycat.bind.tuple.ByteBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.bind.tuple.TupleBase; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.Transaction; + +public class UpgradeFrom4To5 extends AbstractStoreUpgrade +{ + private static final String OLD_DELIVERY_DB = "deliveryDb_v4"; + private static final String NEW_DELIVERY_DB = "deliveryDb_v5"; + private static final String EXCHANGE_DB_NAME = "exchangeDb_v4"; + private static final String OLD_BINDINGS_DB_NAME = "queueBindingsDb_v4"; + private static final String NEW_BINDINGS_DB_NAME = "queueBindingsDb_v5"; + private static final String OLD_QUEUE_DB_NAME = "queueDb_v4"; + private static final String NEW_QUEUE_DB_NAME = "queueDb_v5"; + private static final String OLD_METADATA_DB_NAME = "messageMetaDataDb_v4"; + private static final String NEW_METADATA_DB_NAME = "messageMetaDataDb_v5"; + private static final String OLD_CONTENT_DB_NAME = "messageContentDb_v4"; + private static final String NEW_CONTENT_DB_NAME = "messageContentDb_v5"; + + private static final byte COLON = (byte) ':'; + + private static final Logger _logger = Logger.getLogger(UpgradeFrom4To5.class); + + public void performUpgrade(final Environment environment, final UpgradeInteractionHandler handler, String virtualHostName) throws DatabaseException, AMQStoreException + { + Transaction transaction = null; + try + { + reportStarting(environment, 4); + + transaction = environment.beginTransaction(null, null); + + // find all queues which are bound to a topic exchange and which have a colon in their name + final List<AMQShortString> potentialDurableSubs = findPotentialDurableSubscriptions(environment, transaction); + + Set<String> existingQueues = upgradeQueues(environment, handler, potentialDurableSubs, transaction); + upgradeQueueBindings(environment, handler, potentialDurableSubs, transaction); + Set<Long> messagesToDiscard = upgradeDelivery(environment, existingQueues, handler, transaction); + upgradeContent(environment, handler, messagesToDiscard, transaction); + upgradeMetaData(environment, handler, messagesToDiscard, transaction); + renameRemainingDatabases(environment, handler, transaction); + transaction.commit(); + + reportFinished(environment, 5); + + } + catch (Exception e) + { + transaction.abort(); + if (e instanceof DatabaseException) + { + throw (DatabaseException) e; + } + else if (e instanceof AMQStoreException) + { + throw (AMQStoreException) e; + } + else + { + throw new AMQStoreException("Unexpected exception", e); + } + } + } + + private void upgradeQueueBindings(Environment environment, UpgradeInteractionHandler handler, final List<AMQShortString> potentialDurableSubs, + Transaction transaction) + { + if (environment.getDatabaseNames().contains(OLD_BINDINGS_DB_NAME)) + { + _logger.info("Queue Bindings"); + final BindingTuple bindingTuple = new BindingTuple(); + CursorOperation databaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + // All the information required in binding entries is actually in the *key* not value. + BindingRecord oldBindingRecord = bindingTuple.entryToObject(key); + + AMQShortString queueName = oldBindingRecord.getQueueName(); + AMQShortString exchangeName = oldBindingRecord.getExchangeName(); + AMQShortString routingKey = oldBindingRecord.getRoutingKey(); + FieldTable arguments = oldBindingRecord.getArguments(); + + if (_logger.isDebugEnabled()) + { + _logger.debug(String.format( + "Processing binding for queue %s, exchange %s, routingKey %s arguments %s", queueName, + exchangeName, routingKey, arguments)); + } + + // if the queue name is in the gathered list then inspect its binding arguments + // only topic exchange should have a JMS selector key in binding + if (potentialDurableSubs.contains(queueName) + && exchangeName.equals(ExchangeDefaults.TOPIC_EXCHANGE_NAME)) + { + if (arguments == null) + { + arguments = new FieldTable(); + } + + AMQShortString selectorFilterKey = AMQPFilterTypes.JMS_SELECTOR.getValue(); + if (!arguments.containsKey(selectorFilterKey)) + { + if (_logger.isDebugEnabled()) + { + _logger.info("adding the empty string (i.e. 'no selector') value for " + queueName + + " and exchange " + exchangeName); + } + arguments.put(selectorFilterKey, ""); + } + } + addBindingToDatabase(bindingTuple, targetDatabase, transaction, queueName, exchangeName, routingKey, + arguments); + } + }; + new DatabaseTemplate(environment, OLD_BINDINGS_DB_NAME, NEW_BINDINGS_DB_NAME, transaction) + .run(databaseOperation); + environment.removeDatabase(transaction, OLD_BINDINGS_DB_NAME); + _logger.info(databaseOperation.getRowCount() + " Queue Binding entries"); + } + } + + private Set<String> upgradeQueues(final Environment environment, final UpgradeInteractionHandler handler, + List<AMQShortString> potentialDurableSubs, Transaction transaction) + { + _logger.info("Queues"); + final Set<String> existingQueues = new HashSet<String>(); + if (environment.getDatabaseNames().contains(OLD_QUEUE_DB_NAME)) + { + final QueueRecordBinding binding = new QueueRecordBinding(potentialDurableSubs); + CursorOperation databaseOperation = new CursorOperation() + { + @Override + public void processEntry(final Database sourceDatabase, final Database targetDatabase, + final Transaction transaction, final DatabaseEntry key, final DatabaseEntry value) + { + QueueRecord record = binding.entryToObject(value); + DatabaseEntry newValue = new DatabaseEntry(); + binding.objectToEntry(record, newValue); + targetDatabase.put(transaction, key, newValue); + existingQueues.add(record.getNameShortString().asString()); + sourceDatabase.delete(transaction, key); + } + }; + new DatabaseTemplate(environment, OLD_QUEUE_DB_NAME, NEW_QUEUE_DB_NAME, transaction).run(databaseOperation); + environment.removeDatabase(transaction, OLD_QUEUE_DB_NAME); + _logger.info(databaseOperation.getRowCount() + " Queue entries"); + } + return existingQueues; + } + + private List<AMQShortString> findPotentialDurableSubscriptions(final Environment environment, + Transaction transaction) + { + final List<AMQShortString> exchangeNames = findTopicExchanges(environment); + final List<AMQShortString> queues = new ArrayList<AMQShortString>(); + final PartialBindingRecordBinding binding = new PartialBindingRecordBinding(); + + CursorOperation databaseOperation = new CursorOperation() + { + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + PartialBindingRecord record = binding.entryToObject(key); + if (exchangeNames.contains(record.getExchangeName()) && record.getQueueName().contains(COLON)) + { + queues.add(record.getQueueName()); + } + } + }; + new DatabaseTemplate(environment, OLD_BINDINGS_DB_NAME, transaction).run(databaseOperation); + return queues; + } + + private Set<Long> upgradeDelivery(final Environment environment, final Set<String> existingQueues, + final UpgradeInteractionHandler handler, Transaction transaction) + { + final Set<Long> messagesToDiscard = new HashSet<Long>(); + final Set<String> queuesToDiscard = new HashSet<String>(); + final QueueEntryKeyBinding queueEntryKeyBinding = new QueueEntryKeyBinding(); + _logger.info("Delivery Records"); + + CursorOperation databaseOperation = new CursorOperation() + { + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + QueueEntryKey entryKey = queueEntryKeyBinding.entryToObject(key); + Long messageId = entryKey.getMessageId(); + final String queueName = entryKey.getQueueName().asString(); + if (!existingQueues.contains(queueName)) + { + if (queuesToDiscard.contains(queueName)) + { + messagesToDiscard.add(messageId); + } + else + { + String lineSeparator = System.getProperty("line.separator"); + String question = MessageFormat.format("Found persistent messages for non-durable queue ''{1}''. " + + " Do you with to create this queue and move all the messages into it?" + lineSeparator + + "NOTE: Answering No will result in these messages being discarded!", queueName); + UpgradeInteractionResponse response = handler.requireResponse(question.toString(), + UpgradeInteractionResponse.YES, UpgradeInteractionResponse.YES, + UpgradeInteractionResponse.NO, UpgradeInteractionResponse.ABORT); + + if (response == UpgradeInteractionResponse.YES) + { + createQueue(environment, transaction, queueName); + existingQueues.add(queueName); + } + else if (response == UpgradeInteractionResponse.NO) + { + queuesToDiscard.add(queueName); + messagesToDiscard.add(messageId); + } + else + { + throw new RuntimeException("Unable is aborted!"); + } + } + } + + if (!messagesToDiscard.contains(messageId)) + { + DatabaseEntry newKey = new DatabaseEntry(); + queueEntryKeyBinding.objectToEntry(entryKey, newKey); + targetDatabase.put(transaction, newKey, value); + + } + } + }; + new DatabaseTemplate(environment, OLD_DELIVERY_DB, NEW_DELIVERY_DB, transaction).run(databaseOperation); + + if (!messagesToDiscard.isEmpty()) + { + databaseOperation = new CursorOperation() + { + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + QueueEntryKey entryKey = queueEntryKeyBinding.entryToObject(key); + Long messageId = entryKey.getMessageId(); + + if (messagesToDiscard.contains(messageId)) + { + messagesToDiscard.remove(messageId); + } + } + }; + new DatabaseTemplate(environment, NEW_DELIVERY_DB, transaction).run(databaseOperation); + } + _logger.info(databaseOperation.getRowCount() + " Delivery Records entries "); + environment.removeDatabase(transaction, OLD_DELIVERY_DB); + + return messagesToDiscard; + } + + protected void createQueue(final Environment environment, Transaction transaction, final String queueName) + { + + final QueueRecordBinding binding = new QueueRecordBinding(null); + final BindingTuple bindingTuple = new BindingTuple(); + DatabaseRunnable queueCreateOperation = new DatabaseRunnable() + { + + @Override + public void run(Database newQueueDatabase, Database newBindingsDatabase, Transaction transaction) + { + AMQShortString queueNameAMQ = new AMQShortString(queueName); + QueueRecord record = new QueueRecord(queueNameAMQ, null, false, null); + + DatabaseEntry key = new DatabaseEntry(); + + TupleOutput output = new TupleOutput(); + AMQShortStringEncoding.writeShortString(record.getNameShortString(), output); + TupleBase.outputToEntry(output, key); + + DatabaseEntry newValue = new DatabaseEntry(); + binding.objectToEntry(record, newValue); + newQueueDatabase.put(transaction, key, newValue); + + FieldTable emptyArguments = new FieldTable(); + addBindingToDatabase(bindingTuple, newBindingsDatabase, transaction, queueNameAMQ, + ExchangeDefaults.DIRECT_EXCHANGE_NAME, queueNameAMQ, emptyArguments); + + // TODO QPID-3490 we should not persist a default exchange binding + addBindingToDatabase(bindingTuple, newBindingsDatabase, transaction, queueNameAMQ, + ExchangeDefaults.DEFAULT_EXCHANGE_NAME, queueNameAMQ, emptyArguments); + } + }; + new DatabaseTemplate(environment, NEW_QUEUE_DB_NAME, NEW_BINDINGS_DB_NAME, transaction).run(queueCreateOperation); + } + + private List<AMQShortString> findTopicExchanges(final Environment environment) + { + final List<AMQShortString> topicExchanges = new ArrayList<AMQShortString>(); + final ExchangeRecordBinding binding = new ExchangeRecordBinding(); + CursorOperation databaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + ExchangeRecord record = binding.entryToObject(value); + if (ExchangeDefaults.TOPIC_EXCHANGE_CLASS.equals(record.getType())) + { + topicExchanges.add(record.getName()); + } + } + }; + new DatabaseTemplate(environment, EXCHANGE_DB_NAME, null).run(databaseOperation); + return topicExchanges; + } + + private void upgradeMetaData(final Environment environment, final UpgradeInteractionHandler handler, + final Set<Long> messagesToDiscard, Transaction transaction) + { + _logger.info("Message MetaData"); + if (environment.getDatabaseNames().contains(OLD_METADATA_DB_NAME)) + { + final MessageMetaDataBinding binding = new MessageMetaDataBinding(); + CursorOperation databaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + StorableMessageMetaData metaData = binding.entryToObject(value); + + // get message id + Long messageId = LongBinding.entryToLong(key); + + // ONLY copy data if message is delivered to existing queue + if (messagesToDiscard.contains(messageId)) + { + return; + } + DatabaseEntry newValue = new DatabaseEntry(); + binding.objectToEntry(metaData, newValue); + + targetDatabase.put(transaction, key, newValue); + targetDatabase.put(transaction, key, newValue); + deleteCurrent(); + + } + }; + + new DatabaseTemplate(environment, OLD_METADATA_DB_NAME, NEW_METADATA_DB_NAME, transaction) + .run(databaseOperation); + environment.removeDatabase(transaction, OLD_METADATA_DB_NAME); + _logger.info(databaseOperation.getRowCount() + " Message MetaData entries"); + } + } + + private void upgradeContent(final Environment environment, final UpgradeInteractionHandler handler, + final Set<Long> messagesToDiscard, Transaction transaction) + { + _logger.info("Message Contents"); + if (environment.getDatabaseNames().contains(OLD_CONTENT_DB_NAME)) + { + final MessageContentKeyBinding keyBinding = new MessageContentKeyBinding(); + final ContentBinding contentBinding = new ContentBinding(); + CursorOperation cursorOperation = new CursorOperation() + { + private long _prevMsgId = -1; + private int _bytesSeenSoFar; + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + // determine the msgId of the current entry + MessageContentKey contentKey = keyBinding.entryToObject(key); + long msgId = contentKey.getMessageId(); + + // ONLY copy data if message is delivered to existing queue + if (messagesToDiscard.contains(msgId)) + { + return; + } + // if this is a new message, restart the byte offset count. + if (_prevMsgId != msgId) + { + _bytesSeenSoFar = 0; + } + + // determine the content size + ByteBuffer content = contentBinding.entryToObject(value); + int contentSize = content.limit(); + + // create the new key: id + previously seen data count + MessageContentKey newKey = new MessageContentKey(msgId, _bytesSeenSoFar); + DatabaseEntry newKeyEntry = new DatabaseEntry(); + keyBinding.objectToEntry(newKey, newKeyEntry); + + DatabaseEntry newValueEntry = new DatabaseEntry(); + contentBinding.objectToEntry(content, newValueEntry); + + targetDatabase.put(null, newKeyEntry, newValueEntry); + + _prevMsgId = msgId; + _bytesSeenSoFar += contentSize; + } + }; + new DatabaseTemplate(environment, OLD_CONTENT_DB_NAME, NEW_CONTENT_DB_NAME, transaction).run(cursorOperation); + environment.removeDatabase(transaction, OLD_CONTENT_DB_NAME); + _logger.info(cursorOperation.getRowCount() + " Message Content entries"); + } + } + + /** + * For all databases which haven't been otherwise upgraded, we still need to + * rename them from _v4 to _v5 + */ + private void renameRemainingDatabases(final Environment environment, final UpgradeInteractionHandler handler, + Transaction transaction) + { + for (String dbName : environment.getDatabaseNames()) + { + if (dbName.endsWith("_v4")) + { + String newName = dbName.substring(0, dbName.length() - 3) + "_v5"; + _logger.info("Renaming " + dbName + " into " + newName); + environment.renameDatabase(transaction, dbName, newName); + } + } + + } + + private void addBindingToDatabase(final BindingTuple bindingTuple, Database targetDatabase, Transaction transaction, + AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey, FieldTable arguments) + { + + DatabaseEntry newKey = new DatabaseEntry(); + + bindingTuple.objectToEntry(new BindingRecord(exchangeName, queueName, routingKey, arguments), newKey); + + DatabaseEntry newValue = new DatabaseEntry(); + ByteBinding.byteToEntry((byte) 0, newValue); + + targetDatabase.put(transaction, newKey, newValue); + } + + private static final class ExchangeRecord + { + private final AMQShortString _name; + private final AMQShortString _type; + + private ExchangeRecord(final AMQShortString name, final AMQShortString type) + { + _name = name; + _type = type; + } + + public AMQShortString getName() + { + return _name; + } + + public AMQShortString getType() + { + return _type; + } + } + + private static final class ExchangeRecordBinding extends TupleBinding<ExchangeRecord> + { + + @Override + public ExchangeRecord entryToObject(final TupleInput input) + { + return new ExchangeRecord(AMQShortStringEncoding.readShortString(input), + AMQShortStringEncoding.readShortString(input)); + } + + @Override + public void objectToEntry(final ExchangeRecord object, final TupleOutput output) + { + AMQShortStringEncoding.writeShortString(object.getName(), output); + AMQShortStringEncoding.writeShortString(object.getType(), output); + output.writeBoolean(false); + } + } + + private static final class PartialBindingRecord + { + private final AMQShortString _exchangeName; + private final AMQShortString _queueName; + + private PartialBindingRecord(final AMQShortString name, final AMQShortString type) + { + _exchangeName = name; + _queueName = type; + } + + public AMQShortString getExchangeName() + { + return _exchangeName; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + } + + private static final class PartialBindingRecordBinding extends TupleBinding<PartialBindingRecord> + { + + @Override + public PartialBindingRecord entryToObject(final TupleInput input) + { + return new PartialBindingRecord(AMQShortStringEncoding.readShortString(input), + AMQShortStringEncoding.readShortString(input)); + } + + @Override + public void objectToEntry(final PartialBindingRecord object, final TupleOutput output) + { + throw new UnsupportedOperationException(); + } + } + + static final class QueueRecord + { + private final AMQShortString _queueName; + private final AMQShortString _owner; + private final FieldTable _arguments; + private final boolean _exclusive; + + public QueueRecord(AMQShortString queueName, AMQShortString owner, boolean exclusive, FieldTable arguments) + { + _queueName = queueName; + _owner = owner; + _exclusive = exclusive; + _arguments = arguments; + } + + public AMQShortString getNameShortString() + { + return _queueName; + } + + public AMQShortString getOwner() + { + return _owner; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public FieldTable getArguments() + { + return _arguments; + } + } + + static final class QueueRecordBinding extends TupleBinding<QueueRecord> + { + private final List<AMQShortString> _durableSubNames; + + QueueRecordBinding(final List<AMQShortString> durableSubNames) + { + _durableSubNames = durableSubNames; + } + + @Override + public QueueRecord entryToObject(final TupleInput input) + { + AMQShortString name = AMQShortStringEncoding.readShortString(input); + AMQShortString owner = AMQShortStringEncoding.readShortString(input); + FieldTable arguments = FieldTableEncoding.readFieldTable(input); + boolean exclusive = input.available() > 0 && input.readBoolean(); + exclusive = exclusive || _durableSubNames.contains(name); + + return new QueueRecord(name, owner, exclusive, arguments); + + } + + @Override + public void objectToEntry(final QueueRecord record, final TupleOutput output) + { + AMQShortStringEncoding.writeShortString(record.getNameShortString(), output); + AMQShortStringEncoding.writeShortString(record.getOwner(), output); + FieldTableEncoding.writeFieldTable(record.getArguments(), output); + output.writeBoolean(record.isExclusive()); + + } + } + + static final class MessageMetaDataBinding extends TupleBinding<StorableMessageMetaData> + { + + @Override + public MessageMetaData entryToObject(final TupleInput input) + { + try + { + final MessagePublishInfo publishBody = readMessagePublishInfo(input); + final ContentHeaderBody contentHeaderBody = readContentHeaderBody(input); + final int contentChunkCount = input.readInt(); + + return new MessageMetaData(publishBody, contentHeaderBody, contentChunkCount); + } + catch (Exception e) + { + _logger.error("Error converting entry to object: " + e, e); + // annoyingly just have to return null since we cannot throw + return null; + } + } + + private MessagePublishInfo readMessagePublishInfo(TupleInput tupleInput) + { + + final AMQShortString exchange = AMQShortStringEncoding.readShortString(tupleInput); + final AMQShortString routingKey = AMQShortStringEncoding.readShortString(tupleInput); + final boolean mandatory = tupleInput.readBoolean(); + final boolean immediate = tupleInput.readBoolean(); + + return new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return exchange; + } + + public void setExchange(AMQShortString exchange) + { + + } + + public boolean isImmediate() + { + return immediate; + } + + public boolean isMandatory() + { + return mandatory; + } + + public AMQShortString getRoutingKey() + { + return routingKey; + } + }; + + } + + private ContentHeaderBody readContentHeaderBody(TupleInput tupleInput) throws AMQFrameDecodingException, + AMQProtocolVersionException + { + int bodySize = tupleInput.readInt(); + byte[] underlying = new byte[bodySize]; + tupleInput.readFast(underlying); + + try + { + return ContentHeaderBody.createFromBuffer(new DataInputStream(new ByteArrayInputStream(underlying)), + bodySize); + } + catch (IOException e) + { + throw new AMQFrameDecodingException(null, e.getMessage(), e); + } + } + + @Override + public void objectToEntry(final StorableMessageMetaData metaData, final TupleOutput output) + { + final int bodySize = 1 + metaData.getStorableSize(); + byte[] underlying = new byte[bodySize]; + underlying[0] = (byte) metaData.getType().ordinal(); + java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(underlying); + buf.position(1); + buf = buf.slice(); + + metaData.writeToBuffer(0, buf); + output.writeInt(bodySize); + output.writeFast(underlying); + } + } + + static final class MessageContentKey + { + private long _messageId; + private int _chunk; + + public MessageContentKey(long messageId, int chunkNo) + { + _messageId = messageId; + _chunk = chunkNo; + } + + public int getChunk() + { + return _chunk; + } + + public long getMessageId() + { + return _messageId; + } + + } + + static final class MessageContentKeyBinding extends TupleBinding<MessageContentKey> + { + + public MessageContentKey entryToObject(TupleInput tupleInput) + { + long messageId = tupleInput.readLong(); + int chunk = tupleInput.readInt(); + return new MessageContentKey(messageId, chunk); + } + + public void objectToEntry(MessageContentKey object, TupleOutput tupleOutput) + { + final MessageContentKey mk = object; + tupleOutput.writeLong(mk.getMessageId()); + tupleOutput.writeInt(mk.getChunk()); + } + + } + + static final class ContentBinding extends TupleBinding<ByteBuffer> + { + public ByteBuffer entryToObject(TupleInput tupleInput) + { + final int size = tupleInput.readInt(); + byte[] underlying = new byte[size]; + tupleInput.readFast(underlying); + return ByteBuffer.wrap(underlying); + } + + public void objectToEntry(ByteBuffer src, TupleOutput tupleOutput) + { + src = src.slice(); + + byte[] chunkData = new byte[src.limit()]; + src.duplicate().get(chunkData); + + tupleOutput.writeInt(chunkData.length); + tupleOutput.writeFast(chunkData); + } + } + + static final class QueueEntryKey + { + private AMQShortString _queueName; + private long _messageId; + + public QueueEntryKey(AMQShortString queueName, long messageId) + { + _queueName = queueName; + _messageId = messageId; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + + public long getMessageId() + { + return _messageId; + } + + } + + static final class QueueEntryKeyBinding extends TupleBinding<QueueEntryKey> + { + public QueueEntryKey entryToObject(TupleInput tupleInput) + { + AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); + long messageId = tupleInput.readLong(); + return new QueueEntryKey(queueName, messageId); + } + + public void objectToEntry(QueueEntryKey mk, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(mk.getQueueName(), tupleOutput); + tupleOutput.writeLong(mk.getMessageId()); + } + } + + static final class BindingRecord extends Object + { + private final AMQShortString _exchangeName; + private final AMQShortString _queueName; + private final AMQShortString _routingKey; + private final FieldTable _arguments; + + public BindingRecord(AMQShortString exchangeName, AMQShortString queueName, AMQShortString routingKey, + FieldTable arguments) + { + _exchangeName = exchangeName; + _queueName = queueName; + _routingKey = routingKey; + _arguments = arguments; + } + + public AMQShortString getExchangeName() + { + return _exchangeName; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + public FieldTable getArguments() + { + return _arguments; + } + + } + + static final class BindingTuple extends TupleBinding<BindingRecord> + { + public BindingRecord entryToObject(TupleInput tupleInput) + { + AMQShortString exchangeName = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString routingKey = AMQShortStringEncoding.readShortString(tupleInput); + + FieldTable arguments = FieldTableEncoding.readFieldTable(tupleInput); + + return new BindingRecord(exchangeName, queueName, routingKey, arguments); + } + + public void objectToEntry(BindingRecord binding, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(binding.getExchangeName(), tupleOutput); + AMQShortStringEncoding.writeShortString(binding.getQueueName(), tupleOutput); + AMQShortStringEncoding.writeShortString(binding.getRoutingKey(), tupleOutput); + + FieldTableEncoding.writeFieldTable(binding.getArguments(), tupleOutput); + } + + } +} diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java new file mode 100644 index 0000000000..3265fb6823 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java @@ -0,0 +1,1207 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeInteractionResponse.ABORT; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeInteractionResponse.NO; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeInteractionResponse.YES; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; +import org.apache.qpid.server.util.MapJsonSerializer; + +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import com.sleepycat.je.Cursor; +import com.sleepycat.je.CursorConfig; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.LockMode; +import com.sleepycat.je.OperationStatus; +import com.sleepycat.je.Transaction; + +public class UpgradeFrom5To6 extends AbstractStoreUpgrade +{ + + private static final Logger _logger = Logger.getLogger(UpgradeFrom5To6.class); + + static final String OLD_CONTENT_DB_NAME = "messageContentDb_v5"; + static final String NEW_CONTENT_DB_NAME = "MESSAGE_CONTENT"; + static final String NEW_METADATA_DB_NAME = "MESSAGE_METADATA"; + static final String OLD_META_DATA_DB_NAME = "messageMetaDataDb_v5"; + static final String OLD_EXCHANGE_DB_NAME = "exchangeDb_v5"; + static final String OLD_QUEUE_DB_NAME = "queueDb_v5"; + static final String OLD_DELIVERY_DB_NAME = "deliveryDb_v5"; + static final String OLD_QUEUE_BINDINGS_DB_NAME = "queueBindingsDb_v5"; + static final String OLD_XID_DB_NAME = "xids_v5"; + static final String NEW_XID_DB_NAME = "XIDS"; + static final String CONFIGURED_OBJECTS_DB_NAME = "CONFIGURED_OBJECTS"; + static final String NEW_DELIVERY_DB_NAME = "QUEUE_ENTRIES"; + static final String NEW_BRIDGES_DB_NAME = "BRIDGES"; + static final String NEW_LINKS_DB_NAME = "LINKS"; + static final String OLD_BRIDGES_DB_NAME = "bridges_v5"; + static final String OLD_LINKS_DB_NAME = "links_v5"; + + static final String[] DEFAULT_EXCHANGES = { ExchangeDefaults.DEFAULT_EXCHANGE_NAME.asString(), + ExchangeDefaults.DEFAULT_EXCHANGE_NAME.asString(), ExchangeDefaults.FANOUT_EXCHANGE_NAME.asString(), + ExchangeDefaults.HEADERS_EXCHANGE_NAME.asString(), ExchangeDefaults.TOPIC_EXCHANGE_NAME.asString(), + ExchangeDefaults.DIRECT_EXCHANGE_NAME.asString() }; + private static final Set<String> DEFAULT_EXCHANGES_SET = new HashSet<String>(Arrays.asList(DEFAULT_EXCHANGES)); + + private MapJsonSerializer _serializer = new MapJsonSerializer(); + + /** + * Upgrades from a v5 database to a v6 database + * + * v6 is the first "new style" schema where we don't version every table, + * and the upgrade is re-runnable + * + * Change in this version: + * + * Message content is moved from the database messageContentDb_v5 to + * MESSAGE_CONTENT. The structure of the database changes from ( message-id: + * long, chunk-id: int ) -> ( size: int, byte[] data ) to ( message-id: + * long) -> ( byte[] data ) + * + * That is we keep only one record per message, which contains all the + * message content + * + * Queue, Exchange, Bindings entries are stored now as configurable objects + * in "CONFIGURED_OBJECTS" table. + */ + public void performUpgrade(final Environment environment, final UpgradeInteractionHandler handler, String virtualHostName) + throws DatabaseException, AMQStoreException + { + reportStarting(environment, 5); + upgradeMessages(environment, handler); + upgradeConfiguredObjectsAndDependencies(environment, handler, virtualHostName); + renameDatabases(environment, null); + reportFinished(environment, 6); + } + + private void upgradeConfiguredObjectsAndDependencies(Environment environment, UpgradeInteractionHandler handler, String virtualHostName) + throws AMQStoreException + { + Transaction transaction = null; + try + { + transaction = environment.beginTransaction(null, null); + upgradeConfiguredObjects(environment, handler, transaction, virtualHostName); + upgradeQueueEntries(environment, transaction, virtualHostName); + upgradeXidEntries(environment, transaction, virtualHostName); + transaction.commit(); + } + catch (Exception e) + { + transaction.abort(); + if (e instanceof DatabaseException) + { + throw (DatabaseException) e; + } + else if (e instanceof AMQStoreException) + { + throw (AMQStoreException) e; + } + else + { + throw new AMQStoreException("Unexpected exception", e); + } + } + } + + private void upgradeMessages(final Environment environment, final UpgradeInteractionHandler handler) + throws AMQStoreException + { + Transaction transaction = null; + try + { + transaction = environment.beginTransaction(null, null); + upgradeMessages(environment, handler, transaction); + transaction.commit(); + } + catch (Exception e) + { + transaction.abort(); + if (e instanceof DatabaseException) + { + throw (DatabaseException) e; + } + else if (e instanceof AMQStoreException) + { + throw (AMQStoreException) e; + } + else + { + throw new AMQStoreException("Unexpected exception", e); + } + } + } + + private void renameDatabases(Environment environment, Transaction transaction) + { + List<String> databases = environment.getDatabaseNames(); + String[] oldDatabases = { OLD_META_DATA_DB_NAME, OLD_BRIDGES_DB_NAME, OLD_LINKS_DB_NAME }; + String[] newDatabases = { NEW_METADATA_DB_NAME, NEW_BRIDGES_DB_NAME, NEW_LINKS_DB_NAME }; + + for (int i = 0; i < oldDatabases.length; i++) + { + String oldName = oldDatabases[i]; + String newName = newDatabases[i]; + if (databases.contains(oldName)) + { + _logger.info("Renaming " + oldName + " into " + newName); + environment.renameDatabase(transaction, oldName, newName); + } + } + } + + private void upgradeMessages(final Environment environment, final UpgradeInteractionHandler handler, + final Transaction transaction) throws AMQStoreException + { + _logger.info("Message Contents"); + if (environment.getDatabaseNames().contains(OLD_CONTENT_DB_NAME)) + { + DatabaseRunnable contentOperation = new DatabaseRunnable() + { + @Override + public void run(final Database oldContentDatabase, final Database newContentDatabase, + Transaction contentTransaction) + { + CursorOperation metaDataDatabaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database metadataDatabase, Database notUsed, + Transaction metaDataTransaction, DatabaseEntry key, DatabaseEntry value) + { + long messageId = LongBinding.entryToLong(key); + upgradeMessage(messageId, oldContentDatabase, newContentDatabase, handler, metaDataTransaction, + metadataDatabase); + } + }; + new DatabaseTemplate(environment, OLD_META_DATA_DB_NAME, contentTransaction) + .run(metaDataDatabaseOperation); + _logger.info(metaDataDatabaseOperation.getRowCount() + " Message Content Entries"); + } + }; + new DatabaseTemplate(environment, OLD_CONTENT_DB_NAME, NEW_CONTENT_DB_NAME, transaction).run(contentOperation); + environment.removeDatabase(transaction, OLD_CONTENT_DB_NAME); + } + } + + /** + * Upgrade an individual message, that is read all the data from the old + * database, consolidate it into a single byte[] and then (in a transaction) + * remove the record from the old database and add the corresponding record + * to the new database + */ + private void upgradeMessage(final long messageId, final Database oldDatabase, final Database newDatabase, + final UpgradeInteractionHandler handler, Transaction txn, Database oldMetadataDatabase) + { + SortedMap<Integer, byte[]> messageData = getMessageData(messageId, oldDatabase); + byte[] consolidatedData = new byte[0]; + for (Map.Entry<Integer, byte[]> entry : messageData.entrySet()) + { + int offset = entry.getKey(); + if (offset != consolidatedData.length) + { + String message; + if (offset < consolidatedData.length) + { + message = "Missing data in message id " + messageId + " between offset " + consolidatedData.length + + " and " + offset + ". "; + } + else + { + message = "Duplicate data in message id " + messageId + " between offset " + offset + " and " + + consolidatedData.length + ". "; + } + UpgradeInteractionResponse action = handler.requireResponse(message + + "Do you wish do recover as much of this message as " + + "possible (answering NO will delete the message)?", ABORT, YES, NO, ABORT); + + switch (action) + { + case YES: + byte[] oldData = consolidatedData; + consolidatedData = new byte[offset]; + System.arraycopy(oldData, 0, consolidatedData, 0, Math.min(oldData.length, consolidatedData.length)); + break; + case NO: + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + oldMetadataDatabase.delete(txn, key); + return; + case ABORT: + _logger.error(message); + throw new RuntimeException("Unable to upgrade message " + messageId); + } + + } + byte[] data = new byte[consolidatedData.length + entry.getValue().length]; + System.arraycopy(consolidatedData, 0, data, 0, consolidatedData.length); + System.arraycopy(entry.getValue(), 0, data, offset, entry.getValue().length); + consolidatedData = data; + } + + CompoundKeyBinding binding = new CompoundKeyBinding(); + for (int offset : messageData.keySet()) + { + DatabaseEntry key = new DatabaseEntry(); + binding.objectToEntry(new CompoundKey(messageId, offset), key); + oldDatabase.delete(txn, key); + } + DatabaseEntry key = new DatabaseEntry(); + LongBinding.longToEntry(messageId, key); + NewDataBinding dataBinding = new NewDataBinding(); + DatabaseEntry value = new DatabaseEntry(); + dataBinding.objectToEntry(consolidatedData, value); + + put(newDatabase, txn, key, value); + } + + /** + * @return a (sorted) map of offset -> data for the given message id + */ + private SortedMap<Integer, byte[]> getMessageData(final long messageId, final Database oldDatabase) + { + TreeMap<Integer, byte[]> data = new TreeMap<Integer, byte[]>(); + + Cursor cursor = oldDatabase.openCursor(null, CursorConfig.READ_COMMITTED); + try + { + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + CompoundKeyBinding binding = new CompoundKeyBinding(); + binding.objectToEntry(new CompoundKey(messageId, 0), contentKeyEntry); + + OperationStatus status = cursor.getSearchKeyRange(contentKeyEntry, value, LockMode.DEFAULT); + OldDataBinding dataBinding = new OldDataBinding(); + + while (status == OperationStatus.SUCCESS) + { + CompoundKey compoundKey = binding.entryToObject(contentKeyEntry); + long id = compoundKey.getMessageId(); + + if (id != messageId) + { + // we have exhausted all chunks for this message id, break + break; + } + + int offsetInMessage = compoundKey.getOffset(); + OldDataValue dataValue = dataBinding.entryToObject(value); + data.put(offsetInMessage, dataValue.getData()); + + status = cursor.getNext(contentKeyEntry, value, LockMode.DEFAULT); + } + } + finally + { + cursor.close(); + } + + return data; + } + + private void upgradeConfiguredObjects(Environment environment, UpgradeInteractionHandler handler, Transaction transaction, String virtualHostName) + throws AMQStoreException + { + upgradeQueues(environment, transaction, virtualHostName); + upgradeExchanges(environment, transaction, virtualHostName); + upgradeQueueBindings(environment, transaction, handler, virtualHostName); + } + + private void upgradeXidEntries(Environment environment, Transaction transaction, final String virtualHostName) + { + if (environment.getDatabaseNames().contains(OLD_XID_DB_NAME)) + { + _logger.info("Xid Records"); + final OldPreparedTransactionBinding oldTransactionBinding = new OldPreparedTransactionBinding(); + final NewPreparedTransactionBinding newTransactionBinding = new NewPreparedTransactionBinding(); + CursorOperation xidEntriesCursor = new CursorOperation() + { + @Override + public void processEntry(Database oldXidDatabase, Database newXidDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + OldPreparedTransaction oldPreparedTransaction = oldTransactionBinding.entryToObject(value); + OldRecordImpl[] oldDequeues = oldPreparedTransaction.getDequeues(); + OldRecordImpl[] oldEnqueues = oldPreparedTransaction.getEnqueues(); + + NewRecordImpl[] newEnqueues = null; + NewRecordImpl[] newDequeues = null; + if (oldDequeues != null) + { + newDequeues = new NewRecordImpl[oldDequeues.length]; + for (int i = 0; i < newDequeues.length; i++) + { + OldRecordImpl dequeue = oldDequeues[i]; + UUID id = UUIDGenerator.generateUUID(dequeue.getQueueName(), virtualHostName); + newDequeues[i] = new NewRecordImpl(id, dequeue.getMessageNumber()); + } + } + if (oldEnqueues != null) + { + newEnqueues = new NewRecordImpl[oldEnqueues.length]; + for (int i = 0; i < newEnqueues.length; i++) + { + OldRecordImpl enqueue = oldEnqueues[i]; + UUID id = UUIDGenerator.generateUUID(enqueue.getQueueName(), virtualHostName); + newEnqueues[i] = new NewRecordImpl(id, enqueue.getMessageNumber()); + } + } + NewPreparedTransaction newPreparedTransaction = new NewPreparedTransaction(newEnqueues, newDequeues); + DatabaseEntry newValue = new DatabaseEntry(); + newTransactionBinding.objectToEntry(newPreparedTransaction, newValue); + put(newXidDatabase, transaction, key, newValue); + } + }; + new DatabaseTemplate(environment, OLD_XID_DB_NAME, NEW_XID_DB_NAME, transaction).run(xidEntriesCursor); + environment.removeDatabase(transaction, OLD_XID_DB_NAME); + _logger.info(xidEntriesCursor.getRowCount() + " Xid Entries"); + } + } + + private void upgradeQueueEntries(Environment environment, Transaction transaction, final String virtualHostName) + { + _logger.info("Queue Delivery Records"); + if (environment.getDatabaseNames().contains(OLD_DELIVERY_DB_NAME)) + { + final OldQueueEntryBinding oldBinding = new OldQueueEntryBinding(); + final NewQueueEntryBinding newBinding = new NewQueueEntryBinding(); + CursorOperation queueEntriesCursor = new CursorOperation() + { + @Override + public void processEntry(Database oldDeliveryDatabase, Database newDeliveryDatabase, + Transaction transaction, DatabaseEntry key, DatabaseEntry value) + { + OldQueueEntryKey oldEntryRecord = oldBinding.entryToObject(key); + UUID queueId = UUIDGenerator.generateUUID(oldEntryRecord.getQueueName().asString(), virtualHostName); + + NewQueueEntryKey newEntryRecord = new NewQueueEntryKey(queueId, oldEntryRecord.getMessageId()); + DatabaseEntry newKey = new DatabaseEntry(); + newBinding.objectToEntry(newEntryRecord, newKey); + put(newDeliveryDatabase, transaction, newKey, value); + } + }; + new DatabaseTemplate(environment, OLD_DELIVERY_DB_NAME, NEW_DELIVERY_DB_NAME, transaction) + .run(queueEntriesCursor); + environment.removeDatabase(transaction, OLD_DELIVERY_DB_NAME); + _logger.info(queueEntriesCursor.getRowCount() + " Queue Delivery Record Entries"); + } + } + + private void upgradeQueueBindings(Environment environment, Transaction transaction, final UpgradeInteractionHandler handler, final String virtualHostName) + { + _logger.info("Queue Bindings"); + if (environment.getDatabaseNames().contains(OLD_QUEUE_BINDINGS_DB_NAME)) + { + final QueueBindingBinding binding = new QueueBindingBinding(); + CursorOperation bindingCursor = new CursorOperation() + { + @Override + public void processEntry(Database exchangeDatabase, Database configuredObjectsDatabase, + Transaction transaction, DatabaseEntry key, DatabaseEntry value) + { + // TODO: check and remove orphaned bindings + BindingRecord bindingRecord = binding.entryToObject(key); + String exchangeName = bindingRecord.getExchangeName() == null ? ExchangeDefaults.DEFAULT_EXCHANGE_NAME + .asString() : bindingRecord.getExchangeName().asString(); + String queueName = bindingRecord.getQueueName().asString(); + String routingKey = bindingRecord.getRoutingKey().asString(); + FieldTable arguments = bindingRecord.getArguments(); + + UUID bindingId = UUIDGenerator.generateUUID(); + UpgradeConfiguredObjectRecord configuredObject = createBindingConfiguredObjectRecord(exchangeName, queueName, + routingKey, arguments, virtualHostName); + storeConfiguredObjectEntry(configuredObjectsDatabase, bindingId, configuredObject, transaction); + } + + }; + new DatabaseTemplate(environment, OLD_QUEUE_BINDINGS_DB_NAME, CONFIGURED_OBJECTS_DB_NAME, transaction) + .run(bindingCursor); + environment.removeDatabase(transaction, OLD_QUEUE_BINDINGS_DB_NAME); + _logger.info(bindingCursor.getRowCount() + " Queue Binding Entries"); + } + } + + private List<String> upgradeExchanges(Environment environment, Transaction transaction, final String virtualHostName) + { + final List<String> exchangeNames = new ArrayList<String>(); + _logger.info("Exchanges"); + if (environment.getDatabaseNames().contains(OLD_EXCHANGE_DB_NAME)) + { + final ExchangeBinding exchangeBinding = new ExchangeBinding(); + CursorOperation exchangeCursor = new CursorOperation() + { + @Override + public void processEntry(Database exchangeDatabase, Database configuredObjectsDatabase, + Transaction transaction, DatabaseEntry key, DatabaseEntry value) + { + ExchangeRecord exchangeRecord = exchangeBinding.entryToObject(value); + String exchangeName = exchangeRecord.getNameShortString().asString(); + if (!DEFAULT_EXCHANGES_SET.contains(exchangeName)) + { + String exchangeType = exchangeRecord.getType().asString(); + boolean autoDelete = exchangeRecord.isAutoDelete(); + + UUID exchangeId = UUIDGenerator.generateUUID(exchangeName, virtualHostName); + + UpgradeConfiguredObjectRecord configuredObject = createExchangeConfiguredObjectRecord(exchangeName, + exchangeType, autoDelete); + storeConfiguredObjectEntry(configuredObjectsDatabase, exchangeId, configuredObject, transaction); + exchangeNames.add(exchangeName); + } + } + }; + new DatabaseTemplate(environment, OLD_EXCHANGE_DB_NAME, CONFIGURED_OBJECTS_DB_NAME, transaction) + .run(exchangeCursor); + environment.removeDatabase(transaction, OLD_EXCHANGE_DB_NAME); + _logger.info(exchangeCursor.getRowCount() + " Exchange Entries"); + } + return exchangeNames; + } + + private List<String> upgradeQueues(Environment environment, Transaction transaction, final String virtualHostName) + { + final List<String> queueNames = new ArrayList<String>(); + _logger.info("Queues"); + if (environment.getDatabaseNames().contains(OLD_QUEUE_DB_NAME)) + { + final UpgradeQueueBinding queueBinding = new UpgradeQueueBinding(); + CursorOperation queueCursor = new CursorOperation() + { + @Override + public void processEntry(Database queueDatabase, Database configuredObjectsDatabase, + Transaction transaction, DatabaseEntry key, DatabaseEntry value) + { + OldQueueRecord queueRecord = queueBinding.entryToObject(value); + String queueName = queueRecord.getNameShortString().asString(); + queueNames.add(queueName); + String owner = queueRecord.getOwner() == null ? null : queueRecord.getOwner().asString(); + boolean exclusive = queueRecord.isExclusive(); + FieldTable arguments = queueRecord.getArguments(); + + UUID queueId = UUIDGenerator.generateUUID(queueName, virtualHostName); + UpgradeConfiguredObjectRecord configuredObject = createQueueConfiguredObjectRecord(queueName, owner, exclusive, + arguments); + storeConfiguredObjectEntry(configuredObjectsDatabase, queueId, configuredObject, transaction); + } + }; + new DatabaseTemplate(environment, OLD_QUEUE_DB_NAME, CONFIGURED_OBJECTS_DB_NAME, transaction).run(queueCursor); + environment.removeDatabase(transaction, OLD_QUEUE_DB_NAME); + _logger.info(queueCursor.getRowCount() + " Queue Entries"); + } + return queueNames; + } + + private void storeConfiguredObjectEntry(Database configuredObjectsDatabase, UUID id, + UpgradeConfiguredObjectRecord configuredObject, Transaction transaction) + { + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + UpgradeUUIDBinding uuidBinding = new UpgradeUUIDBinding(); + uuidBinding.objectToEntry(id, key); + ConfiguredObjectBinding configuredBinding = new ConfiguredObjectBinding(); + configuredBinding.objectToEntry(configuredObject, value); + put(configuredObjectsDatabase, transaction, key, value); + } + + private UpgradeConfiguredObjectRecord createQueueConfiguredObjectRecord(String queueName, String owner, boolean exclusive, + FieldTable arguments) + { + Map<String, Object> attributesMap = new HashMap<String, Object>(); + attributesMap.put(Queue.NAME, queueName); + attributesMap.put(Queue.OWNER, owner); + attributesMap.put(Queue.EXCLUSIVE, exclusive); + if (arguments != null) + { + attributesMap.put("ARGUMENTS", FieldTable.convertToMap(arguments)); + } + String json = _serializer.serialize(attributesMap); + UpgradeConfiguredObjectRecord configuredObject = new UpgradeConfiguredObjectRecord(Queue.class.getName(), json); + return configuredObject; + } + + private UpgradeConfiguredObjectRecord createExchangeConfiguredObjectRecord(String exchangeName, String exchangeType, + boolean autoDelete) + { + Map<String, Object> attributesMap = new HashMap<String, Object>(); + attributesMap.put(Exchange.NAME, exchangeName); + attributesMap.put(Exchange.TYPE, exchangeType); + attributesMap.put(Exchange.LIFETIME_POLICY, autoDelete ? LifetimePolicy.AUTO_DELETE.name() + : LifetimePolicy.PERMANENT.name()); + String json = _serializer.serialize(attributesMap); + UpgradeConfiguredObjectRecord configuredObject = new UpgradeConfiguredObjectRecord(Exchange.class.getName(), json); + return configuredObject; + } + + private UpgradeConfiguredObjectRecord createBindingConfiguredObjectRecord(String exchangeName, String queueName, + String routingKey, FieldTable arguments, String virtualHostName) + { + Map<String, Object> attributesMap = new HashMap<String, Object>(); + attributesMap.put(Binding.NAME, routingKey); + attributesMap.put(Binding.EXCHANGE, UUIDGenerator.generateUUID(exchangeName, virtualHostName)); + attributesMap.put(Binding.QUEUE, UUIDGenerator.generateUUID(queueName, virtualHostName)); + if (arguments != null) + { + attributesMap.put(Binding.ARGUMENTS, FieldTable.convertToMap(arguments)); + } + String json = _serializer.serialize(attributesMap); + UpgradeConfiguredObjectRecord configuredObject = new UpgradeConfiguredObjectRecord(Binding.class.getName(), json); + return configuredObject; + } + + private void put(final Database database, Transaction txn, DatabaseEntry key, DatabaseEntry value) + { + OperationStatus status = database.put(txn, key, value); + if (status != OperationStatus.SUCCESS) + { + throw new RuntimeException("Cannot add record into " + database.getDatabaseName() + ":" + status); + } + } + + static final class CompoundKey + { + public final long _messageId; + public final int _offset; + + public CompoundKey(final long messageId, final int offset) + { + _messageId = messageId; + _offset = offset; + } + + public long getMessageId() + { + return _messageId; + } + + public int getOffset() + { + return _offset; + } + } + + static final class CompoundKeyBinding extends TupleBinding<CompoundKey> + { + + @Override + public CompoundKey entryToObject(final TupleInput input) + { + return new CompoundKey(input.readLong(), input.readInt()); + } + + @Override + public void objectToEntry(final CompoundKey object, final TupleOutput output) + { + output.writeLong(object._messageId); + output.writeInt(object._offset); + } + } + + static final class OldDataValue + { + private final int _size; + private final byte[] _data; + + private OldDataValue(final int size, final byte[] data) + { + _size = size; + _data = data; + } + + public int getSize() + { + return _size; + } + + public byte[] getData() + { + return _data; + } + } + + static final class OldDataBinding extends TupleBinding<OldDataValue> + { + + @Override + public OldDataValue entryToObject(final TupleInput input) + { + int size = input.readInt(); + byte[] data = new byte[size]; + input.read(data); + return new OldDataValue(size, data); + } + + @Override + public void objectToEntry(OldDataValue value, final TupleOutput output) + { + output.writeInt(value.getSize()); + output.write(value.getData()); + } + } + + static final class NewDataBinding extends TupleBinding<byte[]> + { + + @Override + public byte[] entryToObject(final TupleInput input) + { + byte[] data = new byte[input.available()]; + input.read(data); + return data; + } + + @Override + public void objectToEntry(final byte[] data, final TupleOutput output) + { + output.write(data); + } + } + + static class OldQueueRecord extends Object + { + private final AMQShortString _queueName; + private final AMQShortString _owner; + private final FieldTable _arguments; + private boolean _exclusive; + + public OldQueueRecord(AMQShortString queueName, AMQShortString owner, boolean exclusive, FieldTable arguments) + { + _queueName = queueName; + _owner = owner; + _exclusive = exclusive; + _arguments = arguments; + } + + public AMQShortString getNameShortString() + { + return _queueName; + } + + public AMQShortString getOwner() + { + return _owner; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public void setExclusive(boolean exclusive) + { + _exclusive = exclusive; + } + + public FieldTable getArguments() + { + return _arguments; + } + + } + + static class UpgradeConfiguredObjectRecord + { + private String _attributes; + private String _type; + + public UpgradeConfiguredObjectRecord(String type, String attributes) + { + super(); + _attributes = attributes; + _type = type; + } + + public String getAttributes() + { + return _attributes; + } + + public String getType() + { + return _type; + } + + } + + static class UpgradeQueueBinding extends TupleBinding<OldQueueRecord> + { + public OldQueueRecord entryToObject(TupleInput tupleInput) + { + AMQShortString name = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString owner = AMQShortStringEncoding.readShortString(tupleInput); + FieldTable arguments = FieldTableEncoding.readFieldTable(tupleInput); + boolean exclusive = tupleInput.readBoolean(); + return new OldQueueRecord(name, owner, exclusive, arguments); + } + + public void objectToEntry(OldQueueRecord queue, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(queue.getNameShortString(), tupleOutput); + AMQShortStringEncoding.writeShortString(queue.getOwner(), tupleOutput); + FieldTableEncoding.writeFieldTable(queue.getArguments(), tupleOutput); + tupleOutput.writeBoolean(queue.isExclusive()); + } + } + + static class UpgradeUUIDBinding extends TupleBinding<UUID> + { + public UUID entryToObject(final TupleInput tupleInput) + { + return new UUID(tupleInput.readLong(), tupleInput.readLong()); + } + + public void objectToEntry(final UUID uuid, final TupleOutput tupleOutput) + { + tupleOutput.writeLong(uuid.getMostSignificantBits()); + tupleOutput.writeLong(uuid.getLeastSignificantBits()); + } + } + + static class ConfiguredObjectBinding extends TupleBinding<UpgradeConfiguredObjectRecord> + { + + public UpgradeConfiguredObjectRecord entryToObject(TupleInput tupleInput) + { + String type = tupleInput.readString(); + String json = tupleInput.readString(); + UpgradeConfiguredObjectRecord configuredObject = new UpgradeConfiguredObjectRecord(type, json); + return configuredObject; + } + + public void objectToEntry(UpgradeConfiguredObjectRecord object, TupleOutput tupleOutput) + { + tupleOutput.writeString(object.getType()); + tupleOutput.writeString(object.getAttributes()); + } + + } + + static class ExchangeRecord extends Object + { + private final AMQShortString _exchangeName; + private final AMQShortString _exchangeType; + private final boolean _autoDelete; + + public ExchangeRecord(AMQShortString exchangeName, AMQShortString exchangeType, boolean autoDelete) + { + _exchangeName = exchangeName; + _exchangeType = exchangeType; + _autoDelete = autoDelete; + } + + public AMQShortString getNameShortString() + { + return _exchangeName; + } + + public AMQShortString getType() + { + return _exchangeType; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + } + + static class ExchangeBinding extends TupleBinding<ExchangeRecord> + { + + public ExchangeRecord entryToObject(TupleInput tupleInput) + { + AMQShortString name = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString typeName = AMQShortStringEncoding.readShortString(tupleInput); + + boolean autoDelete = tupleInput.readBoolean(); + + return new ExchangeRecord(name, typeName, autoDelete); + } + + public void objectToEntry(ExchangeRecord exchange, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(exchange.getNameShortString(), tupleOutput); + AMQShortStringEncoding.writeShortString(exchange.getType(), tupleOutput); + + tupleOutput.writeBoolean(exchange.isAutoDelete()); + } + } + + static class BindingRecord extends Object + { + private final AMQShortString _exchangeName; + private final AMQShortString _queueName; + private final AMQShortString _routingKey; + private final FieldTable _arguments; + + public BindingRecord(AMQShortString exchangeName, AMQShortString queueName, AMQShortString routingKey, + FieldTable arguments) + { + _exchangeName = exchangeName; + _queueName = queueName; + _routingKey = routingKey; + _arguments = arguments; + } + + public AMQShortString getExchangeName() + { + return _exchangeName; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + public FieldTable getArguments() + { + return _arguments; + } + + } + + static class QueueBindingBinding extends TupleBinding<BindingRecord> + { + + public BindingRecord entryToObject(TupleInput tupleInput) + { + AMQShortString exchangeName = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); + AMQShortString routingKey = AMQShortStringEncoding.readShortString(tupleInput); + + FieldTable arguments = FieldTableEncoding.readFieldTable(tupleInput); + + return new BindingRecord(exchangeName, queueName, routingKey, arguments); + } + + public void objectToEntry(BindingRecord binding, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(binding.getExchangeName(), tupleOutput); + AMQShortStringEncoding.writeShortString(binding.getQueueName(), tupleOutput); + AMQShortStringEncoding.writeShortString(binding.getRoutingKey(), tupleOutput); + + FieldTableEncoding.writeFieldTable(binding.getArguments(), tupleOutput); + } + } + + static class OldQueueEntryKey + { + private AMQShortString _queueName; + private long _messageId; + + public OldQueueEntryKey(AMQShortString queueName, long messageId) + { + _queueName = queueName; + _messageId = messageId; + } + + public AMQShortString getQueueName() + { + return _queueName; + } + + public long getMessageId() + { + return _messageId; + } + } + + static class OldQueueEntryBinding extends TupleBinding<OldQueueEntryKey> + { + + public OldQueueEntryKey entryToObject(TupleInput tupleInput) + { + AMQShortString queueName = AMQShortStringEncoding.readShortString(tupleInput); + long messageId = tupleInput.readLong(); + + return new OldQueueEntryKey(queueName, messageId); + } + + public void objectToEntry(OldQueueEntryKey mk, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString(mk.getQueueName(), tupleOutput); + tupleOutput.writeLong(mk.getMessageId()); + } + } + + static class NewQueueEntryKey + { + private UUID _queueId; + private long _messageId; + + public NewQueueEntryKey(UUID queueId, long messageId) + { + _queueId = queueId; + _messageId = messageId; + } + + public UUID getQueueId() + { + return _queueId; + } + + public long getMessageId() + { + return _messageId; + } + } + + static class NewQueueEntryBinding extends TupleBinding<NewQueueEntryKey> + { + + public NewQueueEntryKey entryToObject(TupleInput tupleInput) + { + UUID queueId = new UUID(tupleInput.readLong(), tupleInput.readLong()); + long messageId = tupleInput.readLong(); + + return new NewQueueEntryKey(queueId, messageId); + } + + public void objectToEntry(NewQueueEntryKey mk, TupleOutput tupleOutput) + { + UUID uuid = mk.getQueueId(); + tupleOutput.writeLong(uuid.getMostSignificantBits()); + tupleOutput.writeLong(uuid.getLeastSignificantBits()); + tupleOutput.writeLong(mk.getMessageId()); + } + } + + static class NewPreparedTransaction + { + private final NewRecordImpl[] _enqueues; + private final NewRecordImpl[] _dequeues; + + public NewPreparedTransaction(NewRecordImpl[] enqueues, NewRecordImpl[] dequeues) + { + _enqueues = enqueues; + _dequeues = dequeues; + } + + public NewRecordImpl[] getEnqueues() + { + return _enqueues; + } + + public NewRecordImpl[] getDequeues() + { + return _dequeues; + } + } + + static class NewRecordImpl + { + + private long _messageNumber; + private UUID _queueId; + + public NewRecordImpl(UUID queueId, long messageNumber) + { + _messageNumber = messageNumber; + _queueId = queueId; + } + + public long getMessageNumber() + { + return _messageNumber; + } + + public UUID getId() + { + return _queueId; + } + } + + static class NewPreparedTransactionBinding extends TupleBinding<NewPreparedTransaction> + { + @Override + public NewPreparedTransaction entryToObject(TupleInput input) + { + NewRecordImpl[] enqueues = readRecords(input); + + NewRecordImpl[] dequeues = readRecords(input); + + return new NewPreparedTransaction(enqueues, dequeues); + } + + private NewRecordImpl[] readRecords(TupleInput input) + { + NewRecordImpl[] records = new NewRecordImpl[input.readInt()]; + for (int i = 0; i < records.length; i++) + { + records[i] = new NewRecordImpl(new UUID(input.readLong(), input.readLong()), input.readLong()); + } + return records; + } + + @Override + public void objectToEntry(NewPreparedTransaction preparedTransaction, TupleOutput output) + { + writeRecords(preparedTransaction.getEnqueues(), output); + writeRecords(preparedTransaction.getDequeues(), output); + } + + private void writeRecords(NewRecordImpl[] records, TupleOutput output) + { + if (records == null) + { + output.writeInt(0); + } + else + { + output.writeInt(records.length); + for (NewRecordImpl record : records) + { + UUID id = record.getId(); + output.writeLong(id.getMostSignificantBits()); + output.writeLong(id.getLeastSignificantBits()); + output.writeLong(record.getMessageNumber()); + } + } + } + } + + static class OldRecordImpl + { + + private long _messageNumber; + private String _queueName; + + public OldRecordImpl(String queueName, long messageNumber) + { + _messageNumber = messageNumber; + _queueName = queueName; + } + + public long getMessageNumber() + { + return _messageNumber; + } + + public String getQueueName() + { + return _queueName; + } + } + + static class OldPreparedTransaction + { + private final OldRecordImpl[] _enqueues; + private final OldRecordImpl[] _dequeues; + + public OldPreparedTransaction(OldRecordImpl[] enqueues, OldRecordImpl[] dequeues) + { + _enqueues = enqueues; + _dequeues = dequeues; + } + + public OldRecordImpl[] getEnqueues() + { + return _enqueues; + } + + public OldRecordImpl[] getDequeues() + { + return _dequeues; + } + } + + static class OldPreparedTransactionBinding extends TupleBinding<OldPreparedTransaction> + { + @Override + public OldPreparedTransaction entryToObject(TupleInput input) + { + OldRecordImpl[] enqueues = readRecords(input); + + OldRecordImpl[] dequeues = readRecords(input); + + return new OldPreparedTransaction(enqueues, dequeues); + } + + private OldRecordImpl[] readRecords(TupleInput input) + { + OldRecordImpl[] records = new OldRecordImpl[input.readInt()]; + for (int i = 0; i < records.length; i++) + { + records[i] = new OldRecordImpl(input.readString(), input.readLong()); + } + return records; + } + + @Override + public void objectToEntry(OldPreparedTransaction preparedTransaction, TupleOutput output) + { + writeRecords(preparedTransaction.getEnqueues(), output); + writeRecords(preparedTransaction.getDequeues(), output); + } + + private void writeRecords(OldRecordImpl[] records, TupleOutput output) + { + if (records == null) + { + output.writeInt(0); + } + else + { + output.writeInt(records.length); + for (OldRecordImpl record : records) + { + output.writeString(record.getQueueName()); + output.writeLong(record.getMessageNumber()); + } + } + } + } +}
\ No newline at end of file diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionHandler.java index 468096ccc5..0cedbd15e0 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionHandler.java @@ -18,28 +18,20 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.upgrade; -import com.sleepycat.bind.tuple.TupleBinding; - -import org.apache.qpid.server.store.berkeleydb.records.BindingRecord; - -public class BindingTupleBindingFactory extends TupleBindingFactory<BindingRecord> +public interface UpgradeInteractionHandler { - public BindingTupleBindingFactory(int version) - { - super(version); - } + UpgradeInteractionResponse requireResponse(String question, UpgradeInteractionResponse defaultResponse, + UpgradeInteractionResponse... possibleResponses); - public TupleBinding<BindingRecord> getInstance() + public static final UpgradeInteractionHandler DEFAULT_HANDLER = new UpgradeInteractionHandler() { - switch (getVersion()) + public UpgradeInteractionResponse requireResponse(final String question, + final UpgradeInteractionResponse defaultResponse, + final UpgradeInteractionResponse... possibleResponses) { - default: - case 5: - //no change from v4 - case 4: - return new BindingTuple_4(); + return defaultResponse; } - } + }; } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionResponse.java index 301ee417c5..eb5a049a9a 100644 --- a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionResponse.java @@ -18,8 +18,11 @@ * under the License. * */ -package org.apache.qpid.server.store.berkeleydb.tuples; +package org.apache.qpid.server.store.berkeleydb.upgrade; -public interface BindingTuple +public enum UpgradeInteractionResponse { + YES, + NO, + ABORT } diff --git a/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java new file mode 100644 index 0000000000..e71e39cbb8 --- /dev/null +++ b/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java @@ -0,0 +1,177 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; + +import com.sleepycat.bind.tuple.IntegerBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.LockMode; +import com.sleepycat.je.OperationStatus; + +public class Upgrader +{ + static final String VERSION_DB_NAME = "DB_VERSION"; + + private Environment _environment; + private String _virtualHostName; + + public Upgrader(Environment environment, String virtualHostName) + { + _environment = environment; + _virtualHostName = virtualHostName; + } + + public void upgradeIfNecessary() throws AMQStoreException + { + boolean isEmpty = _environment.getDatabaseNames().isEmpty(); + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + + Database versionDb = null; + try + { + versionDb = _environment.openDatabase(null, VERSION_DB_NAME, dbConfig); + + if(versionDb.count() == 0L) + { + int sourceVersion = isEmpty ? BDBMessageStore.VERSION: identifyOldStoreVersion(); + DatabaseEntry key = new DatabaseEntry(); + IntegerBinding.intToEntry(sourceVersion, key); + DatabaseEntry value = new DatabaseEntry(); + LongBinding.longToEntry(System.currentTimeMillis(), value); + + versionDb.put(null, key, value); + } + + int version = getSourceVersion(versionDb); + + performUpgradeFromVersion(version, versionDb); + } + finally + { + if (versionDb != null) + { + versionDb.close(); + } + } + } + + int getSourceVersion(Database versionDb) + { + int version = BDBMessageStore.VERSION + 1; + OperationStatus result; + + do + { + version--; + DatabaseEntry key = new DatabaseEntry(); + IntegerBinding.intToEntry(version, key); + DatabaseEntry value = new DatabaseEntry(); + + result = versionDb.get(null, key, value, LockMode.READ_COMMITTED); + } + while(result == OperationStatus.NOTFOUND); + return version; + } + + void performUpgradeFromVersion(int sourceVersion, Database versionDb) + throws AMQStoreException + { + while(sourceVersion != BDBMessageStore.VERSION) + { + upgrade(sourceVersion, ++sourceVersion); + DatabaseEntry key = new DatabaseEntry(); + IntegerBinding.intToEntry(sourceVersion, key); + DatabaseEntry value = new DatabaseEntry(); + LongBinding.longToEntry(System.currentTimeMillis(), value); + versionDb.put(null, key, value); + } + } + + void upgrade(final int fromVersion, final int toVersion) throws AMQStoreException + { + try + { + @SuppressWarnings("unchecked") + Class<StoreUpgrade> upgradeClass = + (Class<StoreUpgrade>) Class.forName("org.apache.qpid.server.store.berkeleydb.upgrade." + + "UpgradeFrom"+fromVersion+"To"+toVersion); + Constructor<StoreUpgrade> ctr = upgradeClass.getConstructor(); + StoreUpgrade upgrade = ctr.newInstance(); + upgrade.performUpgrade(_environment, UpgradeInteractionHandler.DEFAULT_HANDLER, _virtualHostName); + } + catch (ClassNotFoundException e) + { + throw new AMQStoreException("Unable to upgrade BDB data store from version " + fromVersion + " to version" + + toVersion, e); + } + catch (NoSuchMethodException e) + { + throw new AMQStoreException("Unable to upgrade BDB data store from version " + fromVersion + " to version" + + toVersion, e); + } + catch (InvocationTargetException e) + { + throw new AMQStoreException("Unable to upgrade BDB data store from version " + fromVersion + " to version" + + toVersion, e); + } + catch (InstantiationException e) + { + throw new AMQStoreException("Unable to upgrade BDB data store from version " + fromVersion + " to version" + + toVersion, e); + } + catch (IllegalAccessException e) + { + throw new AMQStoreException("Unable to upgrade BDB data store from version " + fromVersion + " to version" + + toVersion, e); + } + } + + private int identifyOldStoreVersion() throws DatabaseException + { + int version = 0; + for (String databaseName : _environment.getDatabaseNames()) + { + if (databaseName.contains("_v")) + { + int versionIndex = databaseName.indexOf("_v"); + if (versionIndex == -1) + { + versionIndex = 1; + } + version = Integer.parseInt(databaseName.substring(versionIndex + 2)); + break; + } + } + return version; + } +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreConfigurationTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreConfigurationTest.java new file mode 100644 index 0000000000..687c671566 --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreConfigurationTest.java @@ -0,0 +1,14 @@ +package org.apache.qpid.server.store.berkeleydb; + +import org.apache.qpid.server.store.DurableConfigurationStoreTest; +import org.apache.qpid.server.store.MessageStore; + +public class BDBMessageStoreConfigurationTest extends DurableConfigurationStoreTest +{ + @Override + protected MessageStore createStore() throws Exception + { + return new BDBMessageStore(); + } + +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java index 3d30f02b42..a318187f13 100644 --- a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java @@ -20,6 +20,10 @@ */ package org.apache.qpid.server.store.berkeleydb; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; import org.apache.qpid.AMQStoreException; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.BasicContentHeaderProperties; @@ -33,10 +37,12 @@ import org.apache.qpid.server.message.MessageMetaData; import org.apache.qpid.server.message.MessageMetaData_0_10; import org.apache.qpid.server.message.MessageReference; import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.UUIDGenerator; import org.apache.qpid.server.store.MessageMetaDataType; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.StorableMessageMetaData; import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.Transaction; import org.apache.qpid.server.store.TransactionLogResource; import org.apache.qpid.transport.DeliveryProperties; import org.apache.qpid.transport.Header; @@ -47,20 +53,17 @@ import org.apache.qpid.transport.MessageDeliveryPriority; import org.apache.qpid.transport.MessageProperties; import org.apache.qpid.transport.MessageTransfer; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; - /** * Subclass of MessageStoreTest which runs the standard tests from the superclass against * the BDB Store as well as additional tests specific to the DBB store-implementation. */ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageStoreTest { + private static byte[] CONTENT_BYTES = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + /** - * Tests that message metadata and content are successfully read back from a - * store after it has been reloaded. Both 0-8 and 0-10 metadata is used to + * Tests that message metadata and content are successfully read back from a + * store after it has been reloaded. Both 0-8 and 0-10 metadata is used to * verify their ability to co-exist within the store and be successful retrieved. */ public void testBDBMessagePersistence() throws Exception @@ -73,10 +76,10 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto // Split the content into 2 chunks for the 0-8 message, as per broker behaviour. // Use a single chunk for the 0-10 message as per broker behaviour. String bodyText = "jfhdjsflsdhfjdshfjdslhfjdslhfsjlhfsjkhfdsjkhfdsjkfhdslkjf"; - + ByteBuffer firstContentBytes_0_8 = ByteBuffer.wrap(bodyText.substring(0, 10).getBytes()); ByteBuffer secondContentBytes_0_8 = ByteBuffer.wrap(bodyText.substring(10).getBytes()); - + ByteBuffer completeContentBody_0_10 = ByteBuffer.wrap(bodyText.getBytes()); int bodySize = completeContentBody_0_10.limit(); @@ -100,12 +103,12 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto /* * Create and insert a 0-10 message (metadata and content) - */ + */ MessageProperties msgProps_0_10 = createMessageProperties_0_10(bodySize); DeliveryProperties delProps_0_10 = createDeliveryProperties_0_10(); Header header_0_10 = new Header(delProps_0_10, msgProps_0_10); - MessageTransfer xfr_0_10 = new MessageTransfer("destination", MessageAcceptMode.EXPLICIT, + MessageTransfer xfr_0_10 = new MessageTransfer("destination", MessageAcceptMode.EXPLICIT, MessageAcquireMode.PRE_ACQUIRED, header_0_10, completeContentBody_0_10); MessageMetaData_0_10 messageMetaData_0_10 = new MessageMetaData_0_10(xfr_0_10); @@ -120,7 +123,7 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto /* * reload the store only (read-only) */ - bdbStore = reloadStoreReadOnly(bdbStore); + bdbStore = reloadStore(bdbStore); /* * Read back and validate the 0-8 message metadata and content @@ -190,14 +193,14 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto private DeliveryProperties createDeliveryProperties_0_10() { DeliveryProperties delProps_0_10 = new DeliveryProperties(); - + delProps_0_10.setDeliveryMode(MessageDeliveryMode.PERSISTENT); delProps_0_10.setImmediate(true); delProps_0_10.setExchange("exchange12345"); delProps_0_10.setRoutingKey("routingKey12345"); delProps_0_10.setExpiration(5); delProps_0_10.setPriority(MessageDeliveryPriority.ABOVE_AVERAGE); - + return delProps_0_10; } @@ -207,24 +210,24 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto msgProps_0_10.setContentLength(bodySize); msgProps_0_10.setCorrelationId("qwerty".getBytes()); msgProps_0_10.setContentType("text/html"); - + return msgProps_0_10; } - /** + /** * Close the provided store and create a new (read-only) store to read back the data. - * - * Use this method instead of reloading the virtual host like other tests in order + * + * Use this method instead of reloading the virtual host like other tests in order * to avoid the recovery handler deleting the message for not being on a queue. */ - private BDBMessageStore reloadStoreReadOnly(BDBMessageStore messageStore) throws Exception + private BDBMessageStore reloadStore(BDBMessageStore messageStore) throws Exception { messageStore.close(); - File storePath = new File(String.valueOf(_config.getProperty("store.environment-path"))); BDBMessageStore newStore = new BDBMessageStore(); - newStore.configure(storePath, false); - newStore.start(); + newStore.configure("", _config.subset("store")); + + newStore.startWithNoRecover(); return newStore; } @@ -275,7 +278,61 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto props.getHeaders().setString("Test", "MST"); return props; } - + + public void testGetContentWithOffset() throws Exception + { + MessageStore store = getVirtualHost().getMessageStore(); + BDBMessageStore bdbStore = assertBDBStore(store); + StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreSingleChunkMessage_0_8(store); + long messageid_0_8 = storedMessage_0_8.getMessageNumber(); + + // normal case: offset is 0 + ByteBuffer dst = ByteBuffer.allocate(10); + int length = bdbStore.getContent(messageid_0_8, 0, dst); + assertEquals("Unexpected length", CONTENT_BYTES.length, length); + byte[] array = dst.array(); + assertTrue("Unexpected content", Arrays.equals(CONTENT_BYTES, array)); + + // offset is in the middle + dst = ByteBuffer.allocate(10); + length = bdbStore.getContent(messageid_0_8, 5, dst); + assertEquals("Unexpected length", 5, length); + array = dst.array(); + byte[] expected = new byte[10]; + System.arraycopy(CONTENT_BYTES, 5, expected, 0, 5); + assertTrue("Unexpected content", Arrays.equals(expected, array)); + + // offset beyond the content length + dst = ByteBuffer.allocate(10); + try + { + bdbStore.getContent(messageid_0_8, 15, dst); + fail("Should fail for the offset greater than message size"); + } + catch (RuntimeException e) + { + assertEquals("Unexpected exception message", "Offset 15 is greater than message size 10 for message id " + + messageid_0_8 + "!", e.getMessage()); + } + + // buffer is smaller then message size + dst = ByteBuffer.allocate(5); + length = bdbStore.getContent(messageid_0_8, 0, dst); + assertEquals("Unexpected length", 5, length); + array = dst.array(); + expected = new byte[5]; + System.arraycopy(CONTENT_BYTES, 0, expected, 0, 5); + assertTrue("Unexpected content", Arrays.equals(expected, array)); + + // buffer is smaller then message size, offset is not 0 + dst = ByteBuffer.allocate(5); + length = bdbStore.getContent(messageid_0_8, 2, dst); + assertEquals("Unexpected length", 5, length); + array = dst.array(); + expected = new byte[5]; + System.arraycopy(CONTENT_BYTES, 2, expected, 0, 5); + assertTrue("Unexpected content", Arrays.equals(expected, array)); + } /** * Tests that messages which are added to the store and then removed using the * public MessageStore interfaces are actually removed from the store by then @@ -287,11 +344,10 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto MessageStore store = getVirtualHost().getMessageStore(); BDBMessageStore bdbStore = assertBDBStore(store); - StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreMultiChunkMessage_0_8(store); + StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreSingleChunkMessage_0_8(store); long messageid_0_8 = storedMessage_0_8.getMessageNumber(); - - //remove the message in the fashion the broker normally would - storedMessage_0_8.remove(); + + bdbStore.removeMessage(messageid_0_8, true); //verify the removal using the BDB store implementation methods directly try @@ -308,29 +364,22 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto //expecting no content, allocate a 1 byte ByteBuffer dst = ByteBuffer.allocate(1); - assertEquals("Retrieved content when none was expected", + assertEquals("Retrieved content when none was expected", 0, bdbStore.getContent(messageid_0_8, 0, dst)); } - - private BDBMessageStore assertBDBStore(Object store) + private BDBMessageStore assertBDBStore(MessageStore store) { - if(!(store instanceof BDBMessageStore)) - { - fail("Test requires an instance of BDBMessageStore to proceed"); - } + + assertEquals("Test requires an instance of BDBMessageStore to proceed", BDBMessageStore.class, store.getClass()); return (BDBMessageStore) store; } - private StoredMessage<MessageMetaData> createAndStoreMultiChunkMessage_0_8(MessageStore store) + private StoredMessage<MessageMetaData> createAndStoreSingleChunkMessage_0_8(MessageStore store) { - byte[] body10Bytes = "0123456789".getBytes(); - byte[] body5Bytes = "01234".getBytes(); - - ByteBuffer chunk1 = ByteBuffer.wrap(body10Bytes); - ByteBuffer chunk2 = ByteBuffer.wrap(body5Bytes); + ByteBuffer chunk1 = ByteBuffer.wrap(CONTENT_BYTES); - int bodySize = body10Bytes.length + body5Bytes.length; + int bodySize = CONTENT_BYTES.length; //create and store the message using the MessageStore interface MessagePublishInfo pubInfoBody_0_8 = createPublishInfoBody_0_8(); @@ -342,7 +391,6 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto StoredMessage<MessageMetaData> storedMessage_0_8 = store.addMessage(messageMetaData_0_8); storedMessage_0_8.addContent(0, chunk1); - storedMessage_0_8.addContent(chunk1.limit(), chunk2); storedMessage_0_8.flushToStore(); return storedMessage_0_8; @@ -359,36 +407,36 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto BDBMessageStore bdbStore = assertBDBStore(log); - final AMQShortString mockQueueName = new AMQShortString("queueName"); - + final UUID mockQueueId = UUIDGenerator.generateUUID(); TransactionLogResource mockQueue = new TransactionLogResource() { - public String getResourceName() + @Override + public UUID getId() { - return mockQueueName.asString(); + return mockQueueId; } }; - - MessageStore.Transaction txn = log.newTransaction(); - + + Transaction txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, new MockMessage(1L)); txn.enqueueMessage(mockQueue, new MockMessage(5L)); txn.commitTran(); - List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); - + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueId); + assertEquals("Number of enqueued messages is incorrect", 2, enqueuedIds.size()); Long val = enqueuedIds.get(0); assertEquals("First Message is incorrect", 1L, val.longValue()); val = enqueuedIds.get(1); assertEquals("Second Message is incorrect", 5L, val.longValue()); } - - + + /** - * Tests transaction rollback before a commit has occurred by utilising the - * enqueue and dequeue methods available in the TransactionLog interface - * implemented by the store, and verifying the behaviour using BDB + * Tests transaction rollback before a commit has occurred by utilising the + * enqueue and dequeue methods available in the TransactionLog interface + * implemented by the store, and verifying the behaviour using BDB * implementation methods. */ public void testTranRollbackBeforeCommit() throws Exception @@ -397,39 +445,39 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto BDBMessageStore bdbStore = assertBDBStore(log); - final AMQShortString mockQueueName = new AMQShortString("queueName"); - + final UUID mockQueueId = UUIDGenerator.generateUUID(); TransactionLogResource mockQueue = new TransactionLogResource() { - public String getResourceName() + @Override + public UUID getId() { - return mockQueueName.asString(); + return mockQueueId; } }; - - MessageStore.Transaction txn = log.newTransaction(); - + + Transaction txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, new MockMessage(21L)); txn.abortTran(); - + txn = log.newTransaction(); txn.enqueueMessage(mockQueue, new MockMessage(22L)); txn.enqueueMessage(mockQueue, new MockMessage(23L)); txn.commitTran(); - List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); - + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueId); + assertEquals("Number of enqueued messages is incorrect", 2, enqueuedIds.size()); Long val = enqueuedIds.get(0); assertEquals("First Message is incorrect", 22L, val.longValue()); val = enqueuedIds.get(1); assertEquals("Second Message is incorrect", 23L, val.longValue()); } - + /** - * Tests transaction rollback after a commit has occurred by utilising the - * enqueue and dequeue methods available in the TransactionLog interface - * implemented by the store, and verifying the behaviour using BDB + * Tests transaction rollback after a commit has occurred by utilising the + * enqueue and dequeue methods available in the TransactionLog interface + * implemented by the store, and verifying the behaviour using BDB * implementation methods. */ public void testTranRollbackAfterCommit() throws Exception @@ -438,31 +486,31 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto BDBMessageStore bdbStore = assertBDBStore(log); - final AMQShortString mockQueueName = new AMQShortString("queueName"); - + final UUID mockQueueId = UUIDGenerator.generateUUID(); TransactionLogResource mockQueue = new TransactionLogResource() { - public String getResourceName() + @Override + public UUID getId() { - return mockQueueName.asString(); + return mockQueueId; } }; - - MessageStore.Transaction txn = log.newTransaction(); - + + Transaction txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, new MockMessage(30L)); txn.commitTran(); txn = log.newTransaction(); txn.enqueueMessage(mockQueue, new MockMessage(31L)); txn.abortTran(); - + txn = log.newTransaction(); txn.enqueueMessage(mockQueue, new MockMessage(32L)); txn.commitTran(); - - List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); - + + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueId); + assertEquals("Number of enqueued messages is incorrect", 2, enqueuedIds.size()); Long val = enqueuedIds.get(0); assertEquals("First Message is incorrect", 30L, val.longValue()); @@ -470,6 +518,7 @@ public class BDBMessageStoreTest extends org.apache.qpid.server.store.MessageSto assertEquals("Second Message is incorrect", 32L, val.longValue()); } + @SuppressWarnings("rawtypes") private static class MockMessage implements ServerMessage, EnqueableMessage { private long _messageId; diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java index bcbb7d8b72..122f846a2d 100644 --- a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java @@ -20,19 +20,36 @@ */ package org.apache.qpid.server.store.berkeleydb; +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicPublisher; +import javax.jms.TopicSession; +import javax.jms.TopicSubscriber; + import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.url.URLSyntaxException; -import javax.jms.*; - /** - * Prepares an older version brokers BDB store with the required + * Prepares an older version brokers BDB store with the required * contents for use in the BDBStoreUpgradeTest. * * NOTE: Must be used with the equivalent older version client! * - * The store will then be used to verify that the upgraded is - * completed properly and that once upgraded it functions as + * The store will then be used to verify that the upgraded is + * completed properly and that once upgraded it functions as * expected with the new broker. * */ @@ -43,9 +60,10 @@ public class BDBStoreUpgradeTestPreparer public static final String SELECTOR_SUB_NAME="mySelectorDurSubName"; public static final String SELECTOR_TOPIC_NAME="mySelectorUpgradeTopic"; public static final String QUEUE_NAME="myUpgradeQueue"; + public static final String NON_DURABLE_QUEUE_NAME="queue-non-durable"; private static AMQConnectionFactory _connFac; - private static final String CONN_URL = + private static final String CONN_URL = "amqp://guest:guest@clientid/test?brokerlist='tcp://localhost:5672'"; /** @@ -59,14 +77,28 @@ public class BDBStoreUpgradeTestPreparer private void prepareBroker() throws Exception { prepareQueues(); + prepareNonDurableQueue(); prepareDurableSubscriptionWithSelector(); prepareDurableSubscriptionWithoutSelector(); } + private void prepareNonDurableQueue() throws Exception + { + Connection connection = _connFac.createConnection(); + AMQSession<?, ?> session = (AMQSession<?,?>)connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + AMQShortString queueName = AMQShortString.valueOf(NON_DURABLE_QUEUE_NAME); + AMQDestination destination = (AMQDestination) session.createQueue(NON_DURABLE_QUEUE_NAME); + session.sendCreateQueue(queueName, false, false, false, null); + session.bindQueue(queueName, queueName, null, AMQShortString.valueOf("amq.direct"), destination); + MessageProducer messageProducer = session.createProducer(destination); + sendMessages(session, messageProducer, destination, DeliveryMode.PERSISTENT, 1024, 3); + connection.close(); + } + /** * Prepare a queue for use in testing message and binding recovery * after the upgrade is performed. - * + * * - Create a transacted session on the connection. * - Use a consumer to create the (durable by default) queue. * - Send 5 large messages to test (multi-frame) content recovery. @@ -74,7 +106,7 @@ public class BDBStoreUpgradeTestPreparer * - Commit the session. * - Send 5 small messages to test that uncommitted messages are not recovered. * following the upgrade. - * - Close the session. + * - Close the session. */ private void prepareQueues() throws Exception { @@ -114,9 +146,9 @@ public class BDBStoreUpgradeTestPreparer } /** - * Prepare a DurableSubscription backing queue for use in testing selector + * Prepare a DurableSubscription backing queue for use in testing selector * recovery and queue exclusivity marking during the upgrade process. - * + * * - Create a transacted session on the connection. * - Open and close a DurableSubscription with selector to create the backing queue. * - Send a message which matches the selector. @@ -145,7 +177,7 @@ public class BDBStoreUpgradeTestPreparer TopicSubscriber durSub1 = session.createDurableSubscriber(topic, SELECTOR_SUB_NAME,"testprop='true'", false); durSub1.close(); - // Create a publisher and send a persistent message which matches the selector + // Create a publisher and send a persistent message which matches the selector // followed by one that does not match, and another which matches but is not // committed and so should be 'lost' TopicSession pubSession = connection.createTopicSession(true, Session.SESSION_TRANSACTED); @@ -202,7 +234,7 @@ public class BDBStoreUpgradeTestPreparer connection.close(); } - public static void sendMessages(Session session, MessageProducer messageProducer, + public static void sendMessages(Session session, MessageProducer messageProducer, Destination dest, int deliveryMode, int length, int numMesages) throws JMSException { for (int i = 1; i <= numMesages; i++) @@ -213,7 +245,7 @@ public class BDBStoreUpgradeTestPreparer } } - public static void publishMessages(Session session, TopicPublisher publisher, + public static void publishMessages(Session session, TopicPublisher publisher, Destination dest, int deliveryMode, int length, int numMesages, String selectorProperty) throws JMSException { for (int i = 1; i <= numMesages; i++) @@ -227,8 +259,8 @@ public class BDBStoreUpgradeTestPreparer /** * Generates a string of a given length consisting of the sequence 0,1,2,..,9,0,1,2. - * - * @param length number of characters in the string + * + * @param length number of characters in the string * @return string sequence of the given length */ public static String generateString(int length) @@ -248,6 +280,7 @@ public class BDBStoreUpgradeTestPreparer */ public static void main(String[] args) throws Exception { + System.setProperty("qpid.dest_syntax", "BURL"); BDBStoreUpgradeTestPreparer producer = new BDBStoreUpgradeTestPreparer(); producer.prepareBroker(); } diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java index 55327e3b56..4e201d5473 100644 --- a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java @@ -20,36 +20,15 @@ */ package org.apache.qpid.server.store.berkeleydb; -import com.sleepycat.bind.tuple.TupleBinding; -import com.sleepycat.je.DatabaseEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.BasicContentHeaderProperties; -import org.apache.qpid.framing.ContentHeaderBody; -import org.apache.qpid.framing.MethodRegistry; -import org.apache.qpid.framing.ProtocolVersion; -import org.apache.qpid.framing.abstraction.MessagePublishInfo; -import org.apache.qpid.framing.abstraction.MessagePublishInfoImpl; -import org.apache.qpid.management.common.mbeans.ManagedQueue; -import org.apache.qpid.server.message.EnqueableMessage; -import org.apache.qpid.server.message.MessageMetaData; -import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.store.StoredMessage; -import org.apache.qpid.server.store.TransactionLogResource; -import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_4; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageContentKeyTupleBindingFactory; -import org.apache.qpid.server.store.berkeleydb.tuples.MessageMetaDataTupleBindingFactory; -import org.apache.qpid.test.utils.JMXTestUtils; -import org.apache.qpid.test.utils.QpidBrokerTestCase; -import org.apache.qpid.util.FileUtils; +import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.NON_DURABLE_QUEUE_NAME; import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.QUEUE_NAME; -import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.SUB_NAME; -import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.TOPIC_NAME; import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.SELECTOR_SUB_NAME; import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.SELECTOR_TOPIC_NAME; +import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.SUB_NAME; +import static org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer.TOPIC_NAME; + +import java.io.File; import javax.jms.Connection; import javax.jms.DeliveryMode; @@ -64,18 +43,18 @@ import javax.jms.TopicConnection; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; + +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.test.utils.JMXTestUtils; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.apache.qpid.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Tests upgrading a BDB store and using it with the new broker - * after the required contents are entered into the store using - * an old broker with the BDBStoreUpgradeTestPreparer. The store - * will then be used to verify that the upgraded is completed - * properly and that once upgraded it functions as expected with - * the new broker. + * Tests upgrading a BDB store on broker startup. + * The store will then be used to verify that the upgrade is completed + * properly and that once upgraded it functions as expected. */ public class BDBUpgradeTest extends QpidBrokerTestCase { @@ -84,43 +63,31 @@ public class BDBUpgradeTest extends QpidBrokerTestCase private static final String STRING_1024 = BDBStoreUpgradeTestPreparer.generateString(1024); private static final String STRING_1024_256 = BDBStoreUpgradeTestPreparer.generateString(1024*256); private static final String QPID_WORK_ORIG = System.getProperty("QPID_WORK"); - private static final String QPID_HOME = System.getProperty("QPID_HOME"); - private static final int VERSION_4 = 4; - private String _fromDir; - private String _toDir; - private String _toDirTwice; + private String _storeLocation; @Override public void setUp() throws Exception { assertNotNull("QPID_WORK must be set", QPID_WORK_ORIG); - assertNotNull("QPID_HOME must be set", QPID_HOME); - - _fromDir = QPID_HOME + "/bdbstore-to-upgrade/test-store"; - _toDir = getWorkDirBaseDir() + "/bdbstore/test-store"; - _toDirTwice = getWorkDirBaseDir() + "/bdbstore-upgraded-twice"; + _storeLocation = getWorkDirBaseDir() + "/bdbstore/test-store"; //Clear the two target directories if they exist. - File directory = new File(_toDir); - if (directory.exists() && directory.isDirectory()) - { - FileUtils.delete(directory, true); - } - directory = new File(_toDirTwice); + File directory = new File(_storeLocation); if (directory.exists() && directory.isDirectory()) { FileUtils.delete(directory, true); } - //Upgrade the test store. - upgradeBrokerStore(_fromDir, _toDir); + // copy store files + String src = getClass().getClassLoader().getResource("upgrade/bdbstore-v4/test-store").toURI().getPath(); + FileUtils.copyRecursive(new File(src), new File(_storeLocation)); //override the broker config used and then start the broker with the updated store _configFile = new File("build/etc/config-systests-bdb.xml"); setConfigurationProperty("management.enabled", "true"); - super.setUp(); + super.setUp(); } private String getWorkDirBaseDir() @@ -129,31 +96,6 @@ public class BDBUpgradeTest extends QpidBrokerTestCase } /** - * Tests that the core upgrade method of the store upgrade tool passes through the exception - * from the BDBMessageStore indicating that the data on disk can't be loaded as the previous - * version because it has already been upgraded. - * @throws Exception - */ - public void testMultipleUpgrades() throws Exception - { - //stop the broker started by setUp() in order to allow the second upgrade attempt to proceed - stopBroker(); - - try - { - new BDBStoreUpgrade(_toDir, _toDirTwice, null, false, true).upgradeFromVersion(VERSION_4); - fail("Second Upgrade Succeeded"); - } - catch (Exception e) - { - System.err.println("Showing stack trace, we are expecting an 'Unable to load BDBStore' error"); - e.printStackTrace(); - assertTrue("Incorrect Exception Thrown:" + e.getMessage(), - e.getMessage().contains("Unable to load BDBStore as version 4. Store on disk contains version 5 data")); - } - } - - /** * Test that the selector applied to the DurableSubscription was successfully * transfered to the new store, and functions as expected with continued use * by monitoring message count while sending new messages to the topic and then @@ -175,26 +117,26 @@ public class BDBUpgradeTest extends QpidBrokerTestCase try { ManagedQueue dursubQueue = jmxUtils.getManagedQueue("clientid" + ":" + SELECTOR_SUB_NAME); - assertEquals("DurableSubscription backing queue should have 1 message on it initially", + assertEquals("DurableSubscription backing queue should have 1 message on it initially", new Integer(1), dursubQueue.getMessageCount()); - + // Create a connection and start it TopicConnection connection = (TopicConnection) getConnection(); connection.start(); - + // Send messages which don't match and do match the selector, checking message count - TopicSession pubSession = connection.createTopicSession(true, org.apache.qpid.jms.Session.SESSION_TRANSACTED); + TopicSession pubSession = connection.createTopicSession(true, Session.SESSION_TRANSACTED); Topic topic = pubSession.createTopic(SELECTOR_TOPIC_NAME); TopicPublisher publisher = pubSession.createPublisher(topic); - + BDBStoreUpgradeTestPreparer.publishMessages(pubSession, publisher, topic, DeliveryMode.PERSISTENT, 1*1024, 1, "false"); pubSession.commit(); - assertEquals("DurableSubscription backing queue should still have 1 message on it", + assertEquals("DurableSubscription backing queue should still have 1 message on it", Integer.valueOf(1), dursubQueue.getMessageCount()); - + BDBStoreUpgradeTestPreparer.publishMessages(pubSession, publisher, topic, DeliveryMode.PERSISTENT, 1*1024, 1, "true"); pubSession.commit(); - assertEquals("DurableSubscription backing queue should now have 2 messages on it", + assertEquals("DurableSubscription backing queue should now have 2 messages on it", Integer.valueOf(2), dursubQueue.getMessageCount()); TopicSubscriber durSub = pubSession.createDurableSubscriber(topic, SELECTOR_SUB_NAME,"testprop='true'", false); @@ -240,7 +182,7 @@ public class BDBUpgradeTest extends QpidBrokerTestCase connection.start(); // Send new message matching the topic, checking message count - TopicSession session = connection.createTopicSession(true, org.apache.qpid.jms.Session.SESSION_TRANSACTED); + TopicSession session = connection.createTopicSession(true, Session.SESSION_TRANSACTED); Topic topic = session.createTopic(TOPIC_NAME); TopicPublisher publisher = session.createPublisher(topic); @@ -298,10 +240,10 @@ public class BDBUpgradeTest extends QpidBrokerTestCase /** * Test that the upgraded queue continues to function properly when used - * for persistent messaging and restarting the broker. - * + * for persistent messaging and restarting the broker. + * * Sends the new messages to the queue BEFORE consuming those which were - * sent before the upgrade. In doing so, this also serves to test that + * sent before the upgrade. In doing so, this also serves to test that * the queue bindings were successfully transitioned during the upgrade. */ public void testBindingAndMessageDurabability() throws Exception @@ -329,7 +271,7 @@ public class BDBUpgradeTest extends QpidBrokerTestCase } /** - * Test that all of the committed persistent messages previously sent to + * Test that all of the committed persistent messages previously sent to * the broker are properly received following update of the MetaData and * Content entries during the store upgrade process. */ @@ -349,200 +291,22 @@ public class BDBUpgradeTest extends QpidBrokerTestCase * * @throws Exception */ - public void testMigrationOfMessagesForNonExistingQueues() throws Exception + public void testMigrationOfMessagesForNonDurableQueues() throws Exception { - stopBroker(); - - // copy store data into a new location for adding of phantom message - File storeLocation = new File(_fromDir); - File target = new File(_toDirTwice); - if (!target.exists()) - { - target.mkdirs(); - } - FileUtils.copyRecursive(storeLocation, target); - - // delete migrated data - File directory = new File(_toDir); - if (directory.exists() && directory.isDirectory()) - { - FileUtils.delete(directory, true); - } - - // test data - String nonExistingQueueName = getTestQueueName(); - String messageText = "Test Phantom Message"; - - // add message - addMessageForNonExistingQueue(target, VERSION_4, nonExistingQueueName, messageText); - - String[] inputs = { "Yes", "Yes", "Yes" }; - upgradeBrokerStoreInInterractiveMode(_toDirTwice, _toDir, inputs); - - // start broker - startBroker(); - // Create a connection and start it Connection connection = getConnection(); connection.start(); // consume a message for non-existing store Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - Queue queue = session.createQueue(nonExistingQueueName); + Queue queue = session.createQueue(NON_DURABLE_QUEUE_NAME); MessageConsumer messageConsumer = session.createConsumer(queue); - Message message = messageConsumer.receive(1000); - - // assert consumed message - assertNotNull("Message was not migrated!", message); - assertTrue("Unexpected message received!", message instanceof TextMessage); - String text = ((TextMessage) message).getText(); - assertEquals("Message migration failed!", messageText, text); - } - /** - * An utility method to upgrade broker with simulation user interactions - * - * @param fromDir - * location of the store to migrate - * @param toDir - * location of where migrated data will be stored - * @param inputs - * user answers on upgrade tool questions - * @throws Exception - */ - private void upgradeBrokerStoreInInterractiveMode(String fromDir, String toDir, final String[] inputs) - throws Exception - { - // save to restore system.in after data migration - InputStream stdin = System.in; - - // set fake system in to simulate user interactions - // FIXME: it is a quite dirty simulator of system input but it does the job - System.setIn(new InputStream() + for (int i = 0; i < 3; i++) { - - private int counter = 0; - - public synchronized int read(byte b[], int off, int len) - { - byte[] src = (inputs[counter] + "\n").getBytes(); - System.arraycopy(src, 0, b, off, src.length); - counter++; - return src.length; - } - - @Override - public int read() throws IOException - { - return -1; - } - }); - - try - { - // Upgrade the test store. - new BDBStoreUpgrade(fromDir, toDir, null, true, true).upgradeFromVersion(VERSION_4); - } - finally - { - // restore system in - System.setIn(stdin); - } - } - - @SuppressWarnings("unchecked") - private void addMessageForNonExistingQueue(File storeLocation, int storeVersion, String nonExistingQueueName, - String messageText) throws Exception - { - final AMQShortString queueName = new AMQShortString(nonExistingQueueName); - BDBMessageStore store = new BDBMessageStore(storeVersion); - store.configure(storeLocation, false); - try - { - store.start(); - - // store message objects - ByteBuffer completeContentBody = ByteBuffer.wrap(messageText.getBytes("UTF-8")); - long bodySize = completeContentBody.limit(); - MessagePublishInfo pubInfoBody = new MessagePublishInfoImpl(new AMQShortString("amq.direct"), false, - false, queueName); - BasicContentHeaderProperties props = new BasicContentHeaderProperties(); - props.setDeliveryMode(Integer.valueOf(BasicContentHeaderProperties.PERSISTENT).byteValue()); - props.setContentType("text/plain"); - props.setType("text/plain"); - props.setMessageId("whatever"); - props.setEncoding("UTF-8"); - props.getHeaders().setString("Test", "MST"); - MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); - int classForBasic = methodRegistry.createBasicQosOkBody().getClazz(); - ContentHeaderBody contentHeaderBody = new ContentHeaderBody(classForBasic, 1, props, bodySize); - - // add content entry to database - final long messageId = store.getNewMessageId(); - TupleBinding<MessageContentKey> contentKeyTB = new MessageContentKeyTupleBindingFactory(storeVersion).getInstance(); - MessageContentKey contentKey = null; - if (storeVersion == VERSION_4) - { - contentKey = new MessageContentKey_4(messageId, 0); - } - else - { - throw new Exception(storeVersion + " is not supported"); - } - DatabaseEntry key = new DatabaseEntry(); - contentKeyTB.objectToEntry(contentKey, key); - DatabaseEntry data = new DatabaseEntry(); - ContentTB contentTB = new ContentTB(); - contentTB.objectToEntry(completeContentBody, data); - store.getContentDb().put(null, key, data); - - // add meta data entry to database - TupleBinding<Long> longTB = TupleBinding.getPrimitiveBinding(Long.class); - TupleBinding<Object> metaDataTB = new MessageMetaDataTupleBindingFactory(storeVersion).getInstance(); - key = new DatabaseEntry(); - data = new DatabaseEntry(); - longTB.objectToEntry(new Long(messageId), key); - MessageMetaData metaData = new MessageMetaData(pubInfoBody, contentHeaderBody, 1); - metaDataTB.objectToEntry(metaData, data); - store.getMetaDataDb().put(null, key, data); - - // add delivery entry to database - TransactionLogResource mockQueue = new TransactionLogResource() - { - public String getResourceName() - { - return queueName.asString(); - } - }; - - EnqueableMessage mockMessage = new EnqueableMessage() - { - - public long getMessageNumber() - { - return messageId; - } - - public boolean isPersistent() - { - return true; - } - - public StoredMessage getStoredMessage() - { - return null; - } - }; - - MessageStore log = (MessageStore) store; - MessageStore.Transaction txn = log.newTransaction(); - txn.enqueueMessage(mockQueue, mockMessage); - txn.commitTran(); - } - finally - { - // close store - store.close(); + Message message = messageConsumer.receive(1000); + assertNotNull("Message was not migrated!", message); + assertTrue("Unexpected message received!", message instanceof TextMessage); } } @@ -564,7 +328,7 @@ public class BDBUpgradeTest extends QpidBrokerTestCase } - // Retrieve the matching message + // Retrieve the matching message Message m = durSub.receive(2000); assertNotNull("Failed to receive an expected message", m); if(selector) @@ -623,8 +387,4 @@ public class BDBUpgradeTest extends QpidBrokerTestCase session.close(); } - private void upgradeBrokerStore(String fromDir, String toDir) throws Exception - { - new BDBStoreUpgrade(_fromDir, _toDir, null, false, true).upgradeFromVersion(VERSION_4); - } } diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBindingTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBindingTest.java new file mode 100644 index 0000000000..f8aeb7f7b0 --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBindingTest.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.tuple; + +import junit.framework.TestCase; + +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.store.ConfiguredObjectRecord; + +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class ConfiguredObjectBindingTest extends TestCase +{ + + private ConfiguredObjectRecord _object; + + private static final String DUMMY_ATTRIBUTES_STRING = "dummyAttributes"; + private static final String DUMMY_TYPE_STRING = "dummyType"; + private ConfiguredObjectBinding _configuredObjectBinding; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _configuredObjectBinding = ConfiguredObjectBinding.getInstance(); + _object = new ConfiguredObjectRecord(UUIDGenerator.generateUUID(), DUMMY_TYPE_STRING, DUMMY_ATTRIBUTES_STRING); + } + + public void testObjectToEntryAndEntryToObject() + { + TupleOutput tupleOutput = new TupleOutput(); + + _configuredObjectBinding.objectToEntry(_object, tupleOutput); + + byte[] entryAsBytes = tupleOutput.getBufferBytes(); + TupleInput tupleInput = new TupleInput(entryAsBytes); + + ConfiguredObjectRecord storedObject = _configuredObjectBinding.entryToObject(tupleInput); + assertEquals("Unexpected attributes", DUMMY_ATTRIBUTES_STRING, storedObject.getAttributes()); + assertEquals("Unexpected type", DUMMY_TYPE_STRING, storedObject.getType()); + } +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractUpgradeTestCase.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractUpgradeTestCase.java new file mode 100644 index 0000000000..36991b90d0 --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractUpgradeTestCase.java @@ -0,0 +1,153 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.io.File; + +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.subjects.TestBlankSubject; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.util.FileUtils; + +import com.sleepycat.je.Database; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.je.Transaction; + +public abstract class AbstractUpgradeTestCase extends QpidTestCase +{ + protected static final class StaticAnswerHandler implements UpgradeInteractionHandler + { + private UpgradeInteractionResponse _response; + + public StaticAnswerHandler(UpgradeInteractionResponse response) + { + _response = response; + } + + @Override + public UpgradeInteractionResponse requireResponse(String question, UpgradeInteractionResponse defaultResponse, + UpgradeInteractionResponse... possibleResponses) + { + return _response; + } + } + + public static final String[] QUEUE_NAMES = { "clientid:myDurSubName", "clientid:mySelectorDurSubName", "myUpgradeQueue", + "queue-non-durable" }; + public static int[] QUEUE_SIZES = { 1, 1, 10, 3 }; + public static int TOTAL_MESSAGE_NUMBER = 15; + protected static final LogSubject LOG_SUBJECT = new TestBlankSubject(); + + // one binding per exchange + protected static final int TOTAL_BINDINGS = QUEUE_NAMES.length * 2; + protected static final int TOTAL_EXCHANGES = 5; + + private File _storeLocation; + protected Environment _environment; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _storeLocation = copyStore(getStoreDirectoryName()); + + _environment = createEnvironment(_storeLocation); + } + + /** @return eg "bdbstore-v4" - used for copying store */ + protected abstract String getStoreDirectoryName(); + + protected Environment createEnvironment(File storeLocation) + { + EnvironmentConfig envConfig = new EnvironmentConfig(); + envConfig.setAllowCreate(true); + envConfig.setTransactional(true); + envConfig.setConfigParam("je.lock.nLockTables", "7"); + envConfig.setReadOnly(false); + envConfig.setSharedCache(false); + envConfig.setCacheSize(0); + return new Environment(storeLocation, envConfig); + } + + @Override + public void tearDown() throws Exception + { + try + { + _environment.close(); + } + finally + { + _environment = null; + deleteDirectoryIfExists(_storeLocation); + } + super.tearDown(); + } + + private File copyStore(String storeDirectoryName) throws Exception + { + String src = getClass().getClassLoader().getResource("upgrade/" + storeDirectoryName).toURI().getPath(); + File storeLocation = new File(new File(TMP_FOLDER), "test-store"); + deleteDirectoryIfExists(storeLocation); + FileUtils.copyRecursive(new File(src), new File(TMP_FOLDER)); + return storeLocation; + } + + protected void deleteDirectoryIfExists(File dir) + { + if (dir.exists()) + { + assertTrue("The provided file " + dir + " is not a directory", dir.isDirectory()); + + boolean deletedSuccessfully = FileUtils.delete(dir, true); + + assertTrue("Files at '" + dir + "' should have been deleted", deletedSuccessfully); + } + } + + protected void assertDatabaseRecordCount(String databaseName, final long expectedCountNumber) + { + long count = getDatabaseCount(databaseName); + assertEquals("Unexpected database '" + databaseName + "' entry number", expectedCountNumber, count); + } + + protected long getDatabaseCount(String databaseName) + { + DatabaseCallable<Long> operation = new DatabaseCallable<Long>() + { + + @Override + public Long call(Database sourceDatabase, Database targetDatabase, Transaction transaction) + { + return new Long(sourceDatabase.count()); + + } + }; + Long count = new DatabaseTemplate(_environment, databaseName, null).call(operation); + return count.longValue(); + } + + public String getVirtualHostName() + { + return getName(); + } +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java new file mode 100644 index 0000000000..7ec442b73d --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import junit.framework.TestCase; + +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.Environment; +import com.sleepycat.je.Transaction; + +public class DatabaseTemplateTest extends TestCase +{ + private static final String SOURCE_DATABASE = "sourceDatabase"; + private Environment _environment; + private Database _sourceDatabase; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _environment = mock(Environment.class); + _sourceDatabase = mock(Database.class); + when(_environment.openDatabase(any(Transaction.class), same(SOURCE_DATABASE), isA(DatabaseConfig.class))) + .thenReturn(_sourceDatabase); + } + + public void testExecuteWithTwoDatabases() + { + String targetDatabaseName = "targetDatabase"; + Database targetDatabase = mock(Database.class); + + Transaction txn = mock(Transaction.class); + + when(_environment.openDatabase(same(txn), same(targetDatabaseName), isA(DatabaseConfig.class))) + .thenReturn(targetDatabase); + + DatabaseTemplate databaseTemplate = new DatabaseTemplate(_environment, SOURCE_DATABASE, targetDatabaseName, txn); + + DatabaseRunnable databaseOperation = mock(DatabaseRunnable.class); + databaseTemplate.run(databaseOperation); + + verify(databaseOperation).run(_sourceDatabase, targetDatabase, txn); + verify(_sourceDatabase).close(); + verify(targetDatabase).close(); + } + + public void testExecuteWithOneDatabases() + { + DatabaseTemplate databaseTemplate = new DatabaseTemplate(_environment, SOURCE_DATABASE, null, null); + + DatabaseRunnable databaseOperation = mock(DatabaseRunnable.class); + databaseTemplate.run(databaseOperation); + + verify(databaseOperation).run(_sourceDatabase, (Database)null, (Transaction)null); + verify(_sourceDatabase).close(); + } + +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4to5Test.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4to5Test.java new file mode 100644 index 0000000000..3f9e4e4aa1 --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4to5Test.java @@ -0,0 +1,299 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.store.berkeleydb.BDBStoreUpgradeTestPreparer; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.BindingRecord; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.BindingTuple; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.MessageContentKey; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.MessageContentKeyBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.QueueEntryKey; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.QueueEntryKeyBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom4To5.QueueRecord; + +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.Transaction; + +public class UpgradeFrom4to5Test extends AbstractUpgradeTestCase +{ + private static final String NON_DURABLE_QUEUE = BDBStoreUpgradeTestPreparer.NON_DURABLE_QUEUE_NAME; + private static final String DURABLE_QUEUE = BDBStoreUpgradeTestPreparer.QUEUE_NAME; + private static final String DURABLE_SUBSCRIPTION_QUEUE_WITH_SELECTOR = "clientid:mySelectorDurSubName"; + private static final String DURABLE_SUBSCRIPTION_QUEUE = "clientid:myDurSubName"; + private static final String EXCHANGE_DB_NAME = "exchangeDb_v5"; + private static final String MESSAGE_META_DATA_DB_NAME = "messageMetaDataDb_v5"; + private static final String MESSAGE_CONTENT_DB_NAME = "messageContentDb_v5"; + private static final String DELIVERY_DB_NAME = "deliveryDb_v5"; + private static final String BINDING_DB_NAME = "queueBindingsDb_v5"; + + @Override + protected String getStoreDirectoryName() + { + return "bdbstore-v4"; + } + + public void testPerformUpgradeWithHandlerAnsweringYes() throws Exception + { + UpgradeFrom4To5 upgrade = new UpgradeFrom4To5(); + upgrade.performUpgrade(_environment, new StaticAnswerHandler(UpgradeInteractionResponse.YES), getVirtualHostName()); + + assertQueues(new HashSet<String>(Arrays.asList(QUEUE_NAMES))); + + assertDatabaseRecordCount(DELIVERY_DB_NAME, TOTAL_MESSAGE_NUMBER); + assertDatabaseRecordCount(MESSAGE_META_DATA_DB_NAME, TOTAL_MESSAGE_NUMBER); + assertDatabaseRecordCount(EXCHANGE_DB_NAME, TOTAL_EXCHANGES); + + for (int i = 0; i < QUEUE_SIZES.length; i++) + { + assertQueueMessages(QUEUE_NAMES[i], QUEUE_SIZES[i]); + } + + final List<BindingRecord> queueBindings = loadBindings(); + + assertEquals("Unxpected list size", TOTAL_BINDINGS, queueBindings.size()); + assertBindingRecord(queueBindings, DURABLE_SUBSCRIPTION_QUEUE, "amq.topic", BDBStoreUpgradeTestPreparer.TOPIC_NAME, ""); + assertBindingRecord(queueBindings, DURABLE_SUBSCRIPTION_QUEUE_WITH_SELECTOR, "amq.topic", + BDBStoreUpgradeTestPreparer.SELECTOR_TOPIC_NAME, "testprop='true'"); + assertBindingRecord(queueBindings, DURABLE_QUEUE, "amq.direct", DURABLE_QUEUE, null); + assertBindingRecord(queueBindings, NON_DURABLE_QUEUE, "amq.direct", NON_DURABLE_QUEUE, null); + assertContent(); + } + + public void testPerformUpgradeWithHandlerAnsweringNo() throws Exception + { + UpgradeFrom4To5 upgrade = new UpgradeFrom4To5(); + upgrade.performUpgrade(_environment, new StaticAnswerHandler(UpgradeInteractionResponse.NO), getVirtualHostName()); + assertQueues(new HashSet<String>(Arrays.asList(DURABLE_SUBSCRIPTION_QUEUE, DURABLE_SUBSCRIPTION_QUEUE_WITH_SELECTOR, DURABLE_QUEUE))); + + assertDatabaseRecordCount(DELIVERY_DB_NAME, 12); + assertDatabaseRecordCount(MESSAGE_META_DATA_DB_NAME, 12); + assertDatabaseRecordCount(EXCHANGE_DB_NAME, TOTAL_EXCHANGES); + + assertQueueMessages(DURABLE_SUBSCRIPTION_QUEUE, 1); + assertQueueMessages(DURABLE_SUBSCRIPTION_QUEUE_WITH_SELECTOR, 1); + assertQueueMessages(DURABLE_QUEUE, 10); + + final List<BindingRecord> queueBindings = loadBindings(); + + assertEquals("Unxpected list size", TOTAL_BINDINGS - 2, queueBindings.size()); + assertBindingRecord(queueBindings, DURABLE_SUBSCRIPTION_QUEUE, "amq.topic", BDBStoreUpgradeTestPreparer.TOPIC_NAME, + ""); + assertBindingRecord(queueBindings, DURABLE_SUBSCRIPTION_QUEUE_WITH_SELECTOR, "amq.topic", + BDBStoreUpgradeTestPreparer.SELECTOR_TOPIC_NAME, "testprop='true'"); + assertBindingRecord(queueBindings, DURABLE_QUEUE, "amq.direct", DURABLE_QUEUE, null); + assertContent(); + } + + private List<BindingRecord> loadBindings() + { + final BindingTuple bindingTuple = new BindingTuple(); + final List<BindingRecord> queueBindings = new ArrayList<BindingRecord>(); + CursorOperation databaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + BindingRecord bindingRecord = bindingTuple.entryToObject(key); + + AMQShortString queueName = bindingRecord.getQueueName(); + AMQShortString exchangeName = bindingRecord.getExchangeName(); + AMQShortString routingKey = bindingRecord.getRoutingKey(); + FieldTable arguments = bindingRecord.getArguments(); + queueBindings.add(new BindingRecord(exchangeName, queueName, routingKey, arguments)); + } + }; + new DatabaseTemplate(_environment, BINDING_DB_NAME, null).run(databaseOperation); + return queueBindings; + } + + private void assertBindingRecord(List<BindingRecord> queueBindings, String queueName, String exchangeName, + String routingKey, String selectorKey) + { + BindingRecord record = null; + for (BindingRecord bindingRecord : queueBindings) + { + if (bindingRecord.getQueueName().asString().equals(queueName) + && bindingRecord.getExchangeName().asString().equals(exchangeName)) + { + record = bindingRecord; + break; + } + } + assertNotNull("Binding is not found for queue " + queueName + " and exchange " + exchangeName, record); + assertEquals("Unexpected routing key", routingKey, record.getRoutingKey().asString()); + + if (selectorKey != null) + { + assertEquals("Unexpected selector key for " + queueName, selectorKey, + record.getArguments().get(AMQPFilterTypes.JMS_SELECTOR.getValue())); + } + } + + private void assertQueueMessages(final String queueName, final int expectedQueueSize) + { + final Set<Long> messageIdsForQueue = assertDeliveriesForQueue(queueName, expectedQueueSize); + + assertMetadataForQueue(queueName, expectedQueueSize, messageIdsForQueue); + + assertContentForQueue(queueName, expectedQueueSize, messageIdsForQueue); + } + + private Set<Long> assertDeliveriesForQueue(final String queueName, final int expectedQueueSize) + { + final QueueEntryKeyBinding queueEntryKeyBinding = new QueueEntryKeyBinding(); + final AtomicInteger deliveryCounter = new AtomicInteger(); + final Set<Long> messagesForQueue = new HashSet<Long>(); + + CursorOperation deliveryDatabaseOperation = new CursorOperation() + { + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + QueueEntryKey entryKey = queueEntryKeyBinding.entryToObject(key); + String thisQueueName = entryKey.getQueueName().asString(); + if (thisQueueName.equals(queueName)) + { + deliveryCounter.incrementAndGet(); + messagesForQueue.add(entryKey.getMessageId()); + } + } + }; + new DatabaseTemplate(_environment, DELIVERY_DB_NAME, null).run(deliveryDatabaseOperation); + + assertEquals("Unxpected number of entries in delivery db for queue " + queueName, expectedQueueSize, + deliveryCounter.get()); + + return messagesForQueue; + } + + private void assertMetadataForQueue(final String queueName, final int expectedQueueSize, + final Set<Long> messageIdsForQueue) + { + final AtomicInteger metadataCounter = new AtomicInteger(); + CursorOperation databaseOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + Long messageId = LongBinding.entryToLong(key); + + boolean messageIsForTheRightQueue = messageIdsForQueue.contains(messageId); + if (messageIsForTheRightQueue) + { + metadataCounter.incrementAndGet(); + } + } + }; + new DatabaseTemplate(_environment, MESSAGE_META_DATA_DB_NAME, null).run(databaseOperation); + + assertEquals("Unxpected number of entries in metadata db for queue " + queueName, expectedQueueSize, + metadataCounter.get()); + } + + private void assertContentForQueue(String queueName, int expectedQueueSize, final Set<Long> messageIdsForQueue) + { + final AtomicInteger contentCounter = new AtomicInteger(); + final MessageContentKeyBinding keyBinding = new MessageContentKeyBinding(); + CursorOperation cursorOperation = new CursorOperation() + { + private long _prevMsgId = -1; + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + MessageContentKey contentKey = keyBinding.entryToObject(key); + long msgId = contentKey.getMessageId(); + + if (_prevMsgId != msgId && messageIdsForQueue.contains(msgId)) + { + contentCounter.incrementAndGet(); + } + + _prevMsgId = msgId; + } + }; + new DatabaseTemplate(_environment, MESSAGE_CONTENT_DB_NAME, null).run(cursorOperation); + + assertEquals("Unxpected number of entries in content db for queue " + queueName, expectedQueueSize, + contentCounter.get()); + } + + private void assertQueues(Set<String> expectedQueueNames) + { + List<AMQShortString> durableSubNames = new ArrayList<AMQShortString>(); + final UpgradeFrom4To5.QueueRecordBinding binding = new UpgradeFrom4To5.QueueRecordBinding(durableSubNames); + final Set<String> actualQueueNames = new HashSet<String>(); + + CursorOperation queueNameCollector = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + QueueRecord record = binding.entryToObject(value); + String queueName = record.getNameShortString().asString(); + actualQueueNames.add(queueName); + } + }; + new DatabaseTemplate(_environment, "queueDb_v5", null).run(queueNameCollector); + + assertEquals("Unexpected queue names", expectedQueueNames, actualQueueNames); + } + + private void assertContent() + { + final UpgradeFrom4To5.ContentBinding contentBinding = new UpgradeFrom4To5.ContentBinding(); + CursorOperation contentCursorOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, DatabaseEntry key, + DatabaseEntry value) + { + long id = LongBinding.entryToLong(key); + assertTrue("Unexpected id", id > 0); + ByteBuffer content = contentBinding.entryToObject(value); + assertNotNull("Unexpected content", content); + } + }; + new DatabaseTemplate(_environment, MESSAGE_CONTENT_DB_NAME, null).run(contentCursorOperation); + } +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java new file mode 100644 index 0000000000..5297692820 --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java @@ -0,0 +1,395 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.CONFIGURED_OBJECTS_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NEW_CONTENT_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NEW_DELIVERY_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NEW_METADATA_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NEW_XID_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.OLD_CONTENT_DB_NAME; +import static org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.OLD_XID_DB_NAME; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.store.berkeleydb.entry.Xid; +import org.apache.qpid.server.store.berkeleydb.tuple.XidBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.CompoundKey; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.CompoundKeyBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.ConfiguredObjectBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.UpgradeConfiguredObjectRecord; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewDataBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewPreparedTransaction; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewPreparedTransactionBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewQueueEntryBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewQueueEntryKey; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.NewRecordImpl; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.OldPreparedTransaction; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.OldPreparedTransactionBinding; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.OldRecordImpl; +import org.apache.qpid.server.store.berkeleydb.upgrade.UpgradeFrom5To6.UpgradeUUIDBinding; +import org.apache.qpid.server.util.MapJsonSerializer; + +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.Environment; +import com.sleepycat.je.LockMode; +import com.sleepycat.je.Transaction; + +public class UpgradeFrom5To6Test extends AbstractUpgradeTestCase +{ + private static final Logger _logger = Logger.getLogger(UpgradeFrom5To6Test.class); + + @Override + protected String getStoreDirectoryName() + { + return "bdbstore-v5"; + } + + public void testPerformUpgrade() throws Exception + { + UpgradeFrom5To6 upgrade = new UpgradeFrom5To6(); + upgrade.performUpgrade(_environment, UpgradeInteractionHandler.DEFAULT_HANDLER, getVirtualHostName()); + + assertDatabaseRecordCounts(); + assertContent(); + + assertConfiguredObjects(); + assertQueueEntries(); + } + + public void testPerformUpgradeWithMissingMessageChunkKeepsIncompleteMessage() throws Exception + { + corruptDatabase(); + + UpgradeFrom5To6 upgrade = new UpgradeFrom5To6(); + upgrade.performUpgrade(_environment, new StaticAnswerHandler(UpgradeInteractionResponse.YES), getVirtualHostName()); + + assertDatabaseRecordCounts(); + + assertConfiguredObjects(); + assertQueueEntries(); + } + + public void testPerformUpgradeWithMissingMessageChunkDiscardsIncompleteMessage() throws Exception + { + corruptDatabase(); + + UpgradeFrom5To6 upgrade = new UpgradeFrom5To6(); + + UpgradeInteractionHandler discardMessageInteractionHandler = new StaticAnswerHandler(UpgradeInteractionResponse.NO); + + upgrade.performUpgrade(_environment, discardMessageInteractionHandler, getVirtualHostName()); + + assertDatabaseRecordCount(NEW_METADATA_DB_NAME, 11); + assertDatabaseRecordCount(NEW_CONTENT_DB_NAME, 11); + + assertConfiguredObjects(); + assertQueueEntries(); + } + + public void testPerformXidUpgrade() throws Exception + { + File storeLocation = new File(TMP_FOLDER, getName()); + storeLocation.mkdirs(); + Environment environment = createEnvironment(storeLocation); + try + { + populateOldXidEntries(environment); + UpgradeFrom5To6 upgrade = new UpgradeFrom5To6(); + upgrade.performUpgrade(environment, UpgradeInteractionHandler.DEFAULT_HANDLER, getVirtualHostName()); + assertXidEntries(environment); + } + finally + { + try + { + environment.close(); + } + finally + { + deleteDirectoryIfExists(storeLocation); + } + + } + } + + private void assertXidEntries(Environment environment) + { + final DatabaseEntry value = new DatabaseEntry(); + final DatabaseEntry key = getXidKey(); + new DatabaseTemplate(environment, NEW_XID_DB_NAME, null).run(new DatabaseRunnable() + { + + @Override + public void run(Database xidDatabase, Database nullDatabase, Transaction transaction) + { + xidDatabase.get(null, key, value, LockMode.DEFAULT); + } + }); + NewPreparedTransactionBinding newBinding = new NewPreparedTransactionBinding(); + NewPreparedTransaction newTransaction = newBinding.entryToObject(value); + NewRecordImpl[] newEnqueues = newTransaction.getEnqueues(); + NewRecordImpl[] newDequeues = newTransaction.getDequeues(); + assertEquals("Unxpected new enqueus number", 1, newEnqueues.length); + NewRecordImpl enqueue = newEnqueues[0]; + assertEquals("Unxpected queue id", UUIDGenerator.generateUUID("TEST1", getVirtualHostName()), enqueue.getId()); + assertEquals("Unxpected message id", 1, enqueue.getMessageNumber()); + assertEquals("Unxpected new dequeues number", 1, newDequeues.length); + NewRecordImpl dequeue = newDequeues[0]; + assertEquals("Unxpected queue id", UUIDGenerator.generateUUID("TEST2", getVirtualHostName()), dequeue.getId()); + assertEquals("Unxpected message id", 2, dequeue.getMessageNumber()); + } + + private void populateOldXidEntries(Environment environment) + { + + final DatabaseEntry value = new DatabaseEntry(); + OldRecordImpl[] enqueues = { new OldRecordImpl("TEST1", 1) }; + OldRecordImpl[] dequeues = { new OldRecordImpl("TEST2", 2) }; + OldPreparedTransaction oldPreparedTransaction = new OldPreparedTransaction(enqueues, dequeues); + OldPreparedTransactionBinding oldPreparedTransactionBinding = new OldPreparedTransactionBinding(); + oldPreparedTransactionBinding.objectToEntry(oldPreparedTransaction, value); + + final DatabaseEntry key = getXidKey(); + new DatabaseTemplate(environment, OLD_XID_DB_NAME, null).run(new DatabaseRunnable() + { + + @Override + public void run(Database xidDatabase, Database nullDatabase, Transaction transaction) + { + xidDatabase.put(null, key, value); + } + }); + } + + protected DatabaseEntry getXidKey() + { + final DatabaseEntry value = new DatabaseEntry(); + byte[] globalId = { 1 }; + byte[] branchId = { 2 }; + Xid xid = new Xid(1l, globalId, branchId); + XidBinding xidBinding = XidBinding.getInstance(); + xidBinding.objectToEntry(xid, value); + return value; + } + + private void assertQueueEntries() + { + final Map<UUID, UpgradeConfiguredObjectRecord> configuredObjects = loadConfiguredObjects(); + final NewQueueEntryBinding newBinding = new NewQueueEntryBinding(); + CursorOperation cursorOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + NewQueueEntryKey newEntryRecord = newBinding.entryToObject(key); + assertTrue("Unexpected queue id", configuredObjects.containsKey(newEntryRecord.getQueueId())); + } + }; + new DatabaseTemplate(_environment, NEW_DELIVERY_DB_NAME, null).run(cursorOperation); + } + + /** + * modify the chunk offset of a message to be wrong, so we can test logic + * that preserves incomplete messages + */ + private void corruptDatabase() + { + CursorOperation cursorOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + CompoundKeyBinding binding = new CompoundKeyBinding(); + CompoundKey originalCompoundKey = binding.entryToObject(key); + int corruptedOffset = originalCompoundKey.getOffset() + 2; + CompoundKey corruptedCompoundKey = new CompoundKey(originalCompoundKey.getMessageId(), corruptedOffset); + DatabaseEntry newKey = new DatabaseEntry(); + binding.objectToEntry(corruptedCompoundKey, newKey); + + _logger.info("Deliberately corrupted message id " + originalCompoundKey.getMessageId() + + ", changed offset from " + originalCompoundKey.getOffset() + " to " + + corruptedCompoundKey.getOffset()); + + deleteCurrent(); + sourceDatabase.put(transaction, newKey, value); + + abort(); + } + }; + + Transaction transaction = _environment.beginTransaction(null, null); + new DatabaseTemplate(_environment, OLD_CONTENT_DB_NAME, transaction).run(cursorOperation); + transaction.commit(); + } + + private void assertDatabaseRecordCounts() + { + assertDatabaseRecordCount(CONFIGURED_OBJECTS_DB_NAME, 9); + assertDatabaseRecordCount(NEW_DELIVERY_DB_NAME, 12); + + assertDatabaseRecordCount(NEW_METADATA_DB_NAME, 12); + assertDatabaseRecordCount(NEW_CONTENT_DB_NAME, 12); + } + + private void assertConfiguredObjects() + { + Map<UUID, UpgradeConfiguredObjectRecord> configuredObjects = loadConfiguredObjects(); + assertEquals("Unexpected number of configured objects", 9, configuredObjects.size()); + + Set<Map<String, Object>> expected = new HashSet<Map<String, Object>>(9); + Map<String, Object> queue1 = new HashMap<String, Object>(); + queue1.put("exclusive", Boolean.FALSE); + queue1.put("name", "myUpgradeQueue"); + queue1.put("owner", null); + expected.add(queue1); + Map<String, Object> queue2 = new HashMap<String, Object>(); + queue2.put("exclusive", Boolean.TRUE); + queue2.put("name", "clientid:mySelectorDurSubName"); + queue2.put("owner", "clientid"); + expected.add(queue2); + Map<String, Object> queue3 = new HashMap<String, Object>(); + queue3.put("exclusive", Boolean.TRUE); + queue3.put("name", "clientid:myDurSubName"); + queue3.put("owner", "clientid"); + expected.add(queue3); + + Map<String, Object> queueBinding1 = new HashMap<String, Object>(); + queueBinding1.put("queue", UUIDGenerator.generateUUID("myUpgradeQueue", getVirtualHostName()).toString()); + queueBinding1.put("name", "myUpgradeQueue"); + queueBinding1.put("exchange", UUIDGenerator.generateUUID("<<default>>", getVirtualHostName()).toString()); + expected.add(queueBinding1); + Map<String, Object> queueBinding2 = new HashMap<String, Object>(); + queueBinding2.put("queue", UUIDGenerator.generateUUID("myUpgradeQueue", getVirtualHostName()).toString()); + queueBinding2.put("name", "myUpgradeQueue"); + queueBinding2.put("exchange", UUIDGenerator.generateUUID("amq.direct", getVirtualHostName()).toString()); + Map<String, Object> arguments2 = new HashMap<String, Object>(); + arguments2.put("x-filter-jms-selector", ""); + queueBinding2.put("arguments", arguments2); + expected.add(queueBinding2); + Map<String, Object> queueBinding3 = new HashMap<String, Object>(); + queueBinding3.put("queue", UUIDGenerator.generateUUID("clientid:myDurSubName", getVirtualHostName()).toString()); + queueBinding3.put("name", "myUpgradeTopic"); + queueBinding3.put("exchange", UUIDGenerator.generateUUID("amq.topic", getVirtualHostName()).toString()); + Map<String, Object> arguments3 = new HashMap<String, Object>(); + arguments3.put("x-filter-jms-selector", ""); + queueBinding3.put("arguments", arguments3); + expected.add(queueBinding3); + Map<String, Object> queueBinding4 = new HashMap<String, Object>(); + queueBinding4.put("queue", UUIDGenerator.generateUUID("clientid:mySelectorDurSubName", getVirtualHostName()).toString()); + queueBinding4.put("name", "mySelectorUpgradeTopic"); + queueBinding4.put("exchange", UUIDGenerator.generateUUID("amq.topic", getVirtualHostName()).toString()); + Map<String, Object> arguments4 = new HashMap<String, Object>(); + arguments4.put("x-filter-jms-selector", "testprop='true'"); + queueBinding4.put("arguments", arguments4); + expected.add(queueBinding4); + Map<String, Object> queueBinding5 = new HashMap<String, Object>(); + queueBinding5.put("queue", UUIDGenerator.generateUUID("clientid:myDurSubName", getVirtualHostName()).toString()); + queueBinding5.put("name", "clientid:myDurSubName"); + queueBinding5.put("exchange", UUIDGenerator.generateUUID("<<default>>", getVirtualHostName()).toString()); + expected.add(queueBinding5); + Map<String, Object> queueBinding6 = new HashMap<String, Object>(); + queueBinding6.put("queue", UUIDGenerator.generateUUID("clientid:mySelectorDurSubName", getVirtualHostName()).toString()); + queueBinding6.put("name", "clientid:mySelectorDurSubName"); + queueBinding6.put("exchange", UUIDGenerator.generateUUID("<<default>>", getVirtualHostName()).toString()); + expected.add(queueBinding6); + + Set<String> expectedTypes = new HashSet<String>(); + expectedTypes.add(Queue.class.getName()); + expectedTypes.add(Exchange.class.getName()); + expectedTypes.add(Binding.class.getName()); + MapJsonSerializer jsonSerializer = new MapJsonSerializer(); + for (Entry<UUID, UpgradeConfiguredObjectRecord> entry : configuredObjects.entrySet()) + { + UpgradeConfiguredObjectRecord object = entry.getValue(); + UUID key = entry.getKey(); + Map<String, Object> deserialized = jsonSerializer.deserialize(object.getAttributes()); + assertTrue("Unexpected entry:" + object.getAttributes(), expected.remove(deserialized)); + String type = object.getType(); + assertTrue("Unexpected type:" + type, expectedTypes.contains(type)); + if (type.equals(Exchange.class.getName()) || type.equals(Queue.class.getName())) + { + assertEquals("Unexpected key", key, UUIDGenerator.generateUUID(((String) deserialized.get("name")), getVirtualHostName())); + } + else + { + assertNotNull("Key cannot be null", key); + } + } + assertTrue("Not all expected configured objects found:" + expected, expected.isEmpty()); + } + + private Map<UUID, UpgradeConfiguredObjectRecord> loadConfiguredObjects() + { + final Map<UUID, UpgradeConfiguredObjectRecord> configuredObjectsRecords = new HashMap<UUID, UpgradeConfiguredObjectRecord>(); + final ConfiguredObjectBinding binding = new ConfiguredObjectBinding(); + final UpgradeUUIDBinding uuidBinding = new UpgradeUUIDBinding(); + CursorOperation configuredObjectsCursor = new CursorOperation() + { + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + UUID id = uuidBinding.entryToObject(key); + UpgradeConfiguredObjectRecord object = binding.entryToObject(value); + configuredObjectsRecords.put(id, object); + } + }; + new DatabaseTemplate(_environment, CONFIGURED_OBJECTS_DB_NAME, null).run(configuredObjectsCursor); + return configuredObjectsRecords; + } + + private void assertContent() + { + final NewDataBinding contentBinding = new NewDataBinding(); + CursorOperation contentCursorOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, + DatabaseEntry key, DatabaseEntry value) + { + long id = LongBinding.entryToLong(key); + assertTrue("Unexpected id", id > 0); + byte[] content = contentBinding.entryToObject(value); + assertNotNull("Unexpected content", content); + } + }; + new DatabaseTemplate(_environment, NEW_CONTENT_DB_NAME, null).run(contentCursorOperation); + } +} diff --git a/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderTest.java b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderTest.java new file mode 100644 index 0000000000..ba5ca842bf --- /dev/null +++ b/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderTest.java @@ -0,0 +1,138 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store.berkeleydb.upgrade; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; +import org.apache.qpid.server.store.berkeleydb.tuple.ContentBinding; + +import com.sleepycat.bind.tuple.IntegerBinding; +import com.sleepycat.bind.tuple.LongBinding; +import com.sleepycat.je.Cursor; +import com.sleepycat.je.Database; +import com.sleepycat.je.DatabaseConfig; +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.OperationStatus; +import com.sleepycat.je.Transaction; + +public class UpgraderTest extends AbstractUpgradeTestCase +{ + private Upgrader _upgrader; + + @Override + protected String getStoreDirectoryName() + { + return "bdbstore-v4"; + } + + @Override + public void setUp() throws Exception + { + super.setUp(); + _upgrader = new Upgrader(_environment, getVirtualHostName()); + } + + private int getStoreVersion() + { + DatabaseConfig dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + dbConfig.setAllowCreate(true); + int storeVersion = -1; + Database versionDb = null; + Cursor cursor = null; + try + { + versionDb = _environment.openDatabase(null, Upgrader.VERSION_DB_NAME, dbConfig); + cursor = versionDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + DatabaseEntry value = new DatabaseEntry(); + while (cursor.getNext(key, value, null) == OperationStatus.SUCCESS) + { + int version = IntegerBinding.entryToInt(key); + if (storeVersion < version) + { + storeVersion = version; + } + } + } + finally + { + if (cursor != null) + { + cursor.close(); + } + if (versionDb != null) + { + versionDb.close(); + } + } + return storeVersion; + } + + public void testUpgrade() throws Exception + { + assertEquals("Unexpected store version", -1, getStoreVersion()); + _upgrader.upgradeIfNecessary(); + assertEquals("Unexpected store version", BDBMessageStore.VERSION, getStoreVersion()); + assertContent(); + } + + public void testEmptyDatabaseUpgradeDoesNothing() throws Exception + { + File nonExistentStoreLocation = new File(TMP_FOLDER, getName()); + deleteDirectoryIfExists(nonExistentStoreLocation); + + nonExistentStoreLocation.mkdir(); + _environment = createEnvironment(nonExistentStoreLocation); + _upgrader = new Upgrader(_environment, getVirtualHostName()); + _upgrader.upgradeIfNecessary(); + + List<String> databaseNames = _environment.getDatabaseNames(); + List<String> expectedDatabases = new ArrayList<String>(); + expectedDatabases.add(Upgrader.VERSION_DB_NAME); + assertEquals("Expectedonly VERSION table in initially empty store after upgrade: ", expectedDatabases, databaseNames); + assertEquals("Unexpected store version", BDBMessageStore.VERSION, getStoreVersion()); + + nonExistentStoreLocation.delete(); + } + + private void assertContent() + { + final ContentBinding contentBinding = ContentBinding.getInstance(); + CursorOperation contentCursorOperation = new CursorOperation() + { + + @Override + public void processEntry(Database sourceDatabase, Database targetDatabase, Transaction transaction, DatabaseEntry key, + DatabaseEntry value) + { + long id = LongBinding.entryToLong(key); + assertTrue("Unexpected id", id > 0); + byte[] content = contentBinding.entryToObject(value); + assertNotNull("Unexpected content", content); + } + }; + new DatabaseTemplate(_environment, "MESSAGE_CONTENT", null).run(contentCursorOperation); + } +} diff --git a/java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb b/java/bdbstore/src/test/resources/upgrade/bdbstore-v4/test-store/00000000.jdb Binary files differindex 38158a55e7..167ab7f0ca 100644 --- a/java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb +++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v4/test-store/00000000.jdb diff --git a/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/readme.txt b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/readme.txt new file mode 100644 index 0000000000..a7e754f967 --- /dev/null +++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/readme.txt @@ -0,0 +1,5 @@ +The bdbstore v5 data were obtained by upgrading the bdbstore v4 data as part of running +test UpgradeFrom4to5Test#testPerformUpgradeWithHandlerAnsweringNo. + +The rationale for not using BDBStoreUpgradeTestPreparer in this case is that we need chunked content. +Current implementation of BDBMessageStore only stores messages in one chunk.
\ No newline at end of file diff --git a/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000000.jdb b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000000.jdb Binary files differnew file mode 100644 index 0000000000..d44b21a83e --- /dev/null +++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000000.jdb diff --git a/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000001.jdb b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000001.jdb Binary files differnew file mode 100644 index 0000000000..9b85860c19 --- /dev/null +++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000001.jdb |
