summaryrefslogtreecommitdiff
path: root/java/bdbstore
diff options
context:
space:
mode:
authorKim van der Riet <kpvdr@apache.org>2012-05-04 15:39:19 +0000
committerKim van der Riet <kpvdr@apache.org>2012-05-04 15:39:19 +0000
commit633c33f224f3196f3f9bd80bd2e418d8143fea06 (patch)
tree1391da89470593209466df68c0b40b89c14963b1 /java/bdbstore
parentc73f9286ebff93a6c8dbc29cf05e258c4b55c976 (diff)
downloadqpid-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')
-rwxr-xr-xjava/bdbstore/bin/backup.sh2
-rwxr-xr-xjava/bdbstore/bin/storeUpgrade.sh41
-rw-r--r--java/bdbstore/build.xml9
-rwxr-xr-xjava/bdbstore/etc/scripts/bdbtest.sh43
-rw-r--r--java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml52
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java2
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AbstractBDBMessageStore.java1825
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java2152
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreFactory.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java)27
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java1299
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java52
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java49
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java59
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/PreparedTransaction.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java)26
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/QueueEntryKey.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java)27
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/entry/Xid.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java)32
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/BindingRecord.java62
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java53
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java66
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBinding.java37
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/ContentBinding.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java)29
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/MessageMetaDataBinding.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.java)26
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/PreparedTransactionBinding.java127
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/QueueEntryBinding.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java)29
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/StringMapBinding.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/StringMapBinding.java)6
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/UUIDTupleBinding.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/UUIDTupleBinding.java)8
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuple/XidBinding.java70
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java76
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java47
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java46
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java45
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java170
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java43
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java46
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java70
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java73
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractStoreUpgrade.java77
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorOperation.java89
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/CursorTemplate.java75
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseCallable.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java)8
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseEntryCallback.java30
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseRunnable.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java)20
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplate.java114
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/StoreUpgrade.java31
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4To5.java915
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6.java1207
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionHandler.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java)28
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeInteractionResponse.java (renamed from java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java)7
-rw-r--r--java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/upgrade/Upgrader.java177
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreConfigurationTest.java14
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java211
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java63
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java324
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/tuple/ConfiguredObjectBindingTest.java61
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/AbstractUpgradeTestCase.java153
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/DatabaseTemplateTest.java83
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom4to5Test.java299
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgradeFrom5To6Test.java395
-rw-r--r--java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/upgrade/UpgraderTest.java138
-rw-r--r--java/bdbstore/src/test/resources/upgrade/bdbstore-v4/test-store/00000000.jdb (renamed from java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb)bin1346092 -> 1357197 bytes
-rw-r--r--java/bdbstore/src/test/resources/upgrade/bdbstore-v5/readme.txt5
-rw-r--r--java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000000.jdbbin0 -> 1357227 bytes
-rw-r--r--java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000001.jdbbin0 -> 1332881 bytes
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
index 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
Binary files differ
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
new file mode 100644
index 0000000000..d44b21a83e
--- /dev/null
+++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000000.jdb
Binary files differ
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
new file mode 100644
index 0000000000..9b85860c19
--- /dev/null
+++ b/java/bdbstore/src/test/resources/upgrade/bdbstore-v5/test-store/00000001.jdb
Binary files differ