diff options
Diffstat (limited to 'qpid/java')
153 files changed, 9280 insertions, 1620 deletions
diff --git a/qpid/java/bdbstore/bin/backup.sh b/qpid/java/bdbstore/bin/backup.sh new file mode 100755 index 0000000000..0e2f0fda09 --- /dev/null +++ b/qpid/java/bdbstore/bin/backup.sh @@ -0,0 +1,40 @@ +#!/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 arguments 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 + +WHEREAMI=`dirname $0` +if [ -z "$QPID_HOME" ]; then + export QPID_HOME=`cd $WHEREAMI/../ && pwd` +fi +VERSION=0.13 + +LIBS=$QPID_HOME/lib/je-4.0.103.jar:$QPID_HOME/lib/qpid-bdbstore-$VERSION.jar:$QPID_HOME/lib/qpid-all.jar + + +echo "Starting Hot Backup Script" +java -Dlog4j.configuration=backup-log4j.xml ${JAVA_OPTS} -cp $LIBS org.apache.qpid.server.store.berkeleydb.BDBBackup ${ARGS} diff --git a/qpid/java/bdbstore/bin/storeUpgrade.sh b/qpid/java/bdbstore/bin/storeUpgrade.sh new file mode 100755 index 0000000000..076b9d3f7e --- /dev/null +++ b/qpid/java/bdbstore/bin/storeUpgrade.sh @@ -0,0 +1,43 @@ +#!/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 +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 + export QPID_HOME=$(dirname $(dirname $(readlink -f $0))) + export PATH=${PATH}:${QPID_HOME}/bin +fi + +if [ -z "$BDB_HOME" ]; then + export BDB_HOME=$(dirname $(dirname $(readlink -f $0))) +fi + +VERSION=0.13 + +LIBS=$BDB_HOME/lib/je-4.0.103.jar:$BDB_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/qpid/java/bdbstore/build.xml b/qpid/java/bdbstore/build.xml new file mode 100644 index 0000000000..9355358e6c --- /dev/null +++ b/qpid/java/bdbstore/build.xml @@ -0,0 +1,84 @@ +<!-- + - 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. + --> +<project name="bdbstore" default="build"> + <property name="module.depends" value="common client management/common broker perftests systests" /> + <property name="module.test.depends" value="test common/test broker/test management/common perftests systests" /> + + <import file="../module.xml" /> + + <property name="bdb.lib.dir" value="${project.root}/lib/bdbstore" /> + <property name="bdb.version" value="4.0.103" /> + <property name="bdb.download.url" value="http://download.oracle.com/maven/com/sleepycat/je/${bdb.version}/je-${bdb.version}.jar" /> + <property name="bdb.jar.file" value="${bdb.lib.dir}/je-${bdb.version}.jar" /> + + <!--check whether the BDB jar is present, possibly after download--> + <target name="check-bdb-jar"> + <available file="${bdb.jar.file}" type="file" property="bdb.jar.available"/> + </target> + + <!--echo that BDB is required if it isnt present, with associated licencing note--> + <target name="bdb-jar-required" depends="bdb-licence-note-optional" unless="bdb.jar.available"> + <echo>The BDB JE library is required to use this optional module. + +The jar file may be downloaded by either: + + Seperately running the following command from the qpid/java/bdbstore dir: ant download-bdb + + OR + + Adding -Ddownload-bdb=true to your regular build command.</echo> + <fail>The BDB JE library was not found</fail> + </target> + + <!--issue BDB licencing note if BDB isnt already present--> + <target name="bdb-licence-note-optional" depends="check-bdb-jar" unless="bdb.jar.available"> + <antcall target="bdb-licence-note"/> + </target> + + <!--issue BDB licencing note--> + <target name="bdb-licence-note"> + <echo>*NOTE* The BDB JE library required by this optional module is licensed under the Sleepycat Licence, which is not compatible with the Apache Licence v2.0. + +For a copy of the Sleepycat Licence, please see: +http://www.oracle.com/technetwork/database/berkeleydb/downloads/jeoslicense-086837.html</echo> + </target> + + <!--check if an inline BDB download was requested with the build--> + <target name="check-request-props" if="download-bdb"> + <antcall target="download-bdb"/> + </target> + + <!--download BDB, with licencing note--> + <target name="download-bdb" depends="bdb-licence-note"> + <mkdir dir="${bdb.lib.dir}"/> + <echo>Downloading BDB JE</echo> + <get src="${bdb.download.url}" dest="${bdb.jar.file}" usetimestamp="true" /> + </target> + + <target name="build" depends="check-request-props, bdb-jar-required, module.build" /> + + <target name="postbuild" depends="copy-store-to-upgrade" /> + + <target name="copy-store-to-upgrade" description="copy the upgrade tool resource folder contents into the build tree"> + <copy todir="${qpid.home}" failonerror="true"> + <fileset dir="src/test/resources/upgrade"/> + </copy> + </target> + +</project> diff --git a/qpid/java/bdbstore/etc/scripts/bdbbackuptest.sh b/qpid/java/bdbstore/etc/scripts/bdbbackuptest.sh new file mode 100755 index 0000000000..2a0b72e5ad --- /dev/null +++ b/qpid/java/bdbstore/etc/scripts/bdbbackuptest.sh @@ -0,0 +1,44 @@ +#!/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.5 + +# 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.4.0.jar:$QPID_HOME/lib/qpid-bdbstore-$VERSION.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.server.store.berkeleydb.testclient.BackupTestClient -o $QPID_WORK/results numMessagesToAction=55 ${ARGS} + diff --git a/qpid/java/bdbstore/etc/scripts/bdbtest.sh b/qpid/java/bdbstore/etc/scripts/bdbtest.sh new file mode 100755 index 0000000000..eafdae9710 --- /dev/null +++ b/qpid/java/bdbstore/etc/scripts/bdbtest.sh @@ -0,0 +1,43 @@ +#!/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.5 + +# 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.4.0.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/qpid/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml b/qpid/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml new file mode 100644 index 0000000000..4d71963ea7 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/BDBStoreUpgrade.log4j.xml @@ -0,0 +1,52 @@ +<?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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java new file mode 100644 index 0000000000..8b887b1876 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncoding.java @@ -0,0 +1,59 @@ +/* + * + * 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 org.apache.qpid.framing.AMQShortString; + +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class AMQShortStringEncoding +{ + public static AMQShortString readShortString(TupleInput tupleInput) + { + int length = (int) tupleInput.readShort(); + if (length < 0) + { + return null; + } + else + { + byte[] stringBytes = new byte[length]; + tupleInput.readFast(stringBytes); + return new AMQShortString(stringBytes); + } + + } + + public static void writeShortString(AMQShortString shortString, TupleOutput tupleOutput) + { + + if (shortString == null) + { + tupleOutput.writeShort(-1); + } + else + { + tupleOutput.writeShort(shortString.length()); + tupleOutput.writeFast(shortString.getBytes(), 0, shortString.length()); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java new file mode 100644 index 0000000000..81ae315fe2 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringTB.java @@ -0,0 +1,48 @@ +/* + * + * 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; + +public class AMQShortStringTB extends TupleBinding +{ + private static final Logger _log = Logger.getLogger(AMQShortStringTB.class); + + + public AMQShortStringTB() + { + } + + public Object entryToObject(TupleInput tupleInput) + { + return AMQShortStringEncoding.readShortString(tupleInput); + } + + public void objectToEntry(Object object, TupleOutput tupleOutput) + { + AMQShortStringEncoding.writeShortString((AMQShortString)object, tupleOutput); + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java new file mode 100644 index 0000000000..c515ca5d8e --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBBackup.java @@ -0,0 +1,344 @@ +/* + * + * 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.DatabaseException; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.je.util.DbBackup; + +import org.apache.log4j.Logger; + +import org.apache.qpid.util.CommandLineParser; +import org.apache.qpid.util.FileUtils; + +import java.io.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +/** + * BDBBackup is a utility for taking hot backups of the current state of a BDB transaction log database. + * + * <p/>This utility makes the following assumptions/performs the following actions: + * + * <p/><ul> <li>The from and to directory locations will already exist. This scripts does not create them. <li>If this + * script fails to complete in one minute it will terminate. <li>This script always exits with code 1 on error, code 0 + * on success (standard unix convention). <li>This script will log out at info level, when it starts and ends and a list + * of all files backed up. <li>This script logs all errors at error level. <li>This script does not perform regular + * backups, wrap its calling script in a cron job or similar to do this. </ul> + * + * <p/>This utility is build around the BDB provided backup helper utility class, DbBackup. This utility class provides + * an ability to force BDB to stop writing to the current log file set, whilst the backup is taken, to ensure that a + * consistent snapshot is acquired. Preventing BDB from writing to the current log file set, does not stop BDB from + * continuing to run concurrently while the backup is running, it simply moves onto a new set of log files; this + * provides a 'hot' backup facility. + * + * <p/>DbBackup can also help with incremental backups, by providing the number of the last log file backed up. + * Subsequent backups can be taken, from later log files only. In a messaging application, messages are not expected to + * be long-lived in most cases, so the log files will usually have been completely turned over between backups. This + * utility does not support incremental backups for this reason. + * + * <p/>If the database is locked by BDB, as is required when using transactions, and therefore will always be the case + * in Qpid, this utility cannot make use of the DbBackup utility in a seperate process. DbBackup, needs to ensure that + * the BDB envinronment used to take the backup has exclusive write access to the log files. This utility can take a + * backup as a standalone utility against log files, when a broker is not running, using the {@link #takeBackup(String, + *String,com.sleepycat.je.Environment)} method. + * + * <p/>A seperate backup machanism is provided by the {@link #takeBackupNoLock(String,String)} method which can take a + * hot backup against a running broker. This works by finding out the set of files to copy, and then opening them all to + * read, and repeating this process until a consistent set of open files is obtained. This is done to avoid the + * situation where the BDB cleanup thread deletes a file, between the directory listing and opening of the file to copy. + * All consistently opened files are copied. This is the default mechanism the the {@link #main} method of this utility + * uses. + * + * <p/><table id="crc><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations <tr><td> Hot copy all + * BDB log files from one directory to another. </table> + */ +public class BDBBackup +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(BDBBackup.class); + + /** Used for communicating with the user. */ + private static final Logger console = Logger.getLogger("Console"); + + /** Defines the suffix used to identify BDB log files. */ + private static final String LOG_FILE_SUFFIX = ".jdb"; + + /** Defines the command line format for this utility. */ + public static final String[][] COMMAND_LINE_SPEC = + new String[][] + { + { "fromdir", "The path to the directory to back the bdb log file from.", "dir", "true" }, + { "todir", "The path to the directory to save the backed up bdb log files to.", "dir", "true" } + }; + + /** Defines the timeout to terminate the backup operation on if it fails to complete. One minte. */ + public static final long TIMEOUT = 60000; + + /** + * Runs a backup of the BDB log files in a specified directory, copying the backed up files to another specified + * directory. + * + * <p/>The following arguments must be specified: + * + * <p/><table><caption>Command Line</caption> <tr><th> Option <th> Comment <tr><td> -fromdir <td> The path to the + * directory to back the bdb log file from. <tr><td> -todir <td> The path to the directory to save the backed up + * bdb log files to. </table> + * + * @param args The command line arguments. + */ + public static void main(String[] args) + { + // Process the command line using standard handling (errors and usage followed by System.exit when it is wrong). + Properties options = + CommandLineParser.processCommandLine(args, new CommandLineParser(COMMAND_LINE_SPEC), System.getProperties()); + + // Extract the from and to directory locations and perform a backup between them. + try + { + String fromDir = options.getProperty("fromdir"); + String toDir = options.getProperty("todir"); + + log.info("BDBBackup Utility: Starting Hot Backup."); + + BDBBackup bdbBackup = new BDBBackup(); + String[] backedUpFiles = bdbBackup.takeBackupNoLock(fromDir, toDir); + + if (log.isInfoEnabled()) + { + log.info("BDBBackup Utility: Hot Backup Completed. Files backed up: " + backedUpFiles); + } + } + catch (Exception e) + { + console.info("Backup script encountered an error and has failed: " + e.getMessage()); + log.error("Backup script got exception: " + e.getMessage(), e); + System.exit(1); + } + } + + /** + * Creates a backup of the BDB log files in the source directory, copying them to the destination directory. + * + * @param fromdir The source directory path. + * @param todir The destination directory path. + * @param environment An open BDB environment to perform the back up. + * + * @throws DatabaseException Any underlying execeptions from BDB are allowed to fall through. + */ + public void takeBackup(String fromdir, String todir, Environment environment) throws DatabaseException + { + DbBackup backupHelper = null; + + try + { + backupHelper = new DbBackup(environment); + + // Prevent BDB from writing to its log files while the backup it taken. + backupHelper.startBackup(); + + // Back up the BDB log files to the destination directory. + String[] filesForBackup = backupHelper.getLogFilesInBackupSet(); + + for (int i = 0; i < filesForBackup.length; i++) + { + File sourceFile = new File(fromdir + File.separator + filesForBackup[i]); + File destFile = new File(todir + File.separator + filesForBackup[i]); + FileUtils.copy(sourceFile, destFile); + } + } + finally + { + // Remember to exit backup mode, or all log files won't be cleaned and disk usage will bloat. + if (backupHelper != null) + { + backupHelper.endBackup(); + } + } + } + + /** + * Takes a hot backup when another process has locked the BDB database. + * + * @param fromdir The source directory path. + * @param todir The destination directory path. + * + * @return A list of all of the names of the files succesfully backed up. + */ + public String[] takeBackupNoLock(String fromdir, String todir) + { + if (log.isDebugEnabled()) + { + log.debug("public void takeBackupNoLock(String fromdir = " + fromdir + ", String todir = " + todir + + "): called"); + } + + File fromDirFile = new File(fromdir); + + if (!fromDirFile.isDirectory()) + { + throw new IllegalArgumentException("The specified fromdir(" + fromdir + + ") must be the directory containing your bdbstore."); + } + + File toDirFile = new File(todir); + + if (!toDirFile.exists()) + { + // Create directory if it doesn't exist + toDirFile.mkdirs(); + + if (log.isDebugEnabled()) + { + log.debug("Created backup directory:" + toDirFile); + } + } + + if (!toDirFile.isDirectory()) + { + throw new IllegalArgumentException("The specified todir(" + todir + ") must be a directory."); + } + + // Repeat until manage to open consistent set of files for reading. + boolean consistentSet = false; + FileInputStream[] fileInputStreams = new FileInputStream[0]; + File[] fileSet = new File[0]; + long start = System.currentTimeMillis(); + + while (!consistentSet) + { + // List all .jdb files in the directory. + fileSet = fromDirFile.listFiles(new FilenameFilter() + { + public boolean accept(File dir, String name) + { + return name.endsWith(LOG_FILE_SUFFIX); + } + }); + + // Open them all for reading. + fileInputStreams = new FileInputStream[fileSet.length]; + + if (fileSet.length == 0) + { + throw new RuntimeException("There are no BDB log files to backup in the " + fromdir + " directory."); + } + + for (int i = 0; i < fileSet.length; i++) + { + try + { + fileInputStreams[i] = new FileInputStream(fileSet[i]); + } + catch (FileNotFoundException e) + { + // Close any files opened for reading so far. + for (int j = 0; j < i; j++) + { + if (fileInputStreams[j] != null) + { + try + { + fileInputStreams[j].close(); + } + catch (IOException ioEx) + { + // Rethrow this as a runtime exception, as something strange has happened. + throw new RuntimeException(ioEx); + } + } + } + + // Could not open a consistent file set so try again. + break; + } + + // A consistent set has been opened if all files were sucesfully opened for reading. + if (i == (fileSet.length - 1)) + { + consistentSet = true; + } + } + + // Check that the script has not timed out, and raise an error if it has. + long now = System.currentTimeMillis(); + if ((now - start) > TIMEOUT) + { + throw new RuntimeException("Hot backup script failed to complete in " + (TIMEOUT / 1000) + " seconds."); + } + } + + // Copy the consistent set of open files. + List<String> backedUpFileNames = new LinkedList<String>(); + + for (int j = 0; j < fileSet.length; j++) + { + File destFile = new File(todir + File.separator + fileSet[j].getName()); + try + { + FileUtils.copy(fileSet[j], destFile); + } + catch (RuntimeException re) + { + Throwable cause = re.getCause(); + if ((cause != null) && (cause instanceof IOException)) + { + throw new RuntimeException(re.getMessage() + " fromDir:" + fromdir + " toDir:" + toDirFile, cause); + } + else + { + throw re; + } + } + + backedUpFileNames.add(destFile.getName()); + + // Close all of the files. + try + { + fileInputStreams[j].close(); + } + catch (IOException e) + { + // Rethrow this as a runtime exception, as something strange has happened. + throw new RuntimeException(e); + } + } + + return backedUpFileNames.toArray(new String[backedUpFileNames.size()]); + } + + /* + * Creates an environment for the bdb log files in the specified directory. This envrinonment can only be used + * to backup these files, if they are not locked by another database instance. + * + * @param fromdir The path to the directory to create the environment for. + * + * @throws DatabaseException Any underlying exceptions from BDB are allowed to fall through. + */ + private Environment createSourceDirEnvironment(String fromdir) throws DatabaseException + { + // Initialize the BDB backup utility on the source directory. + return new Environment(new File(fromdir), new EnvironmentConfig()); + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java new file mode 100644 index 0000000000..f900159808 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStore.java @@ -0,0 +1,2124 @@ +/* + * + * 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 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.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +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.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.queue.AMQQueue; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler; +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.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.TransactionLogResource; +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.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler.QueueEntryRecoveryHandler; +import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_5; +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 com.sleepycat.bind.EntryBinding; +import com.sleepycat.bind.tuple.ByteBinding; +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.LockMode; +import com.sleepycat.je.OperationStatus; +import com.sleepycat.je.TransactionConfig; + +/** + * BDBMessageStore implements a persistent {@link MessageStore} using the BDB high performance log. + * + * <p/><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations <tr><td> Accept + * transaction boundary demarcations: Begin, Commit, Abort. <tr><td> Store and remove queues. <tr><td> Store and remove + * 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 +{ + private static final Logger _log = Logger.getLogger(BDBMessageStore.class); + + 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 Database _messageMetaDataDb; + private Database _messageContentDb; + private Database _queueBindingsDb; + private Database _deliveryDb; + private Database _exchangeDb; + private Database _queueDb; + + /* ======= + * 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 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; + } + } + + public void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + _logSubject = logSubject; + CurrentActor.get().message(_logSubject, ConfigStoreMessages.CREATED(this.getClass().getName())); + + if(_configured) + { + throw new Exception("ConfigStore already configured"); + } + + 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) + { + throw new Exception("ConfigStore not 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) + { + throw new Exception("ConfigStore not configured"); + } + + recoverQueueEntries(recoveryHandler); + + + } + + public org.apache.qpid.server.store.TransactionLog.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 + { + if (_state != requiredState) + { + throw new AMQStoreException("Cannot transition to the state: " + newState + "; need to be in state: " + requiredState + + "; currently in state: " + _state); + } + + _state = newState; + } + + private void checkState(State requiredState) throws AMQStoreException + { + if (_state != requiredState) + { + throw new AMQStoreException("Unexpected state: " + _state + "; required state: " + requiredState); + } + } + + private boolean createEnvironment(File environmentPath, boolean readonly) throws DatabaseException + { + _log.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"); + + // Restore 500,000 default timeout. + //envConfig.setLockTimeout(15000); + + // 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(readonly); + try + { + _environment = new Environment(environmentPath, envConfig); + return false; + } + 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(); + } + _environment = new Environment(environmentPath, envConfig); + + return true; + } + else + { + throw de; + } + } + } + + 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 = _environment.openDatabase(null, MESSAGEMETADATADB_NAME, dbConfig); + _queueDb = _environment.openDatabase(null, QUEUEDB_NAME, dbConfig); + _exchangeDb = _environment.openDatabase(null, EXCHANGEDB_NAME, dbConfig); + _queueBindingsDb = _environment.openDatabase(null, QUEUEBINDINGSDB_NAME, dbConfig); + _messageContentDb = _environment.openDatabase(null, MESSAGECONTENTDB_NAME, dbConfig); + _deliveryDb = _environment.openDatabase(null, DELIVERYDB_NAME, 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(); + } + + 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); + + brh.completeBindingRecovery(); + } + 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 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 + BindingKey bindingRecord = (BindingKey) 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 recoverMessages(MessageStoreRecoveryHandler msrh) throws DatabaseException + { + StoredMessageRecoveryHandler mrh = msrh.begin(); + + Cursor cursor = null; + try + { + cursor = _messageMetaDataDb.openCursor(null, null); + DatabaseEntry key = new DatabaseEntry(); + EntryBinding keyBinding = TupleBinding.getPrimitiveBinding(Long.class);; + + DatabaseEntry value = new DatabaseEntry(); + EntryBinding valueBinding = _metaDataTupleBindingFactory.getInstance(); + + long maxId = 0; + + while (cursor.getNext(key, value, LockMode.RMW) == OperationStatus.SUCCESS) + { + long messageId = (Long) keyBinding.entryToObject(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 AMQInternalException If the operation fails for any reason. + */ + public void removeMessage(Long messageId) throws AMQStoreException + { + // _log.debug("public void removeMessage(Long messageId = " + messageId): called"); + + com.sleepycat.je.Transaction tx = null; + + Cursor cursor = null; + try + { + tx = _environment.beginTransaction(null, null); + + //remove the message meta data from the store + DatabaseEntry key = new DatabaseEntry(); + EntryBinding metaKeyBindingTuple = TupleBinding.getPrimitiveBinding(Long.class); + metaKeyBindingTuple.objectToEntry(messageId, key); + + if (_log.isDebugEnabled()) + { + _log.debug("Removing message id " + messageId); + } + + + OperationStatus status = _messageMetaDataDb.delete(tx, key); + if (status == OperationStatus.NOTFOUND) + { + tx.abort(); + + throw new AMQStoreException("Message metadata not found for message id " + messageId); + } + + if (_log.isDebugEnabled()) + { + _log.debug("Deleted metadata for message " + messageId); + } + + //now remove the content data from the store if there is any. + + DatabaseEntry contentKeyEntry = new DatabaseEntry(); + MessageContentKey_5 mck = new MessageContentKey_5(messageId,0); + + 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, 0, true); + + cursor = _messageContentDb.openCursor(tx, null); + + status = cursor.getSearchKeyRange(contentKeyEntry, value, LockMode.RMW); + while (status == OperationStatus.SUCCESS) + { + mck = (MessageContentKey_5) contentKeyTupleBinding.entryToObject(contentKeyEntry); + + if(mck.getMessageId() != messageId) + { + //we have exhausted all chunks for this message id, break + break; + } + else + { + status = cursor.delete(); + + if(status == OperationStatus.NOTFOUND) + { + cursor.close(); + cursor = null; + + tx.abort(); + throw new AMQStoreException("Content chunk offset" + mck.getOffset() + " not found for message " + messageId); + } + + if (_log.isDebugEnabled()) + { + _log.debug("Deleted content chunk offset " + mck.getOffset() + " for message " + messageId); + } + } + + status = cursor.getNext(contentKeyEntry, value, LockMode.RMW); + } + + cursor.close(); + cursor = null; + + commit(tx, true); + } + catch (DatabaseException e) + { + e.printStackTrace(); + + if (tx != null) + { + try + { + if(cursor != null) + { + cursor.close(); + cursor = null; + } + + tx.abort(); + } + 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(cursor != null) + { + try + { + cursor.close(); + } + catch (DatabaseException e) + { + throw new AMQStoreException("Error closing database connection: " + e.getMessage(), e); + } + } + } + } + + /** + * @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 + { + // _log.debug("public void bindQueue(Exchange exchange = " + exchange + ", AMQShortString routingKey = " + routingKey + // + ", AMQQueue queue = " + queue + ", FieldTable args = " + args + "): called"); + + if (_state != State.RECOVERING) + { + BindingKey bindingRecord = new BindingKey(exchange.getNameShortString(), + queue.getNameShortString(), routingKey, args); + + 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 " + queue.getName() + " to exchange " + + exchange.getName() + " 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 BindingKey(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); + } + } + + /** + * 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 + { + // _log.debug("public void enqueueMessage(Transaction tx = " + tx + ", AMQShortString name = " + name + ", Long messageId): called"); + + AMQShortString name = new AMQShortString(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 (_log.isDebugEnabled()) + //{ + // _log.debug("public void commitTranImpl() called with (Transaction=" + tx + ", syncCommit= "+ syncCommit + ")"); + //} + + 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(); + EntryBinding keyBinding = TupleBinding.getPrimitiveBinding(Long.class); + keyBinding.objectToEntry(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(); + EntryBinding keyBinding = TupleBinding.getPrimitiveBinding(Long.class); + keyBinding.objectToEntry(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() + { + return _queueBindingsDb; + } + + void visitMetaDataDb(DatabaseVisitor visitor) throws DatabaseException, AMQStoreException + { + visitDatabase(_messageMetaDataDb, visitor); + } + + 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 + { + // _log.debug("void commit(Transaction tx = " + tx + ", sync = " + syncCommit + "): called"); + + tx.commitNoSync(); + + BDBCommitFuture commitFuture = new BDBCommitFuture(_commitThread, tx, syncCommit); + commitFuture.commit(); + + return commitFuture; + } + + public void startCommitThread() + { + _commitThread.start(); + } + + private static final class BDBCommitFuture implements StoreFuture + { + // private static final Logger _log = Logger.getLogger(BDBCommitFuture.class); + + private final CommitThread _commitThread; + private final com.sleepycat.je.Transaction _tx; + private DatabaseException _databaseException; + private boolean _complete; + private boolean _syncCommit; + + public BDBCommitFuture(CommitThread commitThread, com.sleepycat.je.Transaction tx, boolean syncCommit) + { + // _log.debug("public Commit(CommitThread commitThread = " + commitThread + ", Transaction tx = " + tx + // + "): called"); + + _commitThread = commitThread; + _tx = tx; + _syncCommit = syncCommit; + } + + public synchronized void complete() + { + if (_log.isDebugEnabled()) + { + _log.debug("public synchronized void complete(): called (Transaction = " + _tx + ")"); + } + + _complete = true; + + notifyAll(); + } + + public synchronized void abort(DatabaseException databaseException) + { + // _log.debug("public synchronized void abort(DatabaseException databaseException = " + databaseException + // + "): called"); + + _complete = true; + _databaseException = databaseException; + + notifyAll(); + } + + public void commit() throws DatabaseException + { + //_log.debug("public void commit(): called"); + + _commitThread.addJob(this); + + if(!_syncCommit) + { + _log.debug("CommitAsync was requested, returning immediately."); + return; + } + + synchronized (BDBCommitFuture.this) + { + while (!_complete) + { + try + { + wait(250); + } + catch (InterruptedException e) + { + // _log.error("Unexpected thread interruption: " + e, e); + throw new RuntimeException(e); + } + } + + // _log.debug("Commit completed, _databaseException = " + _databaseException); + + if (_databaseException != null) + { + throw _databaseException; + } + } + } + + public synchronized boolean isComplete() + { + return _complete; + } + + public void waitForCompletion() + { + while (!isComplete()) + { + try + { + wait(250); + } + catch (InterruptedException e) + { + //TODO Should we ignore, or throw a 'StoreException'? + throw new RuntimeException(e); + } + } + } + } + + /** + * Implements a thread which batches and commits a queue of {@link BDBCommitFuture} operations. The commit operations + * themselves are responsible for adding themselves to the queue and waiting for the commit to happen before + * 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> + */ + private class CommitThread extends Thread + { + // private final Logger _log = Logger.getLogger(CommitThread.class); + + private final AtomicBoolean _stopped = new AtomicBoolean(false); + private final AtomicReference<Queue<BDBCommitFuture>> _jobQueue = new AtomicReference<Queue<BDBCommitFuture>>(new ConcurrentLinkedQueue<BDBCommitFuture>()); + private final CheckpointConfig _config = new CheckpointConfig(); + private final Object _lock = new Object(); + + public CommitThread(String name) + { + super(name); + _config.setForce(true); + + } + + public void run() + { + while (!_stopped.get()) + { + synchronized (_lock) + { + while (!_stopped.get() && !hasJobs()) + { + try + { + // RHM-7 Periodically wake up and check, just in case we + // missed a notification. Don't want to lock the broker hard. + _lock.wait(250); + } + catch (InterruptedException e) + { + // _log.info(getName() + " interrupted. "); + } + } + } + processJobs(); + } + } + + private void processJobs() + { + // _log.debug("private void processJobs(): called"); + + // we replace the old queue atomically with a new one and this avoids any need to + // copy elements out of the queue + Queue<BDBCommitFuture> jobs = _jobQueue.getAndSet(new ConcurrentLinkedQueue<BDBCommitFuture>()); + + try + { + // _environment.checkpoint(_config); + _environment.sync(); + + for (BDBCommitFuture commit : jobs) + { + commit.complete(); + } + } + catch (DatabaseException e) + { + for (BDBCommitFuture commit : jobs) + { + commit.abort(e); + } + } + + } + + private boolean hasJobs() + { + return !_jobQueue.get().isEmpty(); + } + + public void addJob(BDBCommitFuture commit) + { + synchronized (_lock) + { + _jobQueue.get().add(commit); + _lock.notifyAll(); + } + } + + public void close() + { + synchronized (_lock) + { + _stopped.set(true); + _lock.notifyAll(); + } + } + } + + + private class StoredBDBMessage implements StoredMessage + { + + private final long _messageId; + private volatile SoftReference<StorableMessageMetaData> _metaDataRef; + private com.sleepycat.je.Transaction _txn; + + StoredBDBMessage(long messageId, StorableMessageMetaData metaData) + { + this(messageId, metaData, true); + } + + + StoredBDBMessage(long messageId, + StorableMessageMetaData metaData, boolean persist) + { + try + { + _messageId = messageId; + + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + if(persist) + { + _txn = _environment.beginTransaction(null, null); + storeMetaData(_txn, messageId, metaData); + } + } + catch (DatabaseException e) + { + throw new RuntimeException(e); + } + catch (AMQStoreException 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) + { + try + { + BDBMessageStore.this.addContent(_txn, _messageId, offsetInMessage, src); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + + public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) + { + try + { + return BDBMessageStore.this.getContent(_messageId, offsetInMessage, dst); + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + } + + public StoreFuture flushToStore() + { + try + { + if(_txn != null) + { + //if(_log.isDebugEnabled()) + //{ + // _log.debug("Flushing message " + _messageId + " to store"); + //} + BDBMessageStore.this.commitTranImpl(_txn, true); + } + } + catch (AMQStoreException e) + { + throw new RuntimeException(e); + } + finally + { + _txn = null; + } + return IMMEDIATE_FUTURE; + } + + public void remove() + { + flushToStore(); + try + { + BDBMessageStore.this.removeMessage(_messageId); + } + 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, 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java new file mode 100644 index 0000000000..211c025dcd --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgrade.java @@ -0,0 +1,1125 @@ +/* + * + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.store.berkeleydb.BDBMessageStore; +import org.apache.qpid.server.store.berkeleydb.BindingKey; +import org.apache.qpid.server.store.berkeleydb.ContentTB; +import org.apache.qpid.server.store.berkeleydb.DatabaseVisitor; +import org.apache.qpid.server.store.berkeleydb.ExchangeTB; +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.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.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.util.FileUtils; +import org.apache.commons.cli.PosixParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; + +import java.io.File; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.FileNotFoundException; +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 com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; +import com.sleepycat.je.Database; +import com.sleepycat.je.Environment; +import com.sleepycat.je.EnvironmentConfig; +import com.sleepycat.bind.tuple.TupleBinding; + +/** + * 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 */ + File _fromDir; + /** The Directory that will be made to contain the upgraded store */ + File _toDir; + /** The Directory that will be made to backup the original store if required */ + File _backupDir; + + /** The Old Store */ + BDBMessageStore _oldMessageStore; + /** The New Store */ + 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(); + 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"); + + final List<AMQShortString> topicExchanges = new ArrayList<AMQShortString>(); + final TupleBinding exchangeTB = new ExchangeTB(); + + DatabaseVisitor exchangeListVisitor = new DatabaseVisitor() + { + 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()); + } + } + }; + _oldMessageStore.visitExchanges(exchangeListVisitor); + + + //Migrate _queueBindingsDb; + _logger.info("Queue Bindings"); + moveContents(_oldMessageStore.getBindingsDb(), _newMessageStore.getBindingsDb(), "Queue Binding"); + + //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 + final List<AMQShortString> durableSubQueues = new ArrayList<AMQShortString>(); + final TupleBinding<BindingKey> bindingTB = _oldMessageStore.getBindingTupleBindingFactory().getInstance(); + + DatabaseVisitor durSubQueueListVisitor = new DatabaseVisitor() + { + public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException + { + BindingKey bindingRec = (BindingKey) bindingTB.entryToObject(key); + AMQShortString queueName = bindingRec.getQueueName(); + AMQShortString exchangeName = bindingRec.getExchangeName(); + + if (topicExchanges.contains(exchangeName) && queueName.asString().contains(":")) + { + durableSubQueues.add(queueName); + } + } + }; + _oldMessageStore.visitBindings(durSubQueueListVisitor); + + + //Migrate _queueDb; + _logger.info("Queues"); + + // hold the list of existing queue names + final List<AMQShortString> existingQueues = new ArrayList<AMQShortString>(); + + final TupleBinding<QueueRecord> queueTupleBinding = _oldMessageStore.getQueueTupleBindingFactory().getInstance(); + + DatabaseVisitor queueVisitor = new DatabaseVisitor() + { + public void visit(DatabaseEntry key, DatabaseEntry value) throws AMQStoreException + { + QueueRecord queueRec = (QueueRecord) 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); + + _count++; + existingQueues.add(queueName); + } + }; + _oldMessageStore.visitQueues(queueVisitor); + + logCount(queueVisitor.getVisitedCount(), "Queue"); + + + // Look for persistent messages stored for non-durable queues + _logger.info("Checking for messages previously sent to non-durable queues"); + + // track all message delivery to existing queues + final HashSet<Long> queueMessages = new HashSet<Long>(); + + // hold all non existing queues and their messages IDs + final HashMap<String, HashSet<Long>> phantomMessageQueues = new HashMap<String, HashSet<Long>>(); + + // delivery DB visitor to check message delivery and identify non existing queues + final QueueEntryTB queueEntryTB = new QueueEntryTB(); + DatabaseVisitor messageDeliveryCheckVisitor = new DatabaseVisitor() + { + 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); + _count++; + } + else + { + queueMessages.add(messageId); + } + } + }; + _oldMessageStore.visitDelivery(messageDeliveryCheckVisitor); + + if (phantomMessageQueues.isEmpty()) + { + _logger.info("No such messages were found"); + } + else + { + _logger.info("Found " + messageDeliveryCheckVisitor.getVisitedCount()+ " such messages in total"); + + for (Entry<String, HashSet<Long>> phantomQueue : phantomMessageQueues.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(); + final TupleBinding<Object> oldMetaDataTupleBinding = _oldMessageStore.getMetaDataTupleBindingFactory().getInstance(); + final TupleBinding<Object> newMetaDataTupleBinding = _newMessageStore.getMetaDataTupleBindingFactory().getInstance(); + + DatabaseVisitor metaDataVisitor = new DatabaseVisitor() + { + public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException + { + _count++; + 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); + } + }; + _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 DatabaseVisitor() + { + long _prevMsgId = -1; //Initialise to invalid value + int _bytesSeenSoFar = 0; + + public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException + { + _count++; + + //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; + } + }; + _oldMessageStore.visitContentDb(contentVisitor); + + logCount(contentVisitor.getVisitedCount(), "Message Content"); + + + //Migrate _deliveryDb; + _logger.info("Delivery Records"); + final Database deliveryDb =_newMessageStore.getDeliveryDb(); + DatabaseVisitor deliveryDbVisitor = new DatabaseVisitor() + { + + public void visit(DatabaseEntry key, DatabaseEntry value) throws DatabaseException + { + _count++; + + // 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); + } + } + }; + _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 + { + _count++; + 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); + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BindingKey.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BindingKey.java new file mode 100644 index 0000000000..396f0ed817 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/BindingKey.java @@ -0,0 +1,62 @@ +/* + * + * 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 org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +public class BindingKey extends Object +{ + private final AMQShortString _exchangeName; + private final AMQShortString _queueName; + private final AMQShortString _routingKey; + private final FieldTable _arguments; + + public BindingKey(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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java new file mode 100644 index 0000000000..5ea3e9c2e8 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ContentTB.java @@ -0,0 +1,52 @@ +/* + * + * 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 java.nio.ByteBuffer; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java new file mode 100644 index 0000000000..9bd879025f --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/DatabaseVisitor.java @@ -0,0 +1,44 @@ +/* + * + * 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 org.apache.qpid.AMQStoreException; + +import com.sleepycat.je.DatabaseEntry; +import com.sleepycat.je.DatabaseException; + +/** Visitor Interface so that each DatabaseEntry for a database can easily be processed. */ +public abstract class DatabaseVisitor +{ + protected int _count; + + abstract public void visit(DatabaseEntry entry, DatabaseEntry value) throws AMQStoreException, DatabaseException; + + public int getVisitedCount() + { + return _count; + } + + public void resetVisitCount() + { + _count = 0; + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java new file mode 100644 index 0000000000..f9c7828bef --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/ExchangeTB.java @@ -0,0 +1,58 @@ +/* + * + * 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/FieldTableEncoding.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/FieldTableEncoding.java new file mode 100644 index 0000000000..c09498cce3 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/FieldTableEncoding.java @@ -0,0 +1,74 @@ +/* + * + * 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 org.apache.qpid.framing.FieldTable; + +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import com.sleepycat.je.DatabaseException; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +public class FieldTableEncoding +{ + public static FieldTable readFieldTable(TupleInput tupleInput) throws DatabaseException + { + long length = tupleInput.readLong(); + if (length <= 0) + { + return null; + } + else + { + + byte[] data = new byte[(int)length]; + tupleInput.readFast(data); + + try + { + return new FieldTable(new DataInputStream(new ByteArrayInputStream(data)),length); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + } + + } + + public static void writeFieldTable(FieldTable fieldTable, TupleOutput tupleOutput) + { + + if (fieldTable == null) + { + tupleOutput.writeLong(0); + } + else + { + tupleOutput.writeLong(fieldTable.getEncodedSize()); + tupleOutput.writeFast(fieldTable.getDataAsBytes()); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java new file mode 100644 index 0000000000..005e8d4604 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/MessageContentKey.java @@ -0,0 +1,42 @@ +/* + * + * 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; + +public class MessageContentKey +{ + private long _messageId; + + public MessageContentKey(long messageId) + { + _messageId = messageId; + } + + + public long getMessageId() + { + return _messageId; + } + + public void setMessageId(long messageId) + { + this._messageId = messageId; + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java new file mode 100644 index 0000000000..c274fdec8c --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/QueueEntryKey.java @@ -0,0 +1,49 @@ +/* + * + * 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 org.apache.qpid.framing.AMQShortString; + +public 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; + } + +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java new file mode 100644 index 0000000000..30357c97d4 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_4.java @@ -0,0 +1,44 @@ +/* + * + * 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.keys; + +import org.apache.qpid.server.store.berkeleydb.MessageContentKey; + +public class MessageContentKey_4 extends MessageContentKey +{ + private int _chunkNum; + + public MessageContentKey_4(long messageId, int chunkNo) + { + super(messageId); + _chunkNum = chunkNo; + } + + public int getChunk() + { + return _chunkNum; + } + + public void setChunk(int chunk) + { + this._chunkNum = chunk; + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java new file mode 100644 index 0000000000..a1a7fe80b5 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/keys/MessageContentKey_5.java @@ -0,0 +1,44 @@ +/* + * + * 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.keys; + +import org.apache.qpid.server.store.berkeleydb.MessageContentKey; + +public class MessageContentKey_5 extends MessageContentKey +{ + private int _offset; + + public MessageContentKey_5(long messageId, int chunkNo) + { + super(messageId); + _offset = chunkNo; + } + + public int getOffset() + { + return _offset; + } + + public void setOffset(int chunk) + { + this._offset = chunk; + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java new file mode 100644 index 0000000000..f20367e33b --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/ExchangeRecord.java @@ -0,0 +1,53 @@ +/* + * + * 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java new file mode 100644 index 0000000000..fbe10433ca --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/records/QueueRecord.java @@ -0,0 +1,66 @@ +/* + * + * 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/testclient/BackupTestClient.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/testclient/BackupTestClient.java new file mode 100644 index 0000000000..f6344b3d7d --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/testclient/BackupTestClient.java @@ -0,0 +1,120 @@ +/* + * + * 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.testclient; + +import org.apache.log4j.Logger; + +import org.apache.qpid.ping.PingDurableClient; +import org.apache.qpid.server.store.berkeleydb.BDBBackup; +import org.apache.qpid.util.CommandLineParser; + +import java.util.Properties; + +/** + * BackupTestClient extends {@link PingDurableClient} with an action that takes a BDB backup when a configurable + * message count is reached. This enables a test user to restore this beackup, knowing how many committed but undelivered + * messages were in the backup, in order to check that all are re-delivered when the backup is retored. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Perform BDB Backup on configurable message count. + * </table> + */ +public class BackupTestClient extends PingDurableClient +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(BackupTestClient.class); + + /** Holds the from directory to take backups from. */ + private String fromDir; + + /** Holds the to directory to store backups in. */ + private String toDir; + + /** + * Default constructor, passes all property overrides to the parent. + * + * @param overrides Any property overrides to apply to the defaults. + * + * @throws Exception Any underlying exception is allowed to fall through. + */ + BackupTestClient(Properties overrides) throws Exception + { + super(overrides); + } + + /** + * Starts the ping/wait/receive process. From and to directory locations for the BDB backups must be specified + * on the command line: + * + * <p/><table><caption>Command Line</caption> + * <tr><th> Option <th> Comment + * <tr><td> -fromdir <td> The path to the directory to back the bdb log file from. + * <tr><td> -todir <td> The path to the directory to save the backed up bdb log files to. + * </table> + * + * @param args The command line arguments. + */ + public static void main(String[] args) + { + try + { + // Use the same command line format as BDBBackup utility, (compulsory from and to directories). + Properties options = + CommandLineParser.processCommandLine(args, new CommandLineParser(BDBBackup.COMMAND_LINE_SPEC), + System.getProperties()); + BackupTestClient pingProducer = new BackupTestClient(options); + + // Keep the from and to directories for backups. + pingProducer.fromDir = options.getProperty("fromdir"); + pingProducer.toDir = options.getProperty("todir"); + + // Create a shutdown hook to terminate the ping-pong producer. + Runtime.getRuntime().addShutdownHook(pingProducer.getShutdownHook()); + + // Ensure that the ping pong producer is registered to listen for exceptions on the connection too. + // pingProducer.getConnection().setExceptionListener(pingProducer); + + // Run the test procedure. + int sent = pingProducer.send(); + pingProducer.waitForUser("Press return to begin receiving the pings."); + pingProducer.receive(sent); + + System.exit(0); + } + catch (Exception e) + { + System.err.println(e.getMessage()); + log.error("Top level handler caught execption.", e); + System.exit(1); + } + } + + /** + * Supplies a triggered action extension, based on message count. This action takes a BDB log file backup. + */ + public void takeAction() + { + BDBBackup backupUtil = new BDBBackup(); + backupUtil.takeBackupNoLock(fromDir, toDir); + System.out.println("Took backup of BDB log files from directory: " + fromDir); + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java new file mode 100644 index 0000000000..301ee417c5 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple.java @@ -0,0 +1,25 @@ +/* + * + * 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; + +public interface BindingTuple +{ +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java new file mode 100644 index 0000000000..8e17f074d7 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTupleBindingFactory.java @@ -0,0 +1,45 @@ +/* + * + * 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 org.apache.qpid.server.store.berkeleydb.BindingKey; + +import com.sleepycat.bind.tuple.TupleBinding; + +public class BindingTupleBindingFactory extends TupleBindingFactory<BindingKey> +{ + public BindingTupleBindingFactory(int version) + { + super(version); + } + + public TupleBinding<BindingKey> getInstance() + { + switch (_version) + { + default: + case 5: + //no change from v4 + case 4: + return new BindingTuple_4(); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java new file mode 100644 index 0000000000..52b131a7f2 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/BindingTuple_4.java @@ -0,0 +1,76 @@ +/* + * + * 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 org.apache.log4j.Logger; +import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.BindingKey; +import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import com.sleepycat.je.DatabaseException; + +public class BindingTuple_4 extends TupleBinding<BindingKey> implements BindingTuple +{ + protected static final Logger _log = Logger.getLogger(BindingTuple.class); + + public BindingTuple_4() + { + super(); + } + + public BindingKey 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 BindingKey(exchangeName, queueName, routingKey, arguments); + } + + public void objectToEntry(BindingKey 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java new file mode 100644 index 0000000000..f5ba6bbce3 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_4.java @@ -0,0 +1,47 @@ +/* + * + * 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 org.apache.qpid.server.store.berkeleydb.MessageContentKey; +import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_4; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java new file mode 100644 index 0000000000..e6a2fd23a8 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTB_5.java @@ -0,0 +1,46 @@ +/* + * + * 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 org.apache.qpid.server.store.berkeleydb.MessageContentKey; +import org.apache.qpid.server.store.berkeleydb.keys.MessageContentKey_5; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java new file mode 100644 index 0000000000..76ee4f66e4 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageContentKeyTupleBindingFactory.java @@ -0,0 +1,45 @@ +/* + * + * 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 org.apache.qpid.server.store.berkeleydb.MessageContentKey; + +import com.sleepycat.bind.tuple.TupleBinding; + +public class MessageContentKeyTupleBindingFactory extends TupleBindingFactory<MessageContentKey> +{ + public MessageContentKeyTupleBindingFactory(int version) + { + super(version); + } + + public TupleBinding<MessageContentKey> getInstance() + { + switch (_version) + { + default: + case 5: + return new MessageContentKeyTB_5(); + case 4: + return new MessageContentKeyTB_4(); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java new file mode 100644 index 0000000000..e26b544e38 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_4.java @@ -0,0 +1,162 @@ +/* + * + * 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.*; +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.*; + +/** + * 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.java new file mode 100644 index 0000000000..6dc041cb23 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTB_5.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.tuples; + +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; +import org.apache.log4j.Logger; +import org.apache.qpid.server.store.MessageMetaDataType; +import org.apache.qpid.server.store.StorableMessageMetaData; + +/** + * Handles the mapping to and from message meta data + */ +public class MessageMetaDataTB_5 extends MessageMetaDataTB_4 +{ + private static final Logger _log = Logger.getLogger(MessageMetaDataTB_5.class); + + @Override + public Object entryToObject(TupleInput tupleInput) + { + try + { + final int bodySize = tupleInput.readInt(); + byte[] dataAsBytes = new byte[bodySize]; + tupleInput.readFast(dataAsBytes); + + java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes); + buf.position(1); + buf = buf.slice(); + MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]]; + StorableMessageMetaData metaData = type.getFactory().createMetaData(buf); + + return metaData; + } + catch (Exception e) + { + _log.error("Error converting entry to object: " + e, e); + // annoyingly just have to return null since we cannot throw + return null; + } + } + + @Override + public void objectToEntry(Object object, 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); + buf.position(1); + buf = buf.slice(); + + metaData.writeToBuffer(0, buf); + tupleOutput.writeInt(bodySize); + tupleOutput.writeFast(underlying); + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java new file mode 100644 index 0000000000..40153c13ea --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/MessageMetaDataTupleBindingFactory.java @@ -0,0 +1,43 @@ +/* + * + * 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 (_version) + { + default: + case 5: + return new MessageMetaDataTB_5(); + case 4: + return new MessageMetaDataTB_4(); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java new file mode 100644 index 0000000000..975e558874 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueEntryTB.java @@ -0,0 +1,46 @@ +/* + * + * 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 org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.QueueEntryKey; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +public class QueueEntryTB 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()); + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java new file mode 100644 index 0000000000..affa9a271d --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple.java @@ -0,0 +1,25 @@ +/* + * + * 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; + +public interface QueueTuple +{ +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java new file mode 100644 index 0000000000..512e319f96 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTupleBindingFactory.java @@ -0,0 +1,46 @@ +/* + * + * 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 org.apache.qpid.server.store.berkeleydb.records.QueueRecord; + +import com.sleepycat.bind.tuple.TupleBinding; + +public class QueueTupleBindingFactory extends TupleBindingFactory<QueueRecord> +{ + + public QueueTupleBindingFactory(int version) + { + super(version); + } + + public TupleBinding<QueueRecord> getInstance() + { + switch (_version) + { + default: + case 5: + return new QueueTuple_5(); + case 4: + return new QueueTuple_4(); + } + } +} diff --git a/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java new file mode 100644 index 0000000000..347eecf08e --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_4.java @@ -0,0 +1,72 @@ +/* + * + * 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.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; +import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +public class QueueTuple_4 extends TupleBinding<QueueRecord> implements QueueTuple +{ + protected static final Logger _logger = Logger.getLogger(QueueTuple_4.class); + + protected FieldTable _arguments; + + 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java new file mode 100644 index 0000000000..0f293b79b7 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/QueueTuple_5.java @@ -0,0 +1,75 @@ +/* + * + * 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.server.store.berkeleydb.AMQShortStringEncoding; +import org.apache.qpid.server.store.berkeleydb.FieldTableEncoding; +import org.apache.qpid.server.store.berkeleydb.records.QueueRecord; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +public class QueueTuple_5 extends QueueTuple_4 +{ + protected static final Logger _logger = Logger.getLogger(QueueTuple_5.class); + + protected FieldTable _arguments; + + 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/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java new file mode 100644 index 0000000000..2adac1f9a3 --- /dev/null +++ b/qpid/java/bdbstore/src/main/java/org/apache/qpid/server/store/berkeleydb/tuples/TupleBindingFactory.java @@ -0,0 +1,35 @@ +/* + * + * 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 abstract class TupleBindingFactory<E> +{ + protected int _version; + + public TupleBindingFactory(int version) + { + _version = version; + } + + public abstract TupleBinding<E> getInstance(); +} diff --git a/qpid/java/bdbstore/src/resources/backup-log4j.xml b/qpid/java/bdbstore/src/resources/backup-log4j.xml new file mode 100644 index 0000000000..6b0619f0b6 --- /dev/null +++ b/qpid/java/bdbstore/src/resources/backup-log4j.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + - + - 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"> + +<!-- =============================================================================== --> +<!-- This is a Log4j configuration specially created for the BDB Backup utility, --> +<!-- it outputs logging to the console for specifically designated console loggers --> +<!-- at info level or above only. This avoids spamming the user with any internals --> +<!-- of the Qpid code. --> +<!-- Use a different logging set up to capture debugging output to diagnose errors. --> +<!-- =============================================================================== --> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false"> + + <!-- ====================================================== --> + <!-- Append messages to the console at info level or above. --> + <!-- ====================================================== --> + + <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender"> + <param name="Target" value="System.out"/> + <param name="Threshold" value="info"/> + + <layout class="org.apache.log4j.PatternLayout"> + <!-- The default pattern: Date Priority [Category] Message\n --> + <param name="ConversionPattern" value="%m%n"/> + </layout> + + </appender> + + <!-- ================ --> + <!-- Limit categories --> + <!-- ================ --> + + <category name="org.apache.qpid.server.store.berkeleydb.BDBBackup"> + <priority value="info"/> + </category> + + <!-- ======================= --> + <!-- Setup the Root category --> + <!-- ======================= --> + + <root> + <appender-ref ref="CONSOLE"/> + </root> + +</log4j:configuration> diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncodingTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncodingTest.java new file mode 100644 index 0000000000..d076babc61 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/AMQShortStringEncodingTest.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.qpid.framing.AMQShortString; + +import com.sleepycat.bind.tuple.TupleInput; +import com.sleepycat.bind.tuple.TupleOutput; + +import junit.framework.TestCase; + +/** + * Tests for {@code AMQShortStringEncoding} including corner cases when string + * is null or over 127 characters in length + */ +public class AMQShortStringEncodingTest extends TestCase +{ + + public void testWriteReadNullValues() + { + // write into tuple output + TupleOutput tupleOutput = new TupleOutput(); + AMQShortStringEncoding.writeShortString(null, tupleOutput); + byte[] data = tupleOutput.getBufferBytes(); + + // read from tuple input + TupleInput tupleInput = new TupleInput(data); + AMQShortString result = AMQShortStringEncoding.readShortString(tupleInput); + assertNull("Expected null but got " + result, result); + } + + public void testWriteReadShortStringWithLengthOver127() + { + AMQShortString value = createString('a', 128); + + // write into tuple output + TupleOutput tupleOutput = new TupleOutput(); + AMQShortStringEncoding.writeShortString(value, tupleOutput); + byte[] data = tupleOutput.getBufferBytes(); + + // read from tuple input + TupleInput tupleInput = new TupleInput(data); + AMQShortString result = AMQShortStringEncoding.readShortString(tupleInput); + assertEquals("Expected " + value + " but got " + result, value, result); + } + + public void testWriteReadShortStringWithLengthLess127() + { + AMQShortString value = new AMQShortString("test"); + + // write into tuple output + TupleOutput tupleOutput = new TupleOutput(); + AMQShortStringEncoding.writeShortString(value, tupleOutput); + byte[] data = tupleOutput.getBufferBytes(); + + // read from tuple input + TupleInput tupleInput = new TupleInput(data); + AMQShortString result = AMQShortStringEncoding.readShortString(tupleInput); + assertEquals("Expected " + value + " but got " + result, value, result); + } + + private AMQShortString createString(char ch, int length) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) + { + sb.append(ch); + } + return new AMQShortString(sb.toString()); + } + +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java new file mode 100644 index 0000000000..ef31b78cfe --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBMessageStoreTest.java @@ -0,0 +1,470 @@ +/* + * + * 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 java.io.File; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import org.apache.qpid.AMQStoreException; +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.server.message.MessageMetaData; +import org.apache.qpid.server.message.MessageMetaData_0_10; +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.TransactionLog; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageAcceptMode; +import org.apache.qpid.transport.MessageAcquireMode; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.transport.MessageDeliveryPriority; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageTransfer; + +/** + * 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 +{ + /** + * 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 + { + MessageStore store = getVirtualHost().getMessageStore(); + + BDBMessageStore bdbStore = assertBDBStore(store); + + // Create content ByteBuffers. + // 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(); + + /* + * Create and insert a 0-8 message (metadata and multi-chunk content) + */ + MessagePublishInfo pubInfoBody_0_8 = createPublishInfoBody_0_8(); + BasicContentHeaderProperties props_0_8 = createContentHeaderProperties_0_8(); + + ContentHeaderBody chb_0_8 = createContentHeaderBody_0_8(props_0_8, bodySize); + + MessageMetaData messageMetaData_0_8 = new MessageMetaData(pubInfoBody_0_8, chb_0_8, 0); + StoredMessage<MessageMetaData> storedMessage_0_8 = bdbStore.addMessage(messageMetaData_0_8); + + long origArrivalTime_0_8 = messageMetaData_0_8.getArrivalTime(); + long messageid_0_8 = storedMessage_0_8.getMessageNumber(); + + storedMessage_0_8.addContent(0, firstContentBytes_0_8); + storedMessage_0_8.addContent(firstContentBytes_0_8.limit(), secondContentBytes_0_8); + storedMessage_0_8.flushToStore(); + + /* + * 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(msgProps_0_10, delProps_0_10); + + 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); + StoredMessage<MessageMetaData_0_10> storedMessage_0_10 = bdbStore.addMessage(messageMetaData_0_10); + + long origArrivalTime_0_10 = messageMetaData_0_10.getArrivalTime(); + long messageid_0_10 = storedMessage_0_10.getMessageNumber(); + + storedMessage_0_10.addContent(0, completeContentBody_0_10); + storedMessage_0_10.flushToStore(); + + /* + * reload the store only (read-only) + */ + bdbStore = reloadStoreReadOnly(bdbStore); + + /* + * Read back and validate the 0-8 message metadata and content + */ + StorableMessageMetaData storeableMMD_0_8 = bdbStore.getMessageMetaData(messageid_0_8); + + assertEquals("Unexpected message type",MessageMetaDataType.META_DATA_0_8, storeableMMD_0_8.getType()); + assertTrue("Unexpected instance type", storeableMMD_0_8 instanceof MessageMetaData); + MessageMetaData returnedMMD_0_8 = (MessageMetaData) storeableMMD_0_8; + + assertEquals("Message arrival time has changed", origArrivalTime_0_8, returnedMMD_0_8.getArrivalTime()); + + MessagePublishInfo returnedPubBody_0_8 = returnedMMD_0_8.getMessagePublishInfo(); + assertEquals("Message exchange has changed", pubInfoBody_0_8.getExchange(), returnedPubBody_0_8.getExchange()); + assertEquals("Immediate flag has changed", pubInfoBody_0_8.isImmediate(), returnedPubBody_0_8.isImmediate()); + assertEquals("Mandatory flag has changed", pubInfoBody_0_8.isMandatory(), returnedPubBody_0_8.isMandatory()); + assertEquals("Routing key has changed", pubInfoBody_0_8.getRoutingKey(), returnedPubBody_0_8.getRoutingKey()); + + ContentHeaderBody returnedHeaderBody_0_8 = returnedMMD_0_8.getContentHeaderBody(); + assertEquals("ContentHeader ClassID has changed", chb_0_8.classId, returnedHeaderBody_0_8.classId); + assertEquals("ContentHeader weight has changed", chb_0_8.weight, returnedHeaderBody_0_8.weight); + assertEquals("ContentHeader bodySize has changed", chb_0_8.bodySize, returnedHeaderBody_0_8.bodySize); + + BasicContentHeaderProperties returnedProperties_0_8 = (BasicContentHeaderProperties) returnedHeaderBody_0_8.getProperties(); + assertEquals("Property ContentType has changed", props_0_8.getContentTypeAsString(), returnedProperties_0_8.getContentTypeAsString()); + assertEquals("Property MessageID has changed", props_0_8.getMessageIdAsString(), returnedProperties_0_8.getMessageIdAsString()); + + ByteBuffer recoveredContent_0_8 = ByteBuffer.allocate((int) chb_0_8.bodySize) ; + long recoveredCount_0_8 = bdbStore.getContent(messageid_0_8, 0, recoveredContent_0_8); + assertEquals("Incorrect amount of payload data recovered", chb_0_8.bodySize, recoveredCount_0_8); + String returnedPayloadString_0_8 = new String(recoveredContent_0_8.array()); + assertEquals("Message Payload has changed", bodyText, returnedPayloadString_0_8); + + /* + * Read back and validate the 0-10 message metadata and content + */ + StorableMessageMetaData storeableMMD_0_10 = bdbStore.getMessageMetaData(messageid_0_10); + + assertEquals("Unexpected message type",MessageMetaDataType.META_DATA_0_10, storeableMMD_0_10.getType()); + assertTrue("Unexpected instance type", storeableMMD_0_10 instanceof MessageMetaData_0_10); + MessageMetaData_0_10 returnedMMD_0_10 = (MessageMetaData_0_10) storeableMMD_0_10; + + assertEquals("Message arrival time has changed", origArrivalTime_0_10, returnedMMD_0_10.getArrivalTime()); + + DeliveryProperties returnedDelProps_0_10 = returnedMMD_0_10.getHeader().get(DeliveryProperties.class); + assertNotNull("DeliveryProperties were not returned", returnedDelProps_0_10); + assertEquals("Immediate flag has changed", delProps_0_10.getImmediate(), returnedDelProps_0_10.getImmediate()); + assertEquals("Routing key has changed", delProps_0_10.getRoutingKey(), returnedDelProps_0_10.getRoutingKey()); + assertEquals("Message exchange has changed", delProps_0_10.getExchange(), returnedDelProps_0_10.getExchange()); + assertEquals("Message expiration has changed", delProps_0_10.getExpiration(), returnedDelProps_0_10.getExpiration()); + assertEquals("Message delivery priority has changed", delProps_0_10.getPriority(), returnedDelProps_0_10.getPriority()); + + MessageProperties returnedMsgProps = returnedMMD_0_10.getHeader().get(MessageProperties.class); + assertNotNull("MessageProperties were not returned", returnedMsgProps); + assertTrue("Message correlationID has changed", Arrays.equals(msgProps_0_10.getCorrelationId(), returnedMsgProps.getCorrelationId())); + assertEquals("Message content length has changed", msgProps_0_10.getContentLength(), returnedMsgProps.getContentLength()); + assertEquals("Message content type has changed", msgProps_0_10.getContentType(), returnedMsgProps.getContentType()); + + ByteBuffer recoveredContent = ByteBuffer.allocate((int) msgProps_0_10.getContentLength()) ; + long recoveredCount = bdbStore.getContent(messageid_0_10, 0, recoveredContent); + assertEquals("Incorrect amount of payload data recovered", msgProps_0_10.getContentLength(), recoveredCount); + + String returnedPayloadString_0_10 = new String(recoveredContent.array()); + assertEquals("Message Payload has changed", bodyText, returnedPayloadString_0_10); + } + + 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; + } + + private MessageProperties createMessageProperties_0_10(int bodySize) + { + MessageProperties msgProps_0_10 = new MessageProperties(); + 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 + * to avoid the recovery handler deleting the message for not being on a queue. + */ + private BDBMessageStore reloadStoreReadOnly(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(); + + return newStore; + } + + private MessagePublishInfo createPublishInfoBody_0_8() + { + return new MessagePublishInfo() + { + public AMQShortString getExchange() + { + return new AMQShortString("exchange12345"); + } + + public void setExchange(AMQShortString exchange) + { + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return true; + } + + public AMQShortString getRoutingKey() + { + return new AMQShortString("routingKey12345"); + } + }; + + } + + private ContentHeaderBody createContentHeaderBody_0_8(BasicContentHeaderProperties props, int length) + { + MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); + int classForBasic = methodRegistry.createBasicQosOkBody().getClazz(); + return new ContentHeaderBody(classForBasic, 1, props, length); + } + + private BasicContentHeaderProperties createContentHeaderProperties_0_8() + { + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + props.setDeliveryMode(Integer.valueOf(BasicContentHeaderProperties.PERSISTENT).byteValue()); + props.setContentType("text/html"); + props.getHeaders().setString("Test", "MST"); + return props; + } + + /** + * 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 + * interrogating the store with its own implementation methods and verifying + * expected exceptions are thrown to indicate the message is not present. + */ + public void testMessageCreationAndRemoval() throws Exception + { + MessageStore store = getVirtualHost().getMessageStore(); + BDBMessageStore bdbStore = assertBDBStore(store); + + StoredMessage<MessageMetaData> storedMessage_0_8 = createAndStoreMultiChunkMessage_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(); + + //verify the removal using the BDB store implementation methods directly + try + { + // the next line should throw since the message id should not be found + bdbStore.getMessageMetaData(messageid_0_8); + fail("No exception thrown when message id not found getting metadata"); + } + catch (AMQStoreException e) + { + // pass since exception expected + } + + //expecting no content, allocate a 1 byte + ByteBuffer dst = ByteBuffer.allocate(1); + + assertEquals("Retrieved content when none was expected", + 0, bdbStore.getContent(messageid_0_8, 0, dst)); + } + + private BDBMessageStore assertBDBStore(Object store) + { + if(!(store instanceof BDBMessageStore)) + { + fail("Test requires an instance of BDBMessageStore to proceed"); + } + + return (BDBMessageStore) store; + } + + private StoredMessage<MessageMetaData> createAndStoreMultiChunkMessage_0_8(MessageStore store) + { + byte[] body10Bytes = "0123456789".getBytes(); + byte[] body5Bytes = "01234".getBytes(); + + ByteBuffer chunk1 = ByteBuffer.wrap(body10Bytes); + ByteBuffer chunk2 = ByteBuffer.wrap(body5Bytes); + + int bodySize = body10Bytes.length + body5Bytes.length; + + //create and store the message using the MessageStore interface + MessagePublishInfo pubInfoBody_0_8 = createPublishInfoBody_0_8(); + BasicContentHeaderProperties props_0_8 = createContentHeaderProperties_0_8(); + + ContentHeaderBody chb_0_8 = createContentHeaderBody_0_8(props_0_8, bodySize); + + MessageMetaData messageMetaData_0_8 = new MessageMetaData(pubInfoBody_0_8, chb_0_8, 0); + 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; + } + + /** + * Tests transaction commit 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 testTranCommit() throws Exception + { + TransactionLog log = getVirtualHost().getTransactionLog(); + + BDBMessageStore bdbStore = assertBDBStore(log); + + final AMQShortString mockQueueName = new AMQShortString("queueName"); + + TransactionLogResource mockQueue = new TransactionLogResource() + { + public String getResourceName() + { + return mockQueueName.asString(); + } + }; + + TransactionLog.Transaction txn = log.newTransaction(); + + txn.enqueueMessage(mockQueue, 1L); + txn.enqueueMessage(mockQueue, 5L); + txn.commitTran(); + + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); + + 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 + * implementation methods. + */ + public void testTranRollbackBeforeCommit() throws Exception + { + TransactionLog log = getVirtualHost().getTransactionLog(); + + BDBMessageStore bdbStore = assertBDBStore(log); + + final AMQShortString mockQueueName = new AMQShortString("queueName"); + + TransactionLogResource mockQueue = new TransactionLogResource() + { + public String getResourceName() + { + return mockQueueName.asString(); + } + }; + + TransactionLog.Transaction txn = log.newTransaction(); + + txn.enqueueMessage(mockQueue, 21L); + txn.abortTran(); + + txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, 22L); + txn.enqueueMessage(mockQueue, 23L); + txn.commitTran(); + + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); + + 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 + * implementation methods. + */ + public void testTranRollbackAfterCommit() throws Exception + { + TransactionLog log = getVirtualHost().getTransactionLog(); + + BDBMessageStore bdbStore = assertBDBStore(log); + + final AMQShortString mockQueueName = new AMQShortString("queueName"); + + TransactionLogResource mockQueue = new TransactionLogResource() + { + public String getResourceName() + { + return mockQueueName.asString(); + } + }; + + TransactionLog.Transaction txn = log.newTransaction(); + + txn.enqueueMessage(mockQueue, 30L); + txn.commitTran(); + + txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, 31L); + txn.abortTran(); + + txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, 32L); + txn.commitTran(); + + List<Long> enqueuedIds = bdbStore.getEnqueuedMessages(mockQueueName); + + assertEquals("Number of enqueued messages is incorrect", 2, enqueuedIds.size()); + Long val = enqueuedIds.get(0); + assertEquals("First Message is incorrect", 30L, val.longValue()); + val = enqueuedIds.get(1); + assertEquals("Second Message is incorrect", 32L, val.longValue()); + } + +} diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java new file mode 100644 index 0000000000..cc19bcf5d8 --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBStoreUpgradeTestPreparer.java @@ -0,0 +1,232 @@ +/* + * + * 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 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 junit.framework.TestCase; + +import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.url.URLSyntaxException; + +/** + * Prepares an older version brokers BDB store with the required + * contents for use in the BDBStoreUpgradeTest. + * + * 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. + */ +public class BDBStoreUpgradeTestPreparer extends TestCase +{ + public static final String TOPIC_NAME="myUpgradeTopic"; + public static final String SUB_NAME="myDurSubName"; + public static final String QUEUE_NAME="myUpgradeQueue"; + + private static AMQConnectionFactory _connFac; + private static final String CONN_URL = + "amqp://guest:guest@clientid/test?brokerlist='tcp://localhost:5672'"; + + /** + * Create a BDBStoreUpgradeTestPreparer instance + */ + public BDBStoreUpgradeTestPreparer () throws URLSyntaxException + { + _connFac = new AMQConnectionFactory(CONN_URL); + } + + /** + * Utility test method to allow running the preparation tool + * using the test framework + */ + public void testPrepareBroker() throws Exception + { + prepareBroker(); + } + + private void prepareBroker() throws Exception + { + prepareQueues(); + prepareDurableSubscription(); + } + + /** + * 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. + * - Send 1 small message to test (single-frame) content recovery. + * - Commit the session. + * - Send 5 small messages to test that uncommitted messages are not recovered. + * following the upgrade. + * - Close the session. + */ + private void prepareQueues() throws Exception + { + // Create a connection + Connection connection = _connFac.createConnection(); + connection.start(); + connection.setExceptionListener(new ExceptionListener() + { + public void onException(JMSException e) + { + e.printStackTrace(); + } + }); + // Create a session on the connection, transacted to confirm delivery + Session session = connection.createSession(true, Session.SESSION_TRANSACTED); + Queue queue = session.createQueue(QUEUE_NAME); + // Create a consumer to ensure the queue gets created + // (and enter it into the store, as queues are made durable by default) + MessageConsumer messageConsumer = session.createConsumer(queue); + messageConsumer.close(); + + // Create a Message producer + MessageProducer messageProducer = session.createProducer(queue); + + // Publish 5 persistent messages, 256k chars to ensure they are multi-frame + sendMessages(session, messageProducer, queue, DeliveryMode.PERSISTENT, 256*1024, 5); + // Publish 5 persistent messages, 1k chars to ensure they are single-frame + sendMessages(session, messageProducer, queue, DeliveryMode.PERSISTENT, 1*1024, 5); + + session.commit(); + + // Publish 5 persistent messages which will NOT be committed and so should be 'lost' + sendMessages(session, messageProducer, queue, DeliveryMode.PERSISTENT, 1*1024, 5); + + session.close(); + connection.close(); + } + + /** + * 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. + * - Send a message which does not match the selector. + * - Send a message which matches the selector but will remain uncommitted. + * - Close the session. + */ + private void prepareDurableSubscription() throws Exception + { + + // Create a connection + TopicConnection connection = _connFac.createTopicConnection(); + connection.start(); + connection.setExceptionListener(new ExceptionListener() + { + public void onException(JMSException e) + { + e.printStackTrace(); + } + }); + // Create a session on the connection, transacted to confirm delivery + Session session = connection.createSession(true, Session.SESSION_TRANSACTED); + Topic topic = session.createTopic(TOPIC_NAME); + + // Create and register a durable subscriber with selector and then close it + TopicSubscriber durSub1 = session.createDurableSubscriber(topic, SUB_NAME,"testprop='true'", false); + durSub1.close(); + + // 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); + TopicPublisher publisher = pubSession.createPublisher(topic); + + publishMessages(session, publisher, topic, DeliveryMode.PERSISTENT, 1*1024, 1, "true"); + publishMessages(session, publisher, topic, DeliveryMode.PERSISTENT, 1*1024, 1, "false"); + pubSession.commit(); + publishMessages(session, publisher, topic, DeliveryMode.PERSISTENT, 1*1024, 1, "true"); + + publisher.close(); + pubSession.close(); + + } + + 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++) + { + Message message = session.createTextMessage(generateString(length)); + message.setIntProperty("ID", i); + messageProducer.send(message, deliveryMode, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE); + } + } + + 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++) + { + Message message = session.createTextMessage(generateString(length)); + message.setIntProperty("ID", i); + message.setStringProperty("testprop", selectorProperty); + publisher.publish(message, deliveryMode, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE); + } + } + + /** + * 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 + * @return string sequence of the given length + */ + public static String generateString(int length) + { + char[] base_chars = new char[]{'0','1','2','3','4','5','6','7','8','9'}; + char[] chars = new char[length]; + for (int i = 0; i < (length); i++) + { + chars[i] = base_chars[i % 10]; + } + return new String(chars); + } + + /** + * Run the preparation tool. + * @param args Command line arguments. + */ + public static void main(String[] args) throws Exception + { + BDBStoreUpgradeTestPreparer producer = new BDBStoreUpgradeTestPreparer(); + producer.prepareBroker(); + } +}
\ No newline at end of file diff --git a/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java new file mode 100644 index 0000000000..4861e007af --- /dev/null +++ b/qpid/java/bdbstore/src/test/java/org/apache/qpid/server/store/berkeleydb/BDBUpgradeTest.java @@ -0,0 +1,540 @@ +/* + * + * 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 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 java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import javax.jms.Connection; +import javax.jms.DeliveryMode; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.Topic; +import javax.jms.TopicConnection; +import javax.jms.TopicPublisher; +import javax.jms.TopicSession; +import javax.jms.TopicSubscriber; + +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.MessageMetaData; +import org.apache.qpid.server.store.TransactionLog; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sleepycat.bind.tuple.TupleBinding; +import com.sleepycat.je.DatabaseEntry; + +/** + * 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. + */ +public class BDBUpgradeTest extends QpidBrokerTestCase +{ + protected static final Logger _logger = LoggerFactory.getLogger(BDBUpgradeTest.class); + + 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; + + @Override + public void setUp() throws Exception + { + assertNotNull("QPID_WORK must be set", QPID_WORK_ORIG); + assertNotNull("QPID_HOME must be set", QPID_HOME); + + if(! isExternalBroker()) + { + //override QPID_WORK to add the InVM port used so the store + //output from the upgrade tool can be found by the broker + setSystemProperty("QPID_WORK", QPID_WORK_ORIG + "/" + getPort()); + } + + _fromDir = QPID_HOME + "/bdbstore-to-upgrade/test-store"; + _toDir = getWorkDirBaseDir() + "/bdbstore/test-store"; + _toDirTwice = getWorkDirBaseDir() + "/bdbstore-upgraded-twice"; + + //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); + if (directory.exists() && directory.isDirectory()) + { + FileUtils.delete(directory, true); + } + + //Upgrade the test store. + upgradeBrokerStore(_fromDir, _toDir); + + //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(); + } + + private String getWorkDirBaseDir() + { + return QPID_WORK_ORIG + (isInternalBroker() ? "" : "/" + getPort()); + } + + /** + * 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. + */ + public void testSelectorDurability() throws Exception + { + JMXTestUtils jmxUtils = null; + try + { + jmxUtils = new JMXTestUtils(this, "guest", "guest"); + jmxUtils.open(); + } + catch (Exception e) + { + fail("Unable to establish JMX connection, test cannot proceed"); + } + + try + { + ManagedQueue dursubQueue = jmxUtils.getManagedQueue("clientid" + ":" + SUB_NAME); + 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); + Topic topic = pubSession.createTopic(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", + new Integer(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", + new Integer(2), dursubQueue.getMessageCount()); + + dursubQueue.clearQueue(); + pubSession.close(); + } + finally + { + jmxUtils.close(); + } + } + + /** + * Test that the backing queue for the durable subscription created was successfully + * detected and set as being exclusive during the upgrade process, and that the + * regular queue was not. + */ + public void testQueueExclusivity() throws Exception + { + JMXTestUtils jmxUtils = null; + try + { + jmxUtils = new JMXTestUtils(this, "guest", "guest"); + jmxUtils.open(); + } + catch (Exception e) + { + fail("Unable to establish JMX connection, test cannot proceed"); + } + + try + { + ManagedQueue queue = jmxUtils.getManagedQueue(QUEUE_NAME); + assertFalse("Queue should not have been marked as Exclusive during upgrade", queue.isExclusive()); + + ManagedQueue dursubQueue = jmxUtils.getManagedQueue("clientid" + ":" + SUB_NAME); + assertTrue("DurableSubscription backing queue should have been marked as Exclusive during upgrade", dursubQueue.isExclusive()); + } + finally + { + jmxUtils.close(); + } + } + + /** + * Test that the upgraded queue continues to function properly when used + * 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 + * the queue bindings were successfully transitioned during the upgrade. + */ + public void testBindingAndMessageDurabability() throws Exception + { + // Create a connection and start it + TopicConnection connection = (TopicConnection) getConnection(); + connection.start(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue(QUEUE_NAME); + MessageProducer messageProducer = session.createProducer(queue); + + // Send a new message + BDBStoreUpgradeTestPreparer.sendMessages(session, messageProducer, queue, DeliveryMode.PERSISTENT, 256*1024, 1); + + session.close(); + + // Restart the broker + restartBroker(); + + // Drain the queue of all messages + connection = (TopicConnection) getConnection(); + connection.start(); + consumeQueueMessages(connection, true); + } + + /** + * 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. + */ + public void testConsumptionOfUpgradedMessages() throws Exception + { + // Create a connection and start it + Connection connection = getConnection(); + connection.start(); + + consumeDurableSubscriptionMessages(connection); + consumeQueueMessages(connection, false); + } + + /** + * Tests store migration containing messages for non-existing queue. + * + * @throws Exception + */ + public void testMigrationOfMessagesForNonExistingQueues() 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); + 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() + { + + 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 + 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(); + } + }; + TransactionLog log = (TransactionLog) store; + TransactionLog.Transaction txn = log.newTransaction(); + txn.enqueueMessage(mockQueue, messageId); + txn.commitTran(); + } + finally + { + // close store + store.close(); + } + } + + private void consumeDurableSubscriptionMessages(Connection connection) throws Exception + { + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Topic topic = session.createTopic(TOPIC_NAME); + + TopicSubscriber durSub = session.createDurableSubscriber(topic, SUB_NAME,"testprop='true'", false); + + // Retrieve the matching message + Message m = durSub.receive(2000); + assertNotNull("Failed to receive an expected message", m); + assertEquals("Selector property did not match", "true", m.getStringProperty("testprop")); + assertEquals("ID property did not match", 1, m.getIntProperty("ID")); + assertEquals("Message content was not as expected",BDBStoreUpgradeTestPreparer.generateString(1024) , ((TextMessage)m).getText()); + + // Verify that neither the non-matching or uncommitted message are received + m = durSub.receive(1000); + assertNull("No more messages should have been recieved", m); + + durSub.close(); + session.close(); + } + + private void consumeQueueMessages(Connection connection, boolean extraMessage) throws Exception + { + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = session.createQueue(QUEUE_NAME); + + MessageConsumer consumer = session.createConsumer(queue); + Message m; + + // Retrieve the initial pre-upgrade messages + for (int i=1; i <= 5 ; i++) + { + m = consumer.receive(2000); + assertNotNull("Failed to receive an expected message", m); + assertEquals("ID property did not match", i, m.getIntProperty("ID")); + assertEquals("Message content was not as expected", STRING_1024_256, ((TextMessage)m).getText()); + } + for (int i=1; i <= 5 ; i++) + { + m = consumer.receive(2000); + assertNotNull("Failed to receive an expected message", m); + assertEquals("ID property did not match", i, m.getIntProperty("ID")); + assertEquals("Message content was not as expected", STRING_1024, ((TextMessage)m).getText()); + } + + if(extraMessage) + { + //verify that the extra message is received + m = consumer.receive(2000); + assertNotNull("Failed to receive an expected message", m); + assertEquals("ID property did not match", 1, m.getIntProperty("ID")); + assertEquals("Message content was not as expected", STRING_1024_256, ((TextMessage)m).getText()); + } + + // Verify that no more messages are received + m = consumer.receive(1000); + assertNull("No more messages should have been recieved", m); + + consumer.close(); + session.close(); + } + + private void upgradeBrokerStore(String fromDir, String toDir) throws Exception + { + new BDBStoreUpgrade(_fromDir, _toDir, null, false, true).upgradeFromVersion(VERSION_4); + } +} diff --git a/qpid/java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb b/qpid/java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb Binary files differnew file mode 100644 index 0000000000..c4e4e6c306 --- /dev/null +++ b/qpid/java/bdbstore/src/test/resources/upgrade/bdbstore-to-upgrade/test-store/00000000.jdb diff --git a/qpid/java/broker-plugins/experimental/info/build.xml b/qpid/java/broker-plugins/experimental/info/build.xml index c5881aa839..8f91adc5ff 100644 --- a/qpid/java/broker-plugins/experimental/info/build.xml +++ b/qpid/java/broker-plugins/experimental/info/build.xml @@ -20,7 +20,14 @@ nn - or more contributor license agreements. See the NOTICE file --> <project name="AMQ Broker Info Plugin" default="build"> - <property name="module.depends" value="common broker broker-plugins"/> + <condition property="info-plugin.optional.depends" value="bdbstore" else=""> + <and> + <contains string="${modules.opt}" substring="bdbstore"/> + <contains string="${profile}" substring="bdb"/> + </and> + </condition> + + <property name="module.depends" value="common broker broker-plugins ${info-plugin.optional.depends}"/> <property name="module.test.depends" value="test broker/test management/common client systests common/test"/> <property name="module.manifest" value="MANIFEST.MF"/> <property name="module.plugin" value="true"/> diff --git a/qpid/java/broker/bin/qpid-server b/qpid/java/broker/bin/qpid-server index 90b11da202..382004c9f5 100755 --- a/qpid/java/broker/bin/qpid-server +++ b/qpid/java/broker/bin/qpid-server @@ -33,8 +33,8 @@ if [ -z "$QPID_PNAME" ]; then export QPID_PNAME=" -DPNAME=QPBRKR" fi -# Set classpath to include Qpid jar with all required jars in manifest -QPID_LIBS=$QPID_HOME/lib/qpid-all.jar +# Set classpath to include the qpid-all manifest jar, and any jars supplied in lib/opt +QPID_LIBS="$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/opt/*" # Set other variables used by the qpid-run script before calling export JAVA=java \ @@ -51,6 +51,6 @@ QPID_OPTS="$QPID_OPTS -Damqj.read_write_pool_size=32 -DQPID_LOG_APPEND=$QPID_LOG if [ -z "$QPID_PID_FILENAME" ]; then export QPID_PID_FILENAME="qpid-server.pid" fi -echo $$ > ${QPID_WORK}/${QPID_PID_FILENAME} +echo $$ > "${QPID_WORK}/${QPID_PID_FILENAME}" -. ${QPID_HOME}/bin/qpid-run org.apache.qpid.server.Main "$@" +. "${QPID_HOME}/bin/qpid-run" org.apache.qpid.server.Main "$@" diff --git a/qpid/java/broker/build.xml b/qpid/java/broker/build.xml index edd71effaa..e733474ef0 100644 --- a/qpid/java/broker/build.xml +++ b/qpid/java/broker/build.xml @@ -76,6 +76,10 @@ <copy todir="${module.release}/lib/plugins" failonerror="true"> <fileset dir="${build.lib}/plugins"/> </copy> + <!--copy optional bdbstore module if it exists --> + <copy todir="${module.release}/lib/" failonerror="false"> + <fileset file="${build.lib}/${project.name}-bdbstore-${project.version}.jar"/> + </copy> </target> <target name="release-bin" depends="release-bin-tasks"/> diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java index a5999711bc..765dee2878 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java @@ -144,7 +144,7 @@ public class BasicConsumeMethodHandler implements StateAwareMethodListener<Basic _logger.debug("Closing connection due to invalid selector"); MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); - AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.INVALID_ARGUMENT.getCode(), + AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.ARGUMENT_INVALID.getCode(), new AMQShortString(ise.getMessage()), body.getClazz(), body.getMethod()); diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java index 028f6d85be..67ddd6ca77 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java @@ -645,7 +645,7 @@ public class ServerSession extends Session implements AuthorizationHolder, Sessi // Log a warning on idle or open transactions if (idleWarn > 0L && idleTime > idleWarn) { - CurrentActor.get().message(getLogSubject(), ChannelMessages.IDLE_TXN(openTime)); + CurrentActor.get().message(getLogSubject(), ChannelMessages.IDLE_TXN(idleTime)); _logger.warn("IDLE TRANSACTION ALERT " + getLogSubject().toString() + " " + idleTime + " ms"); } else if (openWarn > 0L && openTime > openWarn) diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java index 22760318b5..17bd06538f 100644 --- a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java @@ -606,6 +606,12 @@ public class ServerSessionDelegate extends SessionDelegate try { + if (nameNullOrEmpty(method.getExchange())) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Delete not allowed for default exchange"); + return; + } + Exchange exchange = getExchange(session, method.getExchange()); if(exchange == null) @@ -641,6 +647,16 @@ public class ServerSessionDelegate extends SessionDelegate } } + private boolean nameNullOrEmpty(String name) + { + if(name == null || name.length() == 0) + { + return true; + } + + return false; + } + private boolean isStandardExchange(Exchange exchange, Collection<ExchangeType<? extends Exchange>> registeredTypes) { for(ExchangeType type : registeredTypes) @@ -687,9 +703,9 @@ public class ServerSessionDelegate extends SessionDelegate { exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); } - else if (!method.hasExchange()) + else if (nameNullOrEmpty(method.getExchange())) { - exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "exchange not set"); + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Bind not allowed for default exchange"); } /* else if (!method.hasBindingKey()) @@ -758,9 +774,9 @@ public class ServerSessionDelegate extends SessionDelegate { exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); } - else if (!method.hasExchange()) + else if (nameNullOrEmpty(method.getExchange())) { - exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "exchange not set"); + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "Unbind not allowed for default exchange"); } else if (!method.hasBindingKey()) { @@ -790,9 +806,6 @@ public class ServerSessionDelegate extends SessionDelegate } } } - - - super.exchangeUnbind(session, method); } @Override diff --git a/qpid/java/build.deps b/qpid/java/build.deps index 2c56a4a911..d0691275ff 100644 --- a/qpid/java/build.deps +++ b/qpid/java/build.deps @@ -135,3 +135,8 @@ broker-plugins-experimental-info.test.libs=${test.libs} ${servlet-api} ${jetty} management-eclipse-plugin.test.libs=${test.libs} management-common.test.libs=${test.libs} + +# optional bdbstore module deps +bdb-je=lib/bdbstore/je-4.0.103.jar +bdbstore.libs=${bdb-je} +bdbstore.test.libs=${test.libs} diff --git a/qpid/java/test-profiles/clean-dir b/qpid/java/build.overrides index 4d6141b4ab..dbe05b4ec0 100755..100644 --- a/qpid/java/test-profiles/clean-dir +++ b/qpid/java/build.overrides @@ -1,7 +1,4 @@ - -#!/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 @@ -9,17 +6,19 @@ # 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. -# # +# File to allow overriding default values of properties within the build system +# without having to specify them on the command line on every build execution -rm -rf $@; mkdir $@ +# Override the setting for the optional modules to be built +#modules.opt=bdbstore diff --git a/qpid/java/build.xml b/qpid/java/build.xml index 84baa4237e..1118822b14 100644 --- a/qpid/java/build.xml +++ b/qpid/java/build.xml @@ -22,6 +22,8 @@ <import file="common.xml"/> + <property file="${project.root}/build.overrides"/> + <findSubProjects name="broker-plugins" dir="broker-plugins"/> <findSubProjects name="client-plugins" dir="client-plugins"/> <findSubProjects name="management" dir="management" excludes="common,example"/> @@ -31,8 +33,9 @@ <property name="modules.tests" value="systests perftests integrationtests testkit"/> <property name="modules.management" value="${management}"/> <property name="modules.plugin" value="${broker-plugins} ${client-plugins}"/> + <property name="modules.opt" value=""/> <property name="modules" value="${modules.core} ${modules.examples} - ${modules.management} ${modules.tests} ${modules.plugin}"/> + ${modules.management} ${modules.tests} ${modules.plugin} ${modules.opt}"/> <property name="qpid.jar" location="${build.lib}/qpid-all.jar"/> <basename property="qpid.jar.name" file="${qpid.jar}"/> diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java index 92f9ebe07c..b1a22155d6 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java @@ -36,11 +36,13 @@ import javax.jms.XASession; import javax.net.ssl.SSLContext; import org.apache.qpid.AMQException; +import org.apache.qpid.AMQTimeoutException; import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.client.failover.FailoverProtectedOperation; import org.apache.qpid.client.failover.FailoverRetrySupport; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateWaiter; import org.apache.qpid.framing.BasicQosBody; import org.apache.qpid.framing.BasicQosOkBody; @@ -69,8 +71,30 @@ public class AMQConnectionDelegate_8_0 implements AMQConnectionDelegate public void closeConnection(long timeout) throws JMSException, AMQException { - _conn.getProtocolHandler().closeConnection(timeout); + final AMQStateManager stateManager = _conn.getProtocolHandler().getStateManager(); + final AMQState currentState = stateManager.getCurrentState(); + if (currentState.equals(AMQState.CONNECTION_CLOSED)) + { + _logger.debug("Connection already closed."); + } + else if (currentState.equals(AMQState.CONNECTION_CLOSING)) + { + _logger.debug("Connection already closing, awaiting closed state."); + final StateWaiter closeWaiter = new StateWaiter(stateManager, currentState, EnumSet.of(AMQState.CONNECTION_CLOSED)); + try + { + closeWaiter.await(timeout); + } + catch (AMQTimeoutException te) + { + throw new AMQTimeoutException("Close did not complete in timely fashion", te); + } + } + else + { + _conn.getProtocolHandler().closeConnection(timeout); + } } public AMQConnectionDelegate_8_0(AMQConnection conn) diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java index 3ef32fb008..acd46da11a 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java @@ -793,7 +793,7 @@ public abstract class AMQDestination implements Destination, Referenceable return _browseOnly; } - public void setBrowseOnly(boolean b) + private void setBrowseOnly(boolean b) { _browseOnly = b; } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java index e0da1ef41f..30c7403a90 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java @@ -70,7 +70,6 @@ import org.apache.qpid.AMQDisconnectedException; import org.apache.qpid.AMQException; import org.apache.qpid.AMQInvalidArgumentException; import org.apache.qpid.AMQInvalidRoutingKeyException; -import org.apache.qpid.client.AMQDestination.AddressOption; import org.apache.qpid.client.AMQDestination.DestSyntax; import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.client.failover.FailoverNoopSupport; @@ -88,8 +87,6 @@ import org.apache.qpid.client.message.JMSTextMessage; import org.apache.qpid.client.message.MessageFactoryRegistry; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.protocol.AMQProtocolHandler; -import org.apache.qpid.client.state.AMQState; -import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.util.FlowControllingBlockingQueue; import org.apache.qpid.common.AMQPFilterTypes; import org.apache.qpid.framing.AMQShortString; @@ -216,8 +213,6 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic */ protected final boolean DEFAULT_MANDATORY = Boolean.parseBoolean(System.getProperty("qpid.default_mandatory", "true")); - protected final boolean DEFAULT_WAIT_ON_SEND = Boolean.parseBoolean(System.getProperty("qpid.default_wait_on_send", "false")); - /** * The period to wait while flow controlled before sending a log message confirming that the session is still * waiting on flow control being revoked @@ -367,7 +362,13 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic * Set when recover is called. This is to handle the case where recover() is called by application code during * onMessage() processing to ensure that an auto ack is not sent. */ - private boolean _inRecovery; + private volatile boolean _sessionInRecovery; + + /** + * Set when the dispatcher should direct incoming messages straight into the UnackedMessage list instead of + * to the syncRecieveQueue or MessageListener. Used during cleanup, e.g. in Session.recover(). + */ + private volatile boolean _usingDispatcherForCleanup; /** Used to indicates that the connection to which this session belongs, has been stopped. */ private boolean _connectionStopped; @@ -617,30 +618,27 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { throw new IllegalStateException("Session is already closed"); } - else if (hasFailedOver()) + else if (hasFailedOverDirty()) { + //perform an implicit recover in this scenario + recover(); + + //notify the consumer throw new IllegalStateException("has failed over"); } - while (true) + try { - Long tag = _unacknowledgedMessageTags.poll(); - if (tag == null) - { - break; - } - - try - { - acknowledgeMessage(tag, false); - } - catch (TransportException e) - { - throw toJMSException("Exception while acknowledging message(s):" + e.getMessage(), e); - } + acknowledgeImpl(); + } + catch (TransportException e) + { + throw toJMSException("Exception while acknowledging message(s):" + e.getMessage(), e); } } + protected abstract void acknowledgeImpl() throws JMSException; + /** * Acknowledge one or many messages. * @@ -849,42 +847,28 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic * @throws JMSException If the JMS provider fails to commit the transaction due to some internal error. This does * not mean that the commit is known to have failed, merely that it is not known whether it * failed or not. - * @todo Be aware of possible changes to parameter order as versions change. */ public void commit() throws JMSException { checkTransacted(); - try + //Check that we are clean to commit. + if (_failedOverDirty) { - //Check that we are clean to commit. - if (_failedOverDirty) - { - rollback(); - - throw new TransactionRolledBackException("Connection failover has occured since last send. " + - "Forced rollback"); - } + rollback(); + throw new TransactionRolledBackException("Connection failover has occured with uncommitted transaction activity." + + "The session transaction was rolled back."); + } - // Acknowledge all delivered messages - while (true) - { - Long tag = _deliveredMessageTags.poll(); - if (tag == null) - { - break; - } - - acknowledgeMessage(tag, false); - } - // Commits outstanding messages and acknowledgments - sendCommit(); + try + { + commitImpl(); markClean(); } catch (AMQException e) { - throw new JMSAMQException("Failed to commit: " + e.getMessage() + ":" + e.getCause(), e); + throw new JMSAMQException("Exception during commit: " + e.getMessage() + ":" + e.getCause(), e); } catch (FailoverException e) { @@ -896,8 +880,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic } } - public abstract void sendCommit() throws AMQException, FailoverException; - + protected abstract void commitImpl() throws AMQException, FailoverException, TransportException; public void confirmConsumerCancelled(int consumerTag) { @@ -975,7 +958,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic return new AMQQueueBrowser(this, (AMQQueue) queue, messageSelector); } - public MessageConsumer createBrowserConsumer(Destination destination, String messageSelector, boolean noLocal) + protected MessageConsumer createBrowserConsumer(Destination destination, String messageSelector, boolean noLocal) throws JMSException { checkValidDestination(destination); @@ -989,15 +972,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic checkValidDestination(destination); return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, (destination instanceof Topic), null, null, - ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); - } - - public C createExclusiveConsumer(Destination destination) throws JMSException - { - checkValidDestination(destination); - - return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, true, null, null, - ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); + isBrowseOnlyDestination(destination), false); } public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException @@ -1005,7 +980,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic checkValidDestination(destination); return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, (destination instanceof Topic), - messageSelector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); + messageSelector, null, isBrowseOnlyDestination(destination), false); } public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) @@ -1014,16 +989,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic checkValidDestination(destination); return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, noLocal, (destination instanceof Topic), - messageSelector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); - } - - public MessageConsumer createExclusiveConsumer(Destination destination, String messageSelector, boolean noLocal) - throws JMSException - { - checkValidDestination(destination); - - return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, noLocal, true, - messageSelector, null, false, false); + messageSelector, null, isBrowseOnlyDestination(destination), false); } public MessageConsumer createConsumer(Destination destination, int prefetch, boolean noLocal, boolean exclusive, @@ -1031,23 +997,15 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { checkValidDestination(destination); - return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); + return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, null, isBrowseOnlyDestination(destination), false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, - boolean exclusive, String selector) throws JMSException + boolean exclusive, String selector) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); - } - - public MessageConsumer createConsumer(Destination destination, int prefetch, boolean noLocal, boolean exclusive, - String selector, FieldTable rawSelector) throws JMSException - { - checkValidDestination(destination); - - return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, rawSelector, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false); + return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, null, isBrowseOnlyDestination(destination), false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, @@ -1055,7 +1013,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { checkValidDestination(destination); - return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, rawSelector, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), + return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, rawSelector, isBrowseOnlyDestination(destination), false); } @@ -1244,12 +1202,6 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic return createProducerImpl(destination, mandatory, immediate); } - public P createProducer(Destination destination, boolean mandatory, boolean immediate, - boolean waitUntilSent) throws JMSException - { - return createProducerImpl(destination, mandatory, immediate, waitUntilSent); - } - public TopicPublisher createPublisher(Topic topic) throws JMSException { checkNotClosed(); @@ -1467,9 +1419,10 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic public TopicSubscriber createSubscriber(Topic topic) throws JMSException { checkNotClosed(); - Topic dest = checkValidTopic(topic); + checkValidTopic(topic); - return new TopicSubscriberAdaptor(dest, (C) createExclusiveConsumer(dest)); + return new TopicSubscriberAdaptor<C>(topic, + createConsumerImpl(topic, _prefetchHighMark, _prefetchLowMark, false, true, null, null, false, false)); } /** @@ -1486,10 +1439,11 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException { checkNotClosed(); - Topic dest = checkValidTopic(topic); + checkValidTopic(topic); - // AMQTopic dest = new AMQTopic(topic.getTopicName()); - return new TopicSubscriberAdaptor(dest, (C) createExclusiveConsumer(dest, messageSelector, noLocal)); + return new TopicSubscriberAdaptor<C>(topic, + createConsumerImpl(topic, _prefetchHighMark, _prefetchLowMark, noLocal, + true, messageSelector, null, false, false)); } public TemporaryQueue createTemporaryQueue() throws JMSException @@ -1591,10 +1545,8 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic abstract public void sync() throws AMQException; - public int getAcknowledgeMode() throws JMSException + public int getAcknowledgeMode() { - checkNotClosed(); - return _acknowledgeMode; } @@ -1654,10 +1606,8 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic return _ticket; } - public boolean getTransacted() throws JMSException + public boolean getTransacted() { - checkNotClosed(); - return _transacted; } @@ -1759,8 +1709,8 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic // flush any acks we are holding in the buffer. flushAcknowledgments(); - // this is set only here, and the before the consumer's onMessage is called it is set to false - _inRecovery = true; + // this is only set true here, and only set false when the consumers preDeliver method is called + _sessionInRecovery = true; boolean isSuspended = isSuspended(); @@ -1768,9 +1718,18 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { suspendChannel(true); } - + + // Set to true to short circuit delivery of anything currently + //in the pre-dispatch queue. + _usingDispatcherForCleanup = true; + syncDispatchQueue(); - + + // Set to false before sending the recover as 0-8/9/9-1 will + //send messages back before the recover completes, and we + //probably shouldn't clean those! ;-) + _usingDispatcherForCleanup = false; + if (_dispatcher != null) { _dispatcher.recover(); @@ -1779,10 +1738,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic sendRecover(); markClean(); - - // Set inRecovery to false before you start message flow again again. - _inRecovery = false; - + if (!isSuspended) { suspendChannel(false); @@ -2018,6 +1974,12 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { checkTemporaryDestination(destination); + if(!noConsume && isBrowseOnlyDestination(destination)) + { + throw new InvalidDestinationException("The consumer being created is not 'noConsume'," + + "but a 'browseOnly' Destination has been supplied."); + } + final String messageSelector; if (_strictAMQP && !((selector == null) || selector.equals(""))) @@ -2176,7 +2138,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic boolean isInRecovery() { - return _inRecovery; + return _sessionInRecovery; } boolean isQueueBound(AMQShortString exchangeName, AMQShortString queueName) throws JMSException @@ -2298,7 +2260,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic void setInRecovery(boolean inRecovery) { - _inRecovery = inRecovery; + _sessionInRecovery = inRecovery; } boolean isStarted() @@ -2637,15 +2599,9 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic public abstract void sendConsume(C consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector, int tag) throws AMQException, FailoverException; - private P createProducerImpl(Destination destination, boolean mandatory, boolean immediate) + private P createProducerImpl(final Destination destination, final boolean mandatory, final boolean immediate) throws JMSException { - return createProducerImpl(destination, mandatory, immediate, DEFAULT_WAIT_ON_SEND); - } - - private P createProducerImpl(final Destination destination, final boolean mandatory, - final boolean immediate, final boolean waitUntilSent) throws JMSException - { return new FailoverRetrySupport<P, JMSException>( new FailoverProtectedOperation<P, JMSException>() { @@ -2658,7 +2614,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic try { producer = createMessageProducer(destination, mandatory, - immediate, waitUntilSent, producerId); + immediate, producerId); } catch (TransportException e) { @@ -2673,7 +2629,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic } public abstract P createMessageProducer(final Destination destination, final boolean mandatory, - final boolean immediate, final boolean waitUntilSent, long producerId) throws JMSException; + final boolean immediate, final long producerId) throws JMSException; private void declareExchange(AMQDestination amqd, AMQProtocolHandler protocolHandler, boolean nowait) throws AMQException { @@ -3113,21 +3069,11 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic * * @return boolean true if failover has occured. */ - public boolean hasFailedOver() + public boolean hasFailedOverDirty() { return _failedOverDirty; } - /** - * Check to see if any message have been sent in this transaction and have not been commited. - * - * @return boolean true if a message has been sent but not commited - */ - public boolean isDirty() - { - return _dirty; - } - public void setTicket(int ticket) { _ticket = ticket; @@ -3391,7 +3337,7 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic { rejectMessage(message, true); } - else if (isInRecovery()) + else if (_usingDispatcherForCleanup) { _unacknowledgedMessageTags.add(deliveryTag); } @@ -3575,4 +3521,9 @@ public abstract class AMQSession<C extends BasicMessageConsumer, P extends Basic } return code; } + + private boolean isBrowseOnlyDestination(Destination destination) + { + return ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()); + } } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java index bfbb9f7148..86e1fc08de 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java @@ -270,7 +270,7 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic long prefetch = getAMQConnection().getMaxPrefetch(); - if (unackedCount >= prefetch/2 || maxAckDelay <= 0) + if (unackedCount >= prefetch/2 || maxAckDelay <= 0 || _acknowledgeMode == javax.jms.Session.AUTO_ACKNOWLEDGE) { flushAcknowledgments(); } @@ -412,25 +412,6 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic } } - - /** - * Commit the receipt and the delivery of all messages exchanged by this session resources. - */ - public void sendCommit() throws AMQException, FailoverException - { - getQpidSession().setAutoSync(true); - try - { - getQpidSession().txCommit(); - } - finally - { - getQpidSession().setAutoSync(false); - } - // We need to sync so that we get notify of an error. - sync(); - } - /** * Create a queue with a given name. * @@ -463,6 +444,14 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic public void sendRecover() throws AMQException, FailoverException { // release all unacked messages + RangeSet ranges = gatherUnackedRangeSet(); + getQpidSession().messageRelease(ranges, Option.SET_REDELIVERED); + // We need to sync so that we get notify of an error. + sync(); + } + + private RangeSet gatherUnackedRangeSet() + { RangeSet ranges = new RangeSet(); while (true) { @@ -471,11 +460,11 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic { break; } - ranges.add((int) (long) tag); + + ranges.add(tag.intValue()); } - getQpidSession().messageRelease(ranges, Option.SET_REDELIVERED); - // We need to sync so that we get notify of an error. - sync(); + + return ranges; } @@ -676,13 +665,12 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic * Create an 0_10 message producer */ public BasicMessageProducer_0_10 createMessageProducer(final Destination destination, final boolean mandatory, - final boolean immediate, final boolean waitUntilSent, - long producerId) throws JMSException + final boolean immediate, final long producerId) throws JMSException { try { return new BasicMessageProducer_0_10(_connection, (AMQDestination) destination, _transacted, _channelId, this, - getProtocolHandler(), producerId, immediate, mandatory, waitUntilSent); + getProtocolHandler(), producerId, immediate, mandatory); } catch (AMQException e) { @@ -998,32 +986,26 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic } } - @Override - public void commit() throws JMSException + public void commitImpl() throws AMQException, FailoverException, TransportException { - checkTransacted(); - try - { - if( _txSize > 0 ) - { - messageAcknowledge(_txRangeSet, true); - _txRangeSet.clear(); - _txSize = 0; - } - sendCommit(); - } - catch(TransportException e) + if( _txSize > 0 ) { - throw toJMSException("Session exception occured while trying to commit: " + e.getMessage(), e); + messageAcknowledge(_txRangeSet, true); + _txRangeSet.clear(); + _txSize = 0; } - catch (AMQException e) + + getQpidSession().setAutoSync(true); + try { - throw new JMSAMQException("Failed to commit: " + e.getMessage(), e); + getQpidSession().txCommit(); } - catch (FailoverException e) + finally { - throw new JMSAMQException("Fail-over interrupted commit. Status of the commit is uncertain.", e); + getQpidSession().setAutoSync(false); } + // We need to sync so that we get notify of an error. + sync(); } protected final boolean tagLE(long tag1, long tag2) @@ -1386,4 +1368,14 @@ public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, Basic return sb.toString(); } + protected void acknowledgeImpl() + { + RangeSet range = gatherUnackedRangeSet(); + + if(range.size() > 0 ) + { + messageAcknowledge(range, true); + getQpidSession().sync(); + } + } } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java index c010e4c7ed..ccb2b00947 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java @@ -76,6 +76,7 @@ import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91; import org.apache.qpid.jms.Session; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.transport.TransportException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -125,6 +126,20 @@ public final class AMQSession_0_8 extends AMQSession<BasicMessageConsumer_0_8, B return getProtocolHandler().getProtocolVersion(); } + protected void acknowledgeImpl() + { + while (true) + { + Long tag = _unacknowledgedMessageTags.poll(); + if (tag == null) + { + break; + } + + acknowledgeMessage(tag, false); + } + } + public void acknowledgeMessage(long deliveryTag, boolean multiple) { BasicAckBody body = getMethodRegistry().createBasicAckBody(deliveryTag, multiple); @@ -170,8 +185,20 @@ public final class AMQSession_0_8 extends AMQSession<BasicMessageConsumer_0_8, B } } - public void sendCommit() throws AMQException, FailoverException + public void commitImpl() throws AMQException, FailoverException, TransportException { + // Acknowledge all delivered messages + while (true) + { + Long tag = _deliveredMessageTags.poll(); + if (tag == null) + { + break; + } + + acknowledgeMessage(tag, false); + } + final AMQProtocolHandler handler = getProtocolHandler(); handler.syncWrite(getProtocolHandler().getMethodRegistry().createTxCommitBody().generateFrame(_channelId), TxCommitOkBody.class); @@ -401,12 +428,12 @@ public final class AMQSession_0_8 extends AMQSession<BasicMessageConsumer_0_8, B public BasicMessageProducer_0_8 createMessageProducer(final Destination destination, final boolean mandatory, - final boolean immediate, final boolean waitUntilSent, long producerId) throws JMSException + final boolean immediate, long producerId) throws JMSException { try { return new BasicMessageProducer_0_8(_connection, (AMQDestination) destination, _transacted, _channelId, - this, getProtocolHandler(), producerId, immediate, mandatory, waitUntilSent); + this, getProtocolHandler(), producerId, immediate, mandatory); } catch (AMQException e) { @@ -615,5 +642,4 @@ public final class AMQSession_0_8 extends AMQSession<BasicMessageConsumer_0_8, B return null; } } - } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java index 754055ad98..3b807591b0 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java @@ -37,10 +37,7 @@ import javax.jms.MessageListener; import java.util.Arrays; import java.util.Iterator; import java.util.List; -import java.util.SortedSet; import java.util.ArrayList; -import java.util.Collections; -import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -118,29 +115,10 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa protected final int _acknowledgeMode; /** - * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode - */ - private int _outstanding; - - /** - * Switch to enable sending of acknowledgements when using DUPS_OK_ACKNOWLEDGE mode. Enabled when _outstannding - * number of msgs >= _prefetchHigh and disabled at < _prefetchLow - */ - private boolean _dups_ok_acknowledge_send; - - /** * List of tags delievered, The last of which which should be acknowledged on commit in transaction mode. */ private ConcurrentLinkedQueue<Long> _receivedDeliveryTags = new ConcurrentLinkedQueue<Long>(); - /** The last tag that was "multiple" acknowledged on this session (if transacted) */ - private long _lastAcked; - - /** set of tags which have previously been acked; but not part of the multiple ack (transacted mode only) */ - private final SortedSet<Long> _previouslyAcked = new TreeSet<Long>(); - - private final Object _commitLock = new Object(); - /** * The thread that was used to call receive(). This is important for being able to interrupt that thread if a * receive() is in progress. @@ -290,17 +268,6 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa } } - protected void preApplicationProcessing(AbstractJMSMessage jmsMsg) throws JMSException - { - if (_session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) - { - _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag()); - } - - _session.setInRecovery(false); - preDeliver(jmsMsg); - } - /** * @param immediate if true then return immediately if the connection is failing over * @@ -323,14 +290,14 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa } } - if (!_receiving.compareAndSet(false, true)) + if (isMessageListenerSet()) { - throw new javax.jms.IllegalStateException("Another thread is already receiving."); + throw new javax.jms.IllegalStateException("A listener has already been set."); } - if (isMessageListenerSet()) + if (!_receiving.compareAndSet(false, true)) { - throw new javax.jms.IllegalStateException("A listener has already been set."); + throw new javax.jms.IllegalStateException("Another thread is already receiving."); } _receivingThread = Thread.currentThread(); @@ -409,7 +376,7 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) { - preApplicationProcessing(m); + preDeliver(m); postDeliver(m); } return m; @@ -482,7 +449,7 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) { - preApplicationProcessing(m); + preDeliver(m); postDeliver(m); } @@ -734,7 +701,7 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa { if (isMessageListenerSet()) { - preApplicationProcessing(jmsMessage); + preDeliver(jmsMessage); getMessageListener().onMessage(jmsMessage); postDeliver(jmsMessage); } @@ -758,49 +725,42 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa } } - void preDeliver(AbstractJMSMessage msg) + protected void preDeliver(AbstractJMSMessage msg) { + _session.setInRecovery(false); + switch (_acknowledgeMode) { - case Session.PRE_ACKNOWLEDGE: _session.acknowledgeMessage(msg.getDeliveryTag(), false); break; - + case Session.AUTO_ACKNOWLEDGE: + //fall through + case Session.DUPS_OK_ACKNOWLEDGE: + _session.addUnacknowledgedMessage(msg.getDeliveryTag()); + break; case Session.CLIENT_ACKNOWLEDGE: // we set the session so that when the user calls acknowledge() it can call the method on session // to send out the appropriate frame msg.setAMQSession(_session); + _session.addUnacknowledgedMessage(msg.getDeliveryTag()); + _session.markDirty(); break; case Session.SESSION_TRANSACTED: - if (isNoConsume()) - { - _session.acknowledgeMessage(msg.getDeliveryTag(), false); - } - else - { - _session.addDeliveredMessage(msg.getDeliveryTag()); - _session.markDirty(); - } - + _session.addDeliveredMessage(msg.getDeliveryTag()); + _session.markDirty(); + break; + case Session.NO_ACKNOWLEDGE: + //do nothing. + //path used for NO-ACK consumers, and browsers (see constructor). break; } - } void postDeliver(AbstractJMSMessage msg) { switch (_acknowledgeMode) { - - case Session.CLIENT_ACKNOWLEDGE: - if (isNoConsume()) - { - _session.acknowledgeMessage(msg.getDeliveryTag(), false); - } - _session.markDirty(); - break; - case Session.DUPS_OK_ACKNOWLEDGE: case Session.AUTO_ACKNOWLEDGE: // we do not auto ack a message if the application code called recover() @@ -838,63 +798,6 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa return null; } - /** - * Acknowledge up to last message delivered (if any). Used when commiting. - */ - void acknowledgeDelivered() - { - synchronized(_commitLock) - { - ArrayList<Long> tagsToAck = new ArrayList<Long>(); - - while (!_receivedDeliveryTags.isEmpty()) - { - tagsToAck.add(_receivedDeliveryTags.poll()); - } - - Collections.sort(tagsToAck); - - long prevAcked = _lastAcked; - long oldAckPoint = -1; - - while(oldAckPoint != prevAcked) - { - oldAckPoint = prevAcked; - - Iterator<Long> tagsToAckIterator = tagsToAck.iterator(); - - while(tagsToAckIterator.hasNext() && tagsToAckIterator.next() == prevAcked+1) - { - tagsToAckIterator.remove(); - prevAcked++; - } - - Iterator<Long> previousAckIterator = _previouslyAcked.iterator(); - while(previousAckIterator.hasNext() && previousAckIterator.next() == prevAcked+1) - { - previousAckIterator.remove(); - prevAcked++; - } - - } - if(prevAcked != _lastAcked) - { - _session.acknowledgeMessage(prevAcked, true); - _lastAcked = prevAcked; - } - - Iterator<Long> tagsToAckIterator = tagsToAck.iterator(); - - while(tagsToAckIterator.hasNext()) - { - Long tag = tagsToAckIterator.next(); - _session.acknowledgeMessage(tag, false); - _previouslyAcked.add(tag); - } - } - } - - void notifyError(Throwable cause) { // synchronized (_closed) @@ -973,7 +876,7 @@ public abstract class BasicMessageConsumer<U> extends Closeable implements Messa public boolean isNoConsume() { - return _noConsume || _destination.isBrowseOnly() ; + return _noConsume; } public void rollback() diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java index 47da59724c..3c24c67f9b 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java @@ -66,19 +66,13 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM private boolean _preAcquire = true; /** - * Indicate whether this consumer is started. - */ - private boolean _isStarted = false; - - /** * Specify whether this consumer is performing a sync receive */ private final AtomicBoolean _syncReceive = new AtomicBoolean(false); private String _consumerTagString; private long capacity = 0; - - //--- constructor + protected BasicMessageConsumer_0_10(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session, AMQProtocolHandler protocolHandler, @@ -104,7 +98,6 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM _preAcquire = false; } } - _isStarted = connection.started(); // Destination setting overrides connection defaults if (destination.getDestSyntax() == DestSyntax.ADDR && @@ -172,8 +165,6 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM } } - //----- overwritten methods - /** * This method is invoked when this consumer is stopped. * It tells the broker to stop delivering messages to this consumer. @@ -203,11 +194,18 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM super.notifyMessage(messageFrame); } - @Override protected void preApplicationProcessing(AbstractJMSMessage jmsMsg) throws JMSException + @Override + protected void preDeliver(AbstractJMSMessage jmsMsg) { - super.preApplicationProcessing(jmsMsg); - if (!_session.getTransacted() && _session.getAcknowledgeMode() != org.apache.qpid.jms.Session.CLIENT_ACKNOWLEDGE) + super.preDeliver(jmsMsg); + + if (_acknowledgeMode == org.apache.qpid.jms.Session.NO_ACKNOWLEDGE) { + //For 0-10 we need to ensure that all messages are indicated processed in some way to + //ensure their AMQP command-id is marked completed, and so we must send a completion + //even for no-ack messages even though there isnt actually an 'acknowledgement' occurring. + //Add message to the unacked message list to ensure we dont lose record of it before + //sending a completion of some sort. _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag()); } } @@ -219,7 +217,6 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM return _messageFactory.createMessage(msg.getMessageTransfer()); } - // private methods /** * Check whether a message can be delivered to this consumer. * @@ -457,10 +454,8 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedM } if (_acknowledgeMode == org.apache.qpid.jms.Session.AUTO_ACKNOWLEDGE && - !_session.isInRecovery() && - _session.getAMQConnection().getSyncAck()) + !_session.isInRecovery() && _session.getAMQConnection().getSyncAck()) { - ((AMQSession_0_10) getSession()).flushAcknowledgments(); ((AMQSession_0_10) getSession()).getQpidSession().sync(); } } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java index 2bfca025b2..bf4de782a5 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java @@ -114,8 +114,6 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac private final boolean _mandatory; - private final boolean _waitUntilSent; - private boolean _disableMessageId; private UUIDGen _messageIdGenerator = UUIDs.newGenerator(); @@ -127,8 +125,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac protected PublishMode publishMode = PublishMode.ASYNC_PUBLISH_ALL; protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId, - AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory, - boolean waitUntilSent) throws AMQException + AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory) throws AMQException { _connection = connection; _destination = destination; @@ -144,7 +141,6 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac _immediate = immediate; _mandatory = mandatory; - _waitUntilSent = waitUntilSent; _userID = connection.getUsername(); setPublishMode(); } @@ -364,19 +360,6 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } } - public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException - { - checkPreConditions(); - checkDestination(destination); - synchronized (_connection.getFailoverMutex()) - { - validateDestination(destination); - sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, - waitUntilSent); - } - } - private AbstractJMSMessage convertToNativeMessage(Message message) throws JMSException { if (message instanceof AbstractJMSMessage) @@ -451,12 +434,6 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } } - protected void sendImpl(AMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate) throws JMSException - { - sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent); - } - /** * The caller of this method must hold the failover mutex. * @@ -471,23 +448,13 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac * @throws JMSException */ protected void sendImpl(AMQDestination destination, Message origMessage, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate, boolean wait) throws JMSException + boolean mandatory, boolean immediate) throws JMSException { checkTemporaryDestination(destination); origMessage.setJMSDestination(destination); AbstractJMSMessage message = convertToNativeMessage(origMessage); - if (_transacted) - { - if (_session.hasFailedOver() && _session.isDirty()) - { - throw new JMSAMQException("Failover has occurred and session is dirty so unable to send.", - new AMQSessionDirtyException("Failover has occurred and session is dirty " + - "so unable to send.")); - } - } - UUID messageId = null; if (_disableMessageId) { @@ -501,7 +468,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac try { - sendMessage(destination, origMessage, message, messageId, deliveryMode, priority, timeToLive, mandatory, immediate, wait); + sendMessage(destination, origMessage, message, messageId, deliveryMode, priority, timeToLive, mandatory, immediate); } catch (TransportException e) { @@ -526,7 +493,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac abstract void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, UUID messageId, int deliveryMode, int priority, long timeToLive, boolean mandatory, - boolean immediate, boolean wait) throws JMSException; + boolean immediate) throws JMSException; private void checkTemporaryDestination(AMQDestination destination) throws JMSException { diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java index 1fa5c1003f..57f64c2f92 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java @@ -61,10 +61,9 @@ public class BasicMessageProducer_0_10 extends BasicMessageProducer BasicMessageProducer_0_10(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId, AMQSession session, AMQProtocolHandler protocolHandler, long producerId, - boolean immediate, boolean mandatory, boolean waitUntilSent) throws AMQException + boolean immediate, boolean mandatory) throws AMQException { - super(connection, destination, transacted, channelId, session, protocolHandler, producerId, immediate, - mandatory, waitUntilSent); + super(connection, destination, transacted, channelId, session, protocolHandler, producerId, immediate, mandatory); userIDBytes = Strings.toUTF8(_userID); } @@ -104,7 +103,7 @@ public class BasicMessageProducer_0_10 extends BasicMessageProducer */ void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, UUID messageId, int deliveryMode, int priority, long timeToLive, boolean mandatory, - boolean immediate, boolean wait) throws JMSException + boolean immediate) throws JMSException { message.prepareForSending(); diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java index 26e9814e33..34d2ade723 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java @@ -45,10 +45,9 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer { BasicMessageProducer_0_8(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId, - AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory, - boolean waitUntilSent) throws AMQException + AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory) throws AMQException { - super(connection, destination,transacted,channelId,session, protocolHandler, producerId, immediate, mandatory,waitUntilSent); + super(connection, destination,transacted,channelId,session, protocolHandler, producerId, immediate, mandatory); } void declareDestination(AMQDestination destination) @@ -73,7 +72,7 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, UUID messageId, int deliveryMode,int priority, long timeToLive, boolean mandatory, - boolean immediate, boolean wait) throws JMSException + boolean immediate) throws JMSException { BasicPublishBody body = getSession().getMethodRegistry().createBasicPublishBody(_session.getTicket(), destination.getExchangeName(), @@ -168,7 +167,7 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer throw jmse; } - _protocolHandler.writeFrame(compositeFrame, wait); + _protocolHandler.writeFrame(compositeFrame); } /** diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java index 2cf19bf391..b9d4d6fa95 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java @@ -78,7 +78,7 @@ public class ChannelCloseMethodHandler implements StateAwareMethodListener<Chann { throw new AMQNoRouteException("Error: " + reason, null, null); } - else if (errorCode == AMQConstant.INVALID_ARGUMENT) + else if (errorCode == AMQConstant.ARGUMENT_INVALID) { _logger.debug("Broker responded with Invalid Argument."); diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java index c2821591d8..a9434edf49 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java @@ -26,9 +26,7 @@ import org.apache.qpid.client.AMQSession; import javax.jms.Destination; import javax.jms.JMSException; -import java.nio.ByteBuffer; import java.util.Enumeration; -import java.util.Map; import java.util.UUID; public interface AMQMessageDelegate @@ -130,9 +128,9 @@ public interface AMQMessageDelegate void removeProperty(final String propertyName) throws JMSException; - void setAMQSession(final AMQSession s); + void setAMQSession(final AMQSession<?,?> s); - AMQSession getAMQSession(); + AMQSession<?,?> getAMQSession(); long getDeliveryTag(); diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java index 43b3b85641..f360b546b2 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java @@ -37,12 +37,10 @@ import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageFormatException; import javax.jms.MessageNotWriteableException; -import javax.jms.Session; import org.apache.qpid.AMQException; import org.apache.qpid.AMQPInvalidClassException; import org.apache.qpid.client.AMQDestination; -import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.AMQSession_0_10; import org.apache.qpid.client.CustomJMSXProperty; import org.apache.qpid.framing.AMQShortString; @@ -76,13 +74,8 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate private Destination _destination; - private MessageProperties _messageProps; private DeliveryProperties _deliveryProps; - /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */ - private AMQSession _session; - private final long _deliveryTag; - protected AMQMessageDelegate_0_10() { @@ -92,9 +85,9 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate protected AMQMessageDelegate_0_10(MessageProperties messageProps, DeliveryProperties deliveryProps, long deliveryTag) { + super(deliveryTag); _messageProps = messageProps; _deliveryProps = deliveryProps; - _deliveryTag = deliveryTag; _readableProperties = (_messageProps != null); AMQDestination dest; @@ -205,7 +198,6 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate } } - public long getJMSTimestamp() throws JMSException { return _deliveryProps.getTimestamp(); @@ -291,7 +283,7 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate try { - return AMQDestination.createDestination("ADDR:" + addr.toString()); + return AMQDestination.createDestination("ADDR:" + addr); } catch(Exception e) { @@ -325,14 +317,14 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate { try { - int type = ((AMQSession_0_10)_session).resolveAddressType(amqd); + int type = ((AMQSession_0_10)getAMQSession()).resolveAddressType(amqd); if (type == AMQDestination.QUEUE_TYPE) { - ((AMQSession_0_10)_session).setLegacyFiledsForQueueType(amqd); + ((AMQSession_0_10)getAMQSession()).setLegacyFiledsForQueueType(amqd); } else { - ((AMQSession_0_10)_session).setLegacyFiledsForTopicType(amqd); + ((AMQSession_0_10)getAMQSession()).setLegacyFiledsForTopicType(amqd); } } catch(AMQException ex) @@ -905,64 +897,6 @@ public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate _readableProperties = false; } - - public void acknowledgeThis() throws JMSException - { - // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge - // is not specified. In our case, we only set the session field where client acknowledge mode is specified. - if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) - { - if (_session.getAMQConnection().isClosed()) - { - throw new javax.jms.IllegalStateException("Connection is already closed"); - } - - // we set multiple to true here since acknowledgment implies acknowledge of all previous messages - // received on the session - _session.acknowledgeMessage(_deliveryTag, true); - } - } - - public void acknowledge() throws JMSException - { - if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) - { - _session.acknowledge(); - } - } - - - /** - * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls - * acknowledge() - * - * @param s the AMQ session that delivered this message - */ - public void setAMQSession(AMQSession s) - { - _session = s; - } - - public AMQSession getAMQSession() - { - return _session; - } - - /** - * Get the AMQ message number assigned to this message - * - * @return the message number - */ - public long getDeliveryTag() - { - return _deliveryTag; - } - - - - - - protected void checkPropertyName(CharSequence propertyName) { if (propertyName == null) diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java index b9ba946a20..9ab03412fe 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java @@ -30,7 +30,6 @@ import java.util.UUID; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageNotWriteableException; -import javax.jms.Session; import org.apache.qpid.client.AMQDestination; import org.apache.qpid.client.AMQQueue; @@ -60,15 +59,12 @@ public class AMQMessageDelegate_0_8 extends AbstractAMQMessageDelegate Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT)); private ContentHeaderProperties _contentHeaderProperties; - /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */ - private AMQSession _session; - private final long _deliveryTag; // The base set of items that needs to be set. private AMQMessageDelegate_0_8(BasicContentHeaderProperties properties, long deliveryTag) { + super(deliveryTag); _contentHeaderProperties = properties; - _deliveryTag = deliveryTag; _readableProperties = (_contentHeaderProperties != null); _headerAdapter = new JMSHeaderAdapter(_readableProperties ? ((BasicContentHeaderProperties) _contentHeaderProperties).getHeaders() : (new BasicContentHeaderProperties()).getHeaders() ); @@ -518,58 +514,4 @@ public class AMQMessageDelegate_0_8 extends AbstractAMQMessageDelegate _readableProperties = false; } - - - public void acknowledgeThis() throws JMSException - { - // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge - // is not specified. In our case, we only set the session field where client acknowledge mode is specified. - if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) - { - if (_session.getAMQConnection().isClosed()) - { - throw new javax.jms.IllegalStateException("Connection is already closed"); - } - - // we set multiple to true here since acknowledgement implies acknowledge of all previous messages - // received on the session - _session.acknowledgeMessage(_deliveryTag, true); - } - } - - public void acknowledge() throws JMSException - { - if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) - { - _session.acknowledge(); - } - } - - - /** - * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls - * acknowledge() - * - * @param s the AMQ session that delivered this message - */ - public void setAMQSession(AMQSession s) - { - _session = s; - } - - public AMQSession getAMQSession() - { - return _session; - } - - /** - * Get the AMQ message number assigned to this message - * - * @return the message number - */ - public long getDeliveryTag() - { - return _deliveryTag; - } - } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java index 89fbc9722c..1b6c0c751d 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java @@ -23,9 +23,13 @@ package org.apache.qpid.client.message; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.jms.JMSException; +import javax.jms.Session; + import org.apache.qpid.client.AMQAnyDestination; import org.apache.qpid.client.AMQDestination; import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.AMQTopic; import org.apache.qpid.exchange.ExchangeDefaults; import org.apache.qpid.framing.AMQShortString; @@ -78,7 +82,25 @@ public abstract class AbstractAMQMessageDelegate implements AMQMessageDelegate new ExchangeInfo(ExchangeDefaults.HEADERS_EXCHANGE_NAME.toString(), ExchangeDefaults.HEADERS_EXCHANGE_CLASS.toString(), AMQDestination.QUEUE_TYPE)); - + } + + /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */ + private AMQSession<?,?> _session; + private final long _deliveryTag; + + protected AbstractAMQMessageDelegate(long deliveryTag) + { + _deliveryTag = deliveryTag; + } + + /** + * Get the AMQ message number assigned to this message + * + * @return the message number + */ + public long getDeliveryTag() + { + return _deliveryTag; } /** @@ -157,6 +179,47 @@ public abstract class AbstractAMQMessageDelegate implements AMQMessageDelegate { return _exchangeMap.containsKey(exchange); } + + public void acknowledgeThis() throws JMSException + { + // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge + // is not specified. In our case, we only set the session field where client acknowledge mode is specified. + if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) + { + if (_session.getAMQConnection().isClosed()) + { + throw new javax.jms.IllegalStateException("Connection is already closed"); + } + + // we set multiple to true here since acknowledgement implies acknowledge of all previous messages + // received on the session + _session.acknowledgeMessage(getDeliveryTag(), true); + } + } + + public void acknowledge() throws JMSException + { + if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) + { + _session.acknowledge(); + } + } + + /** + * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls + * acknowledge() + * + * @param s the AMQ session that delivered this message + */ + public void setAMQSession(AMQSession<?,?> s) + { + _session = s; + } + + public AMQSession<?,?> getAMQSession() + { + return _session; + } } class ExchangeInfo @@ -202,5 +265,5 @@ class ExchangeInfo public void setDestType(int destType) { this.destType = destType; - } + } } diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java index 208496ec41..284954edba 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java @@ -30,7 +30,6 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.qpid.AMQConnectionClosedException; @@ -66,7 +65,6 @@ import org.apache.qpid.protocol.ProtocolEngine; import org.apache.qpid.thread.Threading; import org.apache.qpid.transport.Sender; import org.apache.qpid.transport.network.NetworkConnection; -import org.apache.qpid.transport.network.NetworkTransport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -516,18 +514,7 @@ public class AMQProtocolHandler implements ProtocolEngine return getStateManager().createWaiter(states); } - /** - * Convenience method that writes a frame to the protocol session. Equivalent to calling - * getProtocolSession().write(). - * - * @param frame the frame to write - */ - public void writeFrame(AMQDataBlock frame) - { - writeFrame(frame, false); - } - - public synchronized void writeFrame(AMQDataBlock frame, boolean wait) + public synchronized void writeFrame(AMQDataBlock frame) { final ByteBuffer buf = asByteBuffer(frame); _writtenBytes += buf.remaining(); @@ -678,22 +665,21 @@ public class AMQProtocolHandler implements ProtocolEngine * <p/>If a failover exception occurs whilst closing the connection it is ignored, as the connection is closed * anyway. * - * @param timeout The timeout to wait for an acknowledgement to the close request. + * @param timeout The timeout to wait for an acknowledgment to the close request. * * @throws AMQException If the close fails for any reason. */ public void closeConnection(long timeout) throws AMQException { - ConnectionCloseBody body = _protocolSession.getMethodRegistry().createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), // replyCode - new AMQShortString("JMS client is closing the connection."), 0, 0); - - final AMQFrame frame = body.generateFrame(0); - - //If the connection is already closed then don't do a syncWrite if (!getStateManager().getCurrentState().equals(AMQState.CONNECTION_CLOSED)) { + // Connection is already closed then don't do a syncWrite try { + final ConnectionCloseBody body = _protocolSession.getMethodRegistry().createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), // replyCode + new AMQShortString("JMS client is closing the connection."), 0, 0); + final AMQFrame frame = body.generateFrame(0); + syncWrite(frame, ConnectionCloseOkBody.class, timeout); _network.close(); closed(); @@ -704,10 +690,9 @@ public class AMQProtocolHandler implements ProtocolEngine } catch (FailoverException e) { - _logger.debug("FailoverException interrupted connection close, ignoring as connection close anyway."); + _logger.debug("FailoverException interrupted connection close, ignoring as connection closed anyway."); } } - } /** @return the number of bytes read from this protocol session */ diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java index 5b7d272506..a938bd47f8 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java @@ -20,27 +20,35 @@ */ package org.apache.qpid.client.protocol; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.jms.JMSException; import javax.security.sasl.SaslClient; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import org.apache.qpid.AMQException; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.ConnectionTuneParameters; +import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.state.AMQStateManager; -import org.apache.qpid.client.state.AMQState; -import org.apache.qpid.framing.*; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.framing.ProtocolVersion; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; import org.apache.qpid.transport.Sender; -import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Wrapper for protocol session that provides type-safe access to session attributes. <p/> The underlying protocol @@ -289,22 +297,11 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession return _connection.getSession(channelId); } - /** - * Convenience method that writes a frame to the protocol session. Equivalent to calling - * getProtocolSession().write(). - * - * @param frame the frame to write - */ public void writeFrame(AMQDataBlock frame) { _protocolHandler.writeFrame(frame); } - public void writeFrame(AMQDataBlock frame, boolean wait) - { - _protocolHandler.writeFrame(frame, wait); - } - /** * Starts the process of closing a session * diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java index 9c7d62670c..0d6fc727c1 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java @@ -31,7 +31,6 @@ import org.slf4j.LoggerFactory; import java.util.Set; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import java.io.IOException; /** * The state manager is responsible for managing the state of the protocol session. <p/> @@ -48,7 +47,7 @@ import java.io.IOException; * * The two step process is required as there is an inherit race condition between starting a process that will cause * the state to change and then attempting to wait for that change. The interest in the change must be first set up so - * that any asynchrous errors that occur can be delivered to the correct waiters. + * that any asynchronous errors that occur can be delivered to the correct waiters. */ public class AMQStateManager implements AMQMethodListener { @@ -84,7 +83,10 @@ public class AMQStateManager implements AMQMethodListener public AMQState getCurrentState() { - return _currentState; + synchronized (_stateLock) + { + return _currentState; + } } public void changeState(AMQState newState) @@ -114,7 +116,7 @@ public class AMQStateManager implements AMQMethodListener } /** - * Setting of the ProtocolSession will be required when Failover has been successfuly compeleted. + * Setting of the ProtocolSession will be required when Failover has been successfully completed. * * The new {@link AMQProtocolSession} that has been re-established needs to be provided as that is now the * connection to the network. @@ -131,9 +133,9 @@ public class AMQStateManager implements AMQMethodListener } /** - * Propogate error to waiters + * Propagate error to waiters * - * @param error The error to propogate. + * @param error The error to propagate. */ public void error(Exception error) { @@ -177,7 +179,7 @@ public class AMQStateManager implements AMQMethodListener } /** - * Create and add a new waiter to the notifcation list. + * Create and add a new waiter to the notification list. * * @param states The waiter will attempt to wait for one of these desired set states to be achived. * diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java index 79f438d35d..732480e1c9 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java @@ -34,7 +34,7 @@ import java.util.Set; * * On construction the current state and a set of States to await for is provided. * - * When await() is called the state at constuction is compared against the awaitStates. If the state at construction is + * When await() is called the state at construction is compared against the awaitStates. If the state at construction is * a desired state then await() returns immediately. * * Otherwise it will block for the set timeout for a desired state to be achieved. @@ -48,9 +48,9 @@ public class StateWaiter extends BlockingWaiter<AMQState> { private static final Logger _logger = LoggerFactory.getLogger(StateWaiter.class); - Set<AMQState> _awaitStates; - private AMQState _startState; - private AMQStateManager _stateManager; + private final Set<AMQState> _awaitStates; + private final AMQState _startState; + private final AMQStateManager _stateManager; /** * @@ -78,9 +78,9 @@ public class StateWaiter extends BlockingWaiter<AMQState> } /** - * Await for the requried State to be achieved within the default timeout. + * Await for the required State to be achieved within the default timeout. * @return The achieved state that was requested. - * @throws AMQException The exception that prevented the required state from being achived. + * @throws AMQException The exception that prevented the required state from being achieved. */ public AMQState await() throws AMQException { @@ -88,13 +88,13 @@ public class StateWaiter extends BlockingWaiter<AMQState> } /** - * Await for the requried State to be achieved. + * Await for the required State to be achieved. * * <b>It is the responsibility of this class to remove the waiter from the StateManager * - * @param timeout The time in milliseconds to wait for any of the states to be achived. + * @param timeout The time in milliseconds to wait for any of the states to be achieved. * @return The achieved state that was requested. - * @throws AMQException The exception that prevented the required state from being achived. + * @throws AMQException The exception that prevented the required state from being achieved. */ public AMQState await(long timeout) throws AMQException { diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java index 208658a5ff..bec41644fc 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java @@ -28,9 +28,8 @@ import java.util.concurrent.locks.ReentrantLock; import org.apache.qpid.AMQException; import org.apache.qpid.AMQTimeoutException; import org.apache.qpid.client.failover.FailoverException; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.protocol.AMQMethodEvent; -import org.apache.qpid.protocol.AMQMethodListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * BlockingWaiter is a 'rendezvous' which delegates handling of @@ -64,6 +63,8 @@ import org.apache.qpid.protocol.AMQMethodListener; */ public abstract class BlockingWaiter<T> { + private static final Logger _logger = LoggerFactory.getLogger(BlockingWaiter.class); + /** This flag is used to indicate that the blocked for method has been received. */ private volatile boolean _ready = false; @@ -180,7 +181,7 @@ public abstract class BlockingWaiter<T> } catch (InterruptedException e) { - System.err.println(e.getMessage()); + _logger.error(e.getMessage(), e); // IGNORE -- //fixme this isn't ideal as being interrupted isn't equivellant to sucess // if (!_ready && timeout != -1) // { @@ -228,12 +229,12 @@ public abstract class BlockingWaiter<T> } /** - * This is a callback, called when an error has occured that should interupt any waiter. + * This is a callback, called when an error has occurred that should interrupt any waiter. * It is also called from within this class to avoid code repetition but it should only be called by the MINA threads. * * Once closed any notification of an exception will be ignored. * - * @param e The exception being propogated. + * @param e The exception being propagated. */ public void error(Exception e) { @@ -255,7 +256,7 @@ public abstract class BlockingWaiter<T> } else { - System.err.println("WARNING: new error '" + e == null ? "null" : e.getMessage() + "' arrived while old one not yet processed:" + _error.getMessage()); + _logger.error("WARNING: new error '" + e == null ? "null" : e.getMessage() + "' arrived while old one not yet processed:" + _error.getMessage()); } if (_waiting.get()) @@ -272,7 +273,7 @@ public abstract class BlockingWaiter<T> } catch (InterruptedException e1) { - System.err.println(e.getMessage()); + _logger.error(e1.getMessage(), e1); } } _errorAck = false; diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java index b830c377b8..4ad917fa83 100644 --- a/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java +++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java @@ -51,7 +51,4 @@ public interface MessageProducer extends javax.jms.MessageProducer int priority, long timeToLive, boolean mandatory, boolean immediate) throws JMSException; - void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException; - } diff --git a/qpid/java/client/src/test/java/org/apache/qpid/client/AMQSession_0_10Test.java b/qpid/java/client/src/test/java/org/apache/qpid/client/AMQSession_0_10Test.java index ea55419144..849827216c 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/client/AMQSession_0_10Test.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/client/AMQSession_0_10Test.java @@ -97,7 +97,7 @@ public class AMQSession_0_10Test extends TestCase AMQSession_0_10 session = createThrowingExceptionAMQSession_0_10(); try { - session.createMessageProducer(createDestination(), true, true, true, 1l); + session.createMessageProducer(createDestination(), true, true, 1l); fail("JMSException should be thrown"); } catch (Exception e) @@ -350,7 +350,7 @@ public class AMQSession_0_10Test extends TestCase AMQSession_0_10 session = createAMQSession_0_10(); try { - session.createMessageProducer(createDestination(), true, true, true, 1l); + session.createMessageProducer(createDestination(), true, true, 1l); } catch (Exception e) { diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java index 66f220643c..d560c413e6 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java @@ -73,7 +73,7 @@ public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListe { throw new AMQNoRouteException("Error: " + reason, null, null); } - else if (errorCode == AMQConstant.INVALID_ARGUMENT) + else if (errorCode == AMQConstant.ARGUMENT_INVALID) { _logger.debug("Broker responded with Invalid Argument."); diff --git a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/TestAMQSession.java b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/TestAMQSession.java index 4637c6e505..6759b43387 100644 --- a/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/TestAMQSession.java +++ b/qpid/java/client/src/test/java/org/apache/qpid/test/unit/message/TestAMQSession.java @@ -20,17 +20,24 @@ */ package org.apache.qpid.test.unit.message; -import org.apache.qpid.client.*; +import java.util.Map; + +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.TemporaryQueue; +import javax.jms.Topic; +import javax.jms.TopicSubscriber; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.BasicMessageConsumer_0_8; +import org.apache.qpid.client.BasicMessageProducer_0_8; +import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.client.message.AMQMessageDelegateFactory; import org.apache.qpid.client.protocol.AMQProtocolHandler; -import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.AMQException; - -import javax.jms.*; - -import java.util.Map; public class TestAMQSession extends AMQSession<BasicMessageConsumer_0_8, BasicMessageProducer_0_8> { @@ -57,7 +64,12 @@ public class TestAMQSession extends AMQSession<BasicMessageConsumer_0_8, BasicMe } - public void sendCommit() throws AMQException, FailoverException + public void commitImpl() throws AMQException, FailoverException + { + + } + + public void acknowledgeImpl() { } @@ -117,7 +129,7 @@ public class TestAMQSession extends AMQSession<BasicMessageConsumer_0_8, BasicMe } - public BasicMessageProducer_0_8 createMessageProducer(Destination destination, boolean mandatory, boolean immediate, boolean waitUntilSent, long producerId) + public BasicMessageProducer_0_8 createMessageProducer(Destination destination, boolean mandatory, boolean immediate, long producerId) { return null; } diff --git a/qpid/java/common.xml b/qpid/java/common.xml index 6bb71ef24e..c6688ee2de 100644 --- a/qpid/java/common.xml +++ b/qpid/java/common.xml @@ -42,7 +42,6 @@ <property name="build.report" location="${build}/report"/> <property name="build.release" location="${build}/release"/> <property name="build.release.prepare" location="${build.release}/prepare"/> - <property name="build.data" location="${build.scratch}/data"/> <property name="build.plugins" location="${build}/lib/plugins"/> <property name="build.coveragereport" location="${build}/coverage"/> <property name="build.findbugs" location="${build}/findbugs"/> diff --git a/qpid/java/common/bin/qpid-run b/qpid/java/common/bin/qpid-run index 15d88992df..ef4363e88b 100755 --- a/qpid/java/common/bin/qpid-run +++ b/qpid/java/common/bin/qpid-run @@ -77,7 +77,10 @@ fi #Set the default system properties that we'll use now that they have #all been initialised -SYSTEM_PROPS="-Damqj.logging.level=$AMQJ_LOGGING_LEVEL -DQPID_HOME=$QPID_HOME -DQPID_WORK=$QPID_WORK" +declare -a SYSTEM_PROPS +SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-Damqj.logging.level=$AMQJ_LOGGING_LEVEL" +SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-DQPID_HOME=$QPID_HOME" +SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="-DQPID_WORK=$QPID_WORK" #If logprefix or logsuffix set to use PID make that happen #Otherwise just pass the value through for these props @@ -90,7 +93,7 @@ if [ -n "$QPID_LOG_PREFIX" ]; then log $INFO Using qpid logprefix property LOG_PREFIX=" -Dlogprefix=$QPID_LOG_PREFIX" fi - SYSTEM_PROPS="${SYSTEM_PROPS} ${LOG_PREFIX}" + SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="${LOG_PREFIX}" fi if [ -n "$QPID_LOG_SUFFIX" ]; then @@ -101,10 +104,10 @@ if [ -n "$QPID_LOG_SUFFIX" ]; then log $INFO Using qpig logsuffix property LOG_SUFFIX=" -Dlogsuffix=$QPID_LOG_SUFFIX" fi - SYSTEM_PROPS="${SYSTEM_PROPS} ${LOG_SUFFIX}" + SYSTEM_PROPS[${#SYSTEM_PROPS[@]}]="${LOG_SUFFIX}" fi -log $INFO System Properties set to $SYSTEM_PROPS +log $INFO System Properties set to ${SYSTEM_PROPS[@]} log $INFO QPID_OPTS set to $QPID_OPTS program=$(basename $0) @@ -254,6 +257,6 @@ if $cygwin; then JAVA=$(cygpath -u $JAVA) fi -COMMAND=($JAVA $JAVA_VM $QPID_PNAME $JAVA_GC $JAVA_MEM $SYSTEM_PROPS $JAVA_OPTS $QPID_OPTS "${JAVA_ARGS[@]}") +COMMAND=($JAVA $JAVA_VM $QPID_PNAME $JAVA_GC $JAVA_MEM "${SYSTEM_PROPS[@]}" $JAVA_OPTS $QPID_OPTS "${JAVA_ARGS[@]}") DISPATCH diff --git a/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java index baca2a4773..2bbaaef1fc 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java @@ -34,7 +34,7 @@ public class AMQInvalidArgumentException extends AMQException { public AMQInvalidArgumentException(String message, Throwable cause) { - super(AMQConstant.INVALID_ARGUMENT, message, cause); + super(AMQConstant.ARGUMENT_INVALID, message, cause); } public boolean isHardError() diff --git a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java index f9f6ca8444..14d1befaf1 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/protocol/AMQConstant.java @@ -104,7 +104,7 @@ public final class AMQConstant public static final AMQConstant REQUEST_TIMEOUT = new AMQConstant(408, "Request Timeout", true); - public static final AMQConstant INVALID_ARGUMENT = new AMQConstant(409, "argument invalid", true); + public static final AMQConstant ARGUMENT_INVALID = new AMQConstant(409, "argument invalid", true); /** * The client sent a malformed frame that the server could not decode. This strongly implies a programming error @@ -153,10 +153,7 @@ public final class AMQConstant public static final AMQConstant FRAME_MIN_SIZE = new AMQConstant(4096, "frame min size", true); - /** - * The server does not support the protocol version - */ - public static final AMQConstant UNSUPPORTED_BROKER_PROTOCOL_ERROR = new AMQConstant(542, "broker unsupported protocol", true); + public static final AMQConstant INVALID_ARGUMENT = new AMQConstant(542, "invalid argument", true); /** * The client imp does not support the protocol version */ diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java index 347bf8e649..1c521244d0 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Connection.java @@ -485,26 +485,12 @@ public class Connection extends ConnectionInvoker { synchronized (lock) { - List <Binary> transactedSessions = new ArrayList(); for (Session ssn : sessions.values()) { - if (ssn.isTransacted()) - { - transactedSessions.add(ssn.getName()); - ssn.setState(Session.State.CLOSED); - } - else - { - map(ssn); - ssn.attach(); - ssn.resume(); - } + map(ssn); + ssn.resume(); } - for (Binary ssn_name : transactedSessions) - { - sessions.remove(ssn_name); - } setState(OPEN); } } diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java index 556134f984..0de558d152 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/Session.java @@ -259,6 +259,8 @@ public class Session extends SessionInvoker { synchronized (commands) { + attach(); + for (int i = maxComplete + 1; lt(i, commandsOut); i++) { Method m = commands[mod(i, commands.length)]; @@ -299,11 +301,18 @@ public class Session extends SessionInvoker sessionCommandPoint(m.getId(), 0); send(m); } - + sessionCommandPoint(commandsOut, 0); + sessionFlush(COMPLETED); resumer = Thread.currentThread(); state = RESUMING; + + if(isTransacted()) + { + txSelect(); + } + listener.resumed(this); resumer = null; } @@ -567,17 +576,6 @@ public class Session extends SessionInvoker { if (m.getEncodedTrack() == Frame.L4) { - - if (state == DETACHED && transacted) - { - state = CLOSED; - delegate.closed(this); - connection.removeSession(this); - throw new SessionException( - "Session failed over, possibly in the middle of a transaction. " + - "Closing the session. Any Transaction in progress will be rolledback."); - } - if (m.hasPayload()) { acquireCredit(); @@ -585,24 +583,30 @@ public class Session extends SessionInvoker synchronized (commands) { - if (state == DETACHED && m.isUnreliable()) + //allow the txSelect operation to be invoked during resume + boolean skipWait = m instanceof TxSelect && state == RESUMING; + + if(!skipWait) { - Thread current = Thread.currentThread(); - if (!current.equals(resumer)) + if (state == DETACHED && m.isUnreliable()) { - return; + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) + { + return; + } } - } - if (state != OPEN && state != CLOSED && state != CLOSING) - { - Thread current = Thread.currentThread(); - if (!current.equals(resumer)) + if (state != OPEN && state != CLOSED && state != CLOSING) { - Waiter w = new Waiter(commands, timeout); - while (w.hasTime() && (state != OPEN && state != CLOSED)) + Thread current = Thread.currentThread(); + if (!current.equals(resumer)) { - w.await(); + Waiter w = new Waiter(commands, timeout); + while (w.hasTime() && (state != OPEN && state != CLOSED)) + { + w.await(); + } } } } diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java index 52cc6363a9..bfc77539ce 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoNetworkConnection.java @@ -54,8 +54,8 @@ public class IoNetworkConnection implements NetworkConnection public void start() { - _ioReceiver.initiate(); _ioSender.initiate(); + _ioReceiver.initiate(); } public Sender<ByteBuffer> getSender() diff --git a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java index 473d4d95ff..427487c879 100644 --- a/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java +++ b/qpid/java/common/src/main/java/org/apache/qpid/transport/network/io/IoSender.java @@ -113,6 +113,10 @@ public final class IoSender implements Runnable, Sender<ByteBuffer> { throw new SenderClosedException("sender is closed", exception); } + if(!senderThread.isAlive()) + { + throw new SenderException("sender thread not alive"); + } final int size = buffer.length; int remaining = buf.remaining(); diff --git a/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java index bde8bc68ad..e69f95f916 100644 --- a/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java +++ b/qpid/java/common/src/test/java/org/apache/qpid/test/utils/QpidTestCase.java @@ -65,7 +65,7 @@ public class QpidTestCase extends TestCase String exclusionListString = System.getProperties().getProperty("test.excludelist", ""); List<String> exclusionList = new ArrayList<String>(); - for (String uri : exclusionListURIs.split("\\s+")) + for (String uri : exclusionListURIs.split(";\\s*")) { File file = new File(uri); if (file.exists()) @@ -87,6 +87,10 @@ public class QpidTestCase extends TestCase _logger.warn("Exception when reading exclusion list", e); } } + else + { + _logger.info("Specified exclude file does not exist: " + uri); + } } if (!exclusionListString.equals("")) diff --git a/qpid/java/lib/bdbstore/README.txt b/qpid/java/lib/bdbstore/README.txt new file mode 100644 index 0000000000..80adb199bf --- /dev/null +++ b/qpid/java/lib/bdbstore/README.txt @@ -0,0 +1,14 @@ +The BDB JE jar must be downloaded into this directory in order to allow the optional bdbstore module to be built against it. + +*NOTE* The BDB JE library is licensed under the Sleepycat Licence [1], which is not compatible with the Apache Lience v2.0. As a result, the BDB JE library is not distributed with the project, and the optional bdbstore module is not compiled by default. + +The jar file may be downloaded by either: + + Seperately running the following command from the qpid/java/bdbstore dir: ant download-bdb + + OR + + Adding -Ddownload-bdb=true to your regular build command + + +[1] http://www.oracle.com/technetwork/database/berkeleydb/downloads/jeoslicense-086837.html diff --git a/qpid/java/module.xml b/qpid/java/module.xml index 050382ee80..349fb91e4a 100644 --- a/qpid/java/module.xml +++ b/qpid/java/module.xml @@ -207,7 +207,6 @@ <mkdir dir="${build.etc}"/> <mkdir dir="${build.lib}"/> <mkdir dir="${build.results}"/> - <mkdir dir="${build.data}"/> <mkdir dir="${build.plugins}"/> <mkdir dir="${module.classes}"/> <mkdir dir="${module.precompiled}"/> @@ -317,7 +316,7 @@ <property file="${build.scratch}/test-${profile}.properties"/> <map property="test.excludefiles" value="${test.excludes}"> - <globmapper from="*" to="${test.profiles}/*"/> + <globmapper from="*" to="${test.profiles}/*;"/> </map> <condition property="dontruntest" value="dontruntest" else="runtest"> @@ -335,7 +334,8 @@ <echo message="Using profile:${profile}" level="info"/> <junit fork="yes" forkmode="once" maxmemory="${test.mem}" reloading="no" haltonfailure="${haltonfailure}" haltonerror="${haltonerror}" - failureproperty="test.failures" printsummary="on" timeout="6000000" > + failureproperty="test.failures" printsummary="on" timeout="6000000" + dir="${project.root}" > <jvmarg line="${jvm.args}" /> @@ -660,7 +660,8 @@ <mkdir dir="${build.coveragereport}" /> <junit fork="yes" forkmode="once" maxmemory="${test.mem}" reloading="no" haltonfailure="${haltonfailure}" haltonerror="${haltonerror}" - failureproperty="test.failures" printsummary="on" timeout="600000" > + failureproperty="test.failures" printsummary="on" timeout="600000" + dir="${project.root}" > <sysproperty key="amqj.logging.level" value="${amqj.logging.level}"/> <sysproperty key="amqj.protocol.logging.level" value="${amqj.protocol.logging.level}"/> @@ -670,7 +671,6 @@ <sysproperty key="java.naming.factory.initial" value="${java.naming.factory.initial}"/> <sysproperty key="java.naming.provider.url" value="${java.naming.provider.url}"/> <sysproperty key="broker" value="${broker}"/> - <sysproperty key="broker.clean" value="${broker.clean}"/> <sysproperty key="broker.version" value="${broker.version}"/> <sysproperty key="broker.ready" value="${broker.ready}" /> <sysproperty key="test.output" value="${module.results}"/> diff --git a/qpid/java/systests/build.xml b/qpid/java/systests/build.xml index 33ad2227bb..fb2bae1d47 100644 --- a/qpid/java/systests/build.xml +++ b/qpid/java/systests/build.xml @@ -19,7 +19,15 @@ nn - or more contributor license agreements. See the NOTICE file - --> <project name="System Tests" default="build"> - <property name="module.depends" value="client management/tools/qpid-cli management/common broker broker/test common common/test junit-toolkit"/> + + <condition property="systests.optional.depends" value="bdbstore" else=""> + <and> + <contains string="${modules.opt}" substring="bdbstore"/> + <contains string="${profile}" substring="bdb"/> + </and> + </condition> + + <property name="module.depends" value="client management/common broker broker/test common common/test junit-toolkit ${systests.optional.depends}"/> <property name="module.test.src" location="src/main/java"/> <property name="module.test.excludes" value="**/DropInTest.java,**/TestClientControlledTest.java"/> diff --git a/qpid/java/systests/etc/config-systests-aclv2.xml b/qpid/java/systests/etc/config-systests-aclv2.xml index 33563e7891..e8b971a2a0 100644 --- a/qpid/java/systests/etc/config-systests-aclv2.xml +++ b/qpid/java/systests/etc/config-systests-aclv2.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.config}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.config}" optional="true"/> <xml fileName="${QPID_HOME}/etc/config-systests-aclv2-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config-systests-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config.xml"/> diff --git a/qpid/java/systests/etc/config-systests-bdb-settings.xml b/qpid/java/systests/etc/config-systests-bdb-settings.xml new file mode 100644 index 0000000000..4fa69d0abc --- /dev/null +++ b/qpid/java/systests/etc/config-systests-bdb-settings.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + - + - 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. + - + --> +<broker> + <virtualhosts>${QPID_HOME}/etc/virtualhosts-systests-bdb.xml</virtualhosts> +</broker> + + diff --git a/qpid/java/systests/etc/config-systests-bdb.xml b/qpid/java/systests/etc/config-systests-bdb.xml new file mode 100644 index 0000000000..9364006fcc --- /dev/null +++ b/qpid/java/systests/etc/config-systests-bdb.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + - + - 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. + - + --> +<configuration> + <system/> + <override> + <xml fileName="${QPID_HOME}/${test.config}" optional="true"/> + <xml fileName="${QPID_HOME}/etc/config-systests-bdb-settings.xml"/> + <xml fileName="${QPID_HOME}/etc/config-systests-settings.xml"/> + <xml fileName="${QPID_HOME}/etc/config.xml"/> + </override> +</configuration> diff --git a/qpid/java/systests/etc/config-systests-derby.xml b/qpid/java/systests/etc/config-systests-derby.xml index ba27a0c020..303154d8f0 100644 --- a/qpid/java/systests/etc/config-systests-derby.xml +++ b/qpid/java/systests/etc/config-systests-derby.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.config}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.config}" optional="true"/> <xml fileName="${QPID_HOME}/etc/config-systests-derby-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config-systests-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config.xml"/> diff --git a/qpid/java/systests/etc/config-systests-firewall.xml b/qpid/java/systests/etc/config-systests-firewall.xml index c0ce71210f..c73ac6a687 100644 --- a/qpid/java/systests/etc/config-systests-firewall.xml +++ b/qpid/java/systests/etc/config-systests-firewall.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.config}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.config}" optional="true"/> <xml fileName="${QPID_FIREWALL_CONFIG_SETTINGS}" optional="true"/> <xml fileName="${QPID_HOME}/etc/config-systests-firewall-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config-systests-settings.xml"/> diff --git a/qpid/java/systests/etc/config-systests.xml b/qpid/java/systests/etc/config-systests.xml index 5d7d878e76..0e8f2803e3 100644 --- a/qpid/java/systests/etc/config-systests.xml +++ b/qpid/java/systests/etc/config-systests.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.config}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.config}" optional="true"/> <xml fileName="${QPID_HOME}/etc/config-systests-settings.xml"/> <xml fileName="${QPID_HOME}/etc/config.xml"/> </override> diff --git a/qpid/java/systests/etc/virtualhosts-systests-aclv2.xml b/qpid/java/systests/etc/virtualhosts-systests-aclv2.xml index eb96577487..db396d7ab1 100644 --- a/qpid/java/systests/etc/virtualhosts-systests-aclv2.xml +++ b/qpid/java/systests/etc/virtualhosts-systests-aclv2.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.virtualhosts}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.virtualhosts}" optional="true"/> <xml fileName="${QPID_HOME}/etc/virtualhosts-systests-aclv2-settings.xml"/> <xml fileName="${QPID_HOME}/etc/virtualhosts.xml"/> </override> diff --git a/qpid/java/systests/etc/virtualhosts-systests-bdb-settings.xml b/qpid/java/systests/etc/virtualhosts-systests-bdb-settings.xml new file mode 100644 index 0000000000..ce16523f13 --- /dev/null +++ b/qpid/java/systests/etc/virtualhosts-systests-bdb-settings.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + - + - 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. + - + --> +<virtualhosts> + <work>${QPID_WORK}</work> + + <virtualhost> + <name>localhost</name> + <localhost> + <store> + <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class> + <environment-path>${work}/bdbstore/localhost-store</environment-path> + </store> + </localhost> + </virtualhost> + + <virtualhost> + <name>development</name> + <development> + <store> + <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class> + <environment-path>${work}/bdbstore/development-store</environment-path> + </store> + </development> + </virtualhost> + + <virtualhost> + <name>test</name> + <test> + <store> + <class>org.apache.qpid.server.store.berkeleydb.BDBMessageStore</class> + <environment-path>${work}/bdbstore/test-store</environment-path> + </store> + </test> + </virtualhost> +</virtualhosts> + + diff --git a/qpid/java/systests/etc/virtualhosts-systests-bdb.xml b/qpid/java/systests/etc/virtualhosts-systests-bdb.xml new file mode 100644 index 0000000000..367fee65ac --- /dev/null +++ b/qpid/java/systests/etc/virtualhosts-systests-bdb.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + - + - 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. + - + --> +<configuration> + <system/> + <override> + <xml fileName="${QPID_HOME}/${test.virtualhosts}" optional="true"/> + <xml fileName="${QPID_HOME}/etc/virtualhosts-systests-bdb-settings.xml"/> + <xml fileName="${QPID_HOME}/etc/virtualhosts.xml"/> + </override> +</configuration> diff --git a/qpid/java/systests/etc/virtualhosts-systests-derby.xml b/qpid/java/systests/etc/virtualhosts-systests-derby.xml index 171be37416..3745100e1f 100644 --- a/qpid/java/systests/etc/virtualhosts-systests-derby.xml +++ b/qpid/java/systests/etc/virtualhosts-systests-derby.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.virtualhosts}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.virtualhosts}" optional="true"/> <xml fileName="${QPID_HOME}/etc/virtualhosts-systests-derby-settings.xml"/> <xml fileName="${QPID_HOME}/etc/virtualhosts.xml"/> </override> diff --git a/qpid/java/systests/etc/virtualhosts-systests-firewall.xml b/qpid/java/systests/etc/virtualhosts-systests-firewall.xml index 51ab6739b3..c5c6a86d7c 100644 --- a/qpid/java/systests/etc/virtualhosts-systests-firewall.xml +++ b/qpid/java/systests/etc/virtualhosts-systests-firewall.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.virtualhosts}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.virtualhosts}" optional="true"/> <xml fileName="${QPID_FIREWALL_VIRTUALHOSTS_SETTINGS}" optional="true"/> <xml fileName="${QPID_HOME}/etc/virtualhosts.xml"/> </override> diff --git a/qpid/java/systests/etc/virtualhosts-systests.xml b/qpid/java/systests/etc/virtualhosts-systests.xml index 71f1cc9889..d6aeefac72 100644 --- a/qpid/java/systests/etc/virtualhosts-systests.xml +++ b/qpid/java/systests/etc/virtualhosts-systests.xml @@ -22,7 +22,7 @@ <configuration> <system/> <override> - <xml fileName="${test.virtualhosts}" optional="true"/> + <xml fileName="${QPID_HOME}/${test.virtualhosts}" optional="true"/> <xml fileName="${QPID_HOME}/etc/virtualhosts.xml"/> </override> </configuration> diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/failover/FailoverBehaviourTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/failover/FailoverBehaviourTest.java new file mode 100644 index 0000000000..aa62106c46 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/failover/FailoverBehaviourTest.java @@ -0,0 +1,924 @@ +/* + * 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.client.failover; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.jms.Connection; +import javax.jms.Destination; +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.jms.TransactionRolledBackException; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.test.utils.FailoverBaseCase; + +/** + * Test suite to test all possible failover corner cases + */ +public class FailoverBehaviourTest extends FailoverBaseCase implements ConnectionListener, ExceptionListener +{ + private static final String TEST_MESSAGE_FORMAT = "test message {0}"; + + /** Indicates whether tests are run against clustered broker */ + private static boolean CLUSTERED = Boolean.getBoolean("profile.clustered"); + + /** Default number of messages to send before failover */ + private static final int DEFAULT_NUMBER_OF_MESSAGES = 10; + + /** Actual number of messages to send before failover */ + protected int _messageNumber = Integer.getInteger("profile.failoverMsgCount", DEFAULT_NUMBER_OF_MESSAGES); + + /** Test connection */ + protected Connection _connection; + + /** + * Failover completion latch is used to wait till connectivity to broker is + * restored + */ + private CountDownLatch _failoverComplete; + + /** + * Consumer session + */ + private Session _consumerSession; + + /** + * Test destination + */ + private Destination _destination; + + /** + * Consumer + */ + private MessageConsumer _consumer; + + /** + * Producer session + */ + private Session _producerSession; + + /** + * Producer + */ + private MessageProducer _producer; + + /** + * Holds exception sent into {@link ExceptionListener} on failover + */ + private JMSException _exceptionListenerException; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _connection = getConnection(); + _connection.setExceptionListener(this); + ((AMQConnection) _connection).setConnectionListener(this); + _failoverComplete = new CountDownLatch(1); + } + + /** + * Test whether MessageProducer can successfully publish messages after + * failover and rollback transaction + */ + public void testMessageProducingAndRollbackAfterFailover() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + produceMessages(); + causeFailure(); + + assertFailoverException(); + // producer should be able to send messages after failover + _producer.send(_producerSession.createTextMessage("test message " + _messageNumber)); + + // rollback after failover + _producerSession.rollback(); + + // tests whether sending and committing is working after failover + produceMessages(); + _producerSession.commit(); + + // tests whether receiving and committing is working after failover + consumeMessages(); + _consumerSession.commit(); + } + + /** + * Test whether {@link TransactionRolledBackException} is thrown on commit + * of dirty transacted session after failover. + * <p> + * Verifies whether second after failover commit is successful. + */ + public void testTransactionRolledBackExceptionThrownOnCommitAfterFailoverOnProducingMessages() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + produceMessages(); + causeFailure(); + + assertFailoverException(); + + // producer should be able to send messages after failover + _producer.send(_producerSession.createTextMessage("test message " + _messageNumber)); + + try + { + _producerSession.commit(); + fail("TransactionRolledBackException is expected on commit after failover with dirty session!"); + } + catch (JMSException t) + { + assertTrue("Expected TransactionRolledBackException but thrown " + t, + t instanceof TransactionRolledBackException); + } + + // simulate process of user replaying the transaction + produceMessages("replayed test message {0}", _messageNumber, false); + + // no exception should be thrown + _producerSession.commit(); + + // only messages sent after rollback should be received + consumeMessages("replayed test message {0}", _messageNumber); + + // no exception should be thrown + _consumerSession.commit(); + } + + /** + * Tests JMSException is not thrown on commit with a clean session after + * failover + */ + public void testNoJMSExceptionThrownOnCommitAfterFailoverWithCleanProducerSession() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + + causeFailure(); + + assertFailoverException(); + + // should not throw an exception for a clean session + _producerSession.commit(); + + // tests whether sending and committing is working after failover + produceMessages(); + _producerSession.commit(); + + // tests whether receiving and committing is working after failover + consumeMessages(); + _consumerSession.commit(); + } + + /** + * Tests {@link TransactionRolledBackException} is thrown on commit of dirty + * transacted session after failover. + * <p> + * Verifies whether second after failover commit is successful. + */ + public void testTransactionRolledBackExceptionThrownOnCommitAfterFailoverOnMessageReceiving() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + produceMessages(); + _producerSession.commit(); + + // receive messages but do not commit + consumeMessages(); + + causeFailure(); + + assertFailoverException(); + + try + { + // should throw TransactionRolledBackException + _consumerSession.commit(); + fail("TransactionRolledBackException is expected on commit after failover"); + } + catch (Exception t) + { + assertTrue("Expected TransactionRolledBackException but thrown " + t, + t instanceof TransactionRolledBackException); + } + + resendMessagesIfNecessary(); + + // consume messages successfully + consumeMessages(); + _consumerSession.commit(); + } + + /** + * Tests JMSException is not thrown on commit with a clean session after failover + */ + public void testNoJMSExceptionThrownOnCommitAfterFailoverWithCleanConsumerSession() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + produceMessages(); + _producerSession.commit(); + + consumeMessages(); + _consumerSession.commit(); + + causeFailure(); + + assertFailoverException(); + + // should not throw an exception with a clean consumer session + _consumerSession.commit(); + } + + /** + * Test that TransactionRolledBackException is thrown on commit of + * dirty session in asynchronous consumer after failover. + */ + public void testTransactionRolledBackExceptionThrownOnCommitAfterFailoverOnReceivingMessagesAsynchronously() + throws Exception + { + init(Session.SESSION_TRANSACTED, false); + FailoverTestMessageListener ml = new FailoverTestMessageListener(); + _consumer.setMessageListener(ml); + + _connection.start(); + + produceMessages(); + _producerSession.commit(); + + // wait for message receiving + ml.awaitForEnd(); + + assertEquals("Received unexpected number of messages!", _messageNumber, ml.getMessageCounter()); + + // assert messages + int counter = 0; + for (Message message : ml.getReceivedMessages()) + { + assertReceivedMessage(message, TEST_MESSAGE_FORMAT, counter++); + } + ml.reset(); + + causeFailure(); + assertFailoverException(); + + + try + { + _consumerSession.commit(); + fail("TransactionRolledBackException should be thrown!"); + } + catch (TransactionRolledBackException e) + { + // that is what is expected + } + + resendMessagesIfNecessary(); + + // wait for message receiving + ml.awaitForEnd(); + + assertEquals("Received unexpected number of messages!", _messageNumber, ml.getMessageCounter()); + + // assert messages + counter = 0; + for (Message message : ml.getReceivedMessages()) + { + assertReceivedMessage(message, TEST_MESSAGE_FORMAT, counter++); + } + + // commit again. It should be successful + _consumerSession.commit(); + } + + /** + * Test that {@link Session#rollback()} does not throw exception after failover + * and that we are able to consume messages. + */ + public void testRollbackAfterFailover() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + + produceMessages(); + _producerSession.commit(); + + consumeMessages(); + + causeFailure(); + + assertFailoverException(); + + _consumerSession.rollback(); + + resendMessagesIfNecessary(); + + // tests whether receiving and committing is working after failover + consumeMessages(); + _consumerSession.commit(); + } + + /** + * Test that {@link Session#rollback()} does not throw exception after receiving further messages + * after failover, and we can receive published messages after rollback. + */ + public void testRollbackAfterReceivingAfterFailover() throws Exception + { + init(Session.SESSION_TRANSACTED, true); + + produceMessages(); + _producerSession.commit(); + + consumeMessages(); + causeFailure(); + + assertFailoverException(); + + resendMessagesIfNecessary(); + + consumeMessages(); + + _consumerSession.rollback(); + + // tests whether receiving and committing is working after failover + consumeMessages(); + _consumerSession.commit(); + } + + /** + * Test that {@link Session#recover()} does not throw an exception after failover + * and that we can consume messages after recover. + */ + public void testRecoverAfterFailover() throws Exception + { + init(Session.CLIENT_ACKNOWLEDGE, true); + + produceMessages(); + + // consume messages but do not acknowledge them + consumeMessages(); + + causeFailure(); + + assertFailoverException(); + + _consumerSession.recover(); + + resendMessagesIfNecessary(); + + // tests whether receiving and acknowledgment is working after recover + Message lastMessage = consumeMessages(); + lastMessage.acknowledge(); + } + + /** + * Test that receiving more messages after failover and then calling + * {@link Session#recover()} does not throw an exception + * and that we can consume messages after recover. + */ + public void testRecoverWithConsumedMessagesAfterFailover() throws Exception + { + init(Session.CLIENT_ACKNOWLEDGE, true); + + produceMessages(); + + // consume messages but do not acknowledge them + consumeMessages(); + + causeFailure(); + + assertFailoverException(); + + // publishing should work after failover + resendMessagesIfNecessary(); + + // consume messages again on a dirty session + consumeMessages(); + + // recover should successfully restore session + _consumerSession.recover(); + + // tests whether receiving and acknowledgment is working after recover + Message lastMessage = consumeMessages(); + lastMessage.acknowledge(); + } + + /** + * Test that first call to {@link Message#acknowledge()} after failover + * throws a JMSEXception if session is dirty. + */ + public void testAcknowledgeAfterFailover() throws Exception + { + init(Session.CLIENT_ACKNOWLEDGE, true); + + produceMessages(); + + // consume messages but do not acknowledge them + Message lastMessage = consumeMessages(); + causeFailure(); + + assertFailoverException(); + + try + { + // an implicit recover performed when acknowledge throws an exception due to failover + lastMessage.acknowledge(); + fail("JMSException should be thrown"); + } + catch (JMSException t) + { + // TODO: assert error code and/or expected exception type + } + + resendMessagesIfNecessary(); + + // tests whether receiving and acknowledgment is working after recover + lastMessage = consumeMessages(); + lastMessage.acknowledge(); + } + + /** + * Test that receiving of messages after failover prior to calling + * {@link Message#acknowledge()} still results in acknowledge throwing an exception. + */ + public void testAcknowledgeAfterMessageReceivingAfterFailover() throws Exception + { + init(Session.CLIENT_ACKNOWLEDGE, true); + + produceMessages(); + + // consume messages but do not acknowledge them + consumeMessages(); + causeFailure(); + + assertFailoverException(); + + resendMessagesIfNecessary(); + + // consume again on dirty session + Message lastMessage = consumeMessages(); + try + { + // an implicit recover performed when acknowledge throws an exception due to failover + lastMessage.acknowledge(); + fail("JMSException should be thrown"); + } + catch (JMSException t) + { + // TODO: assert error code and/or expected exception type + } + + // tests whether receiving and acknowledgment is working on a clean session + lastMessage = consumeMessages(); + lastMessage.acknowledge(); + } + + /** + * Tests that call to {@link Message#acknowledge()} after failover throws an exception in asynchronous consumer + * and we can consume messages after acknowledge. + */ + public void testAcknowledgeAfterFailoverForAsynchronousConsumer() throws Exception + { + init(Session.CLIENT_ACKNOWLEDGE, false); + FailoverTestMessageListener ml = new FailoverTestMessageListener(); + _consumer.setMessageListener(ml); + _connection.start(); + + produceMessages(); + + // wait for message receiving + ml.awaitForEnd(); + + assertEquals("Received unexpected number of messages!", _messageNumber, ml.getMessageCounter()); + + // assert messages + int counter = 0; + Message currentMessage = null; + for (Message message : ml.getReceivedMessages()) + { + assertReceivedMessage(message, TEST_MESSAGE_FORMAT, counter++); + currentMessage = message; + } + ml.reset(); + + causeFailure(); + assertFailoverException(); + + + try + { + currentMessage.acknowledge(); + fail("JMSException should be thrown!"); + } + catch (JMSException e) + { + // TODO: assert error code and/or expected exception type + } + + resendMessagesIfNecessary(); + + // wait for message receiving + ml.awaitForEnd(); + + assertEquals("Received unexpected number of messages!", _messageNumber, ml.getMessageCounter()); + + // assert messages + counter = 0; + for (Message message : ml.getReceivedMessages()) + { + assertReceivedMessage(message, TEST_MESSAGE_FORMAT, counter++); + currentMessage = message; + } + + // acknowledge again. It should be successful + currentMessage.acknowledge(); + } + + /** + * Test whether {@link Session#recover()} works as expected after failover + * in AA mode. + */ + public void testRecoverAfterFailoverInAutoAcknowledgeMode() throws Exception + { + init(Session.AUTO_ACKNOWLEDGE, true); + + produceMessages(); + + // receive first message in order to start a dispatcher thread + Message receivedMessage = _consumer.receive(1000l); + assertReceivedMessage(receivedMessage, TEST_MESSAGE_FORMAT, 0); + + causeFailure(); + + assertFailoverException(); + + _consumerSession.recover(); + + resendMessagesIfNecessary(); + + // tests whether receiving is working after recover + consumeMessages(); + } + + public void testClientAcknowledgedSessionCloseAfterFailover() throws Exception + { + sessionCloseAfterFailoverImpl(Session.CLIENT_ACKNOWLEDGE); + } + + public void testTransactedSessionCloseAfterFailover() throws Exception + { + sessionCloseAfterFailoverImpl(Session.SESSION_TRANSACTED); + } + + public void testAutoAcknowledgedSessionCloseAfterFailover() throws Exception + { + sessionCloseAfterFailoverImpl(Session.AUTO_ACKNOWLEDGE); + } + + /** + * Tests {@link Session#close()} for session with given acknowledge mode + * to ensure that close works after failover. + * + * @param acknowledgeMode session acknowledge mode + * @throws JMSException + */ + private void sessionCloseAfterFailoverImpl(int acknowledgeMode) throws JMSException + { + init(acknowledgeMode, true); + produceMessages(TEST_MESSAGE_FORMAT, _messageNumber, false); + if (acknowledgeMode == Session.SESSION_TRANSACTED) + { + _producerSession.commit(); + } + + // intentionally receive message but do not commit or acknowledge it in + // case of transacted or CLIENT_ACK session + Message receivedMessage = _consumer.receive(1000l); + assertReceivedMessage(receivedMessage, TEST_MESSAGE_FORMAT, 0); + + causeFailure(); + + assertFailoverException(); + + // for transacted/client_ack session + // no exception should be thrown but transaction should be automatically + // rolled back + _consumerSession.close(); + } + + /** + * A helper method to instantiate produce and consumer sessions, producer + * and consumer. + * + * @param acknowledgeMode + * acknowledge mode + * @param startConnection + * indicates whether connection should be started + * @throws JMSException + */ + private void init(int acknowledgeMode, boolean startConnection) throws JMSException + { + boolean isTransacted = acknowledgeMode == Session.SESSION_TRANSACTED ? true : false; + + _consumerSession = _connection.createSession(isTransacted, acknowledgeMode); + _destination = _consumerSession.createQueue(getTestQueueName() + "_" + System.currentTimeMillis()); + _consumer = _consumerSession.createConsumer(_destination); + + if (startConnection) + { + _connection.start(); + } + + _producerSession = _connection.createSession(isTransacted, acknowledgeMode); + _producer = _producerSession.createProducer(_destination); + + } + + /** + * Resends messages if reconnected to a non-clustered broker + * + * @throws JMSException + */ + private void resendMessagesIfNecessary() throws JMSException + { + if (!CLUSTERED) + { + // assert that a new broker does not have messages on a queue + if (_consumer.getMessageListener() == null) + { + Message message = _consumer.receive(100l); + assertNull("Received a message after failover with non-clustered broker!", message); + } + // re-sending messages if reconnected to a non-clustered broker + produceMessages(true); + } + } + + /** + * Produces a default number of messages with default text content into test + * queue + * + * @throws JMSException + */ + private void produceMessages() throws JMSException + { + produceMessages(false); + } + + private void produceMessages(boolean seperateProducer) throws JMSException + { + produceMessages(TEST_MESSAGE_FORMAT, _messageNumber, seperateProducer); + } + + /** + * Consumes a default number of messages and asserts their content. + * + * @return last consumed message + * @throws JMSException + */ + private Message consumeMessages() throws JMSException + { + return consumeMessages(TEST_MESSAGE_FORMAT, _messageNumber); + } + + /** + * Produces given number of text messages with content matching given + * content pattern + * + * @param messagePattern message content pattern + * @param messageNumber number of messages to send + * @param standaloneProducer whether to use the existing producer or a new one. + * @throws JMSException + */ + private void produceMessages(String messagePattern, int messageNumber, boolean standaloneProducer) throws JMSException + { + Session producerSession; + MessageProducer producer; + + if(standaloneProducer) + { + producerSession = _connection.createSession(true, Session.SESSION_TRANSACTED); + producer = producerSession.createProducer(_destination); + } + else + { + producerSession = _producerSession; + producer = _producer; + } + + for (int i = 0; i < messageNumber; i++) + { + String text = MessageFormat.format(messagePattern, i); + Message message = producerSession.createTextMessage(text); + producer.send(message); + } + + if(standaloneProducer) + { + producerSession.commit(); + } + } + + /** + * Consumes given number of text messages and asserts that their content + * matches given pattern + * + * @param messagePattern + * messages content pattern + * @param messageNumber + * message number to received + * @return last consumed message + * @throws JMSException + */ + private Message consumeMessages(String messagePattern, int messageNumber) throws JMSException + { + Message receivedMesssage = null; + for (int i = 0; i < messageNumber; i++) + { + receivedMesssage = _consumer.receive(1000l); + assertReceivedMessage(receivedMesssage, messagePattern, i); + } + return receivedMesssage; + } + + /** + * Asserts received message + * + * @param receivedMessage + * received message + * @param messagePattern + * messages content pattern + * @param messageIndex + * message index + */ + private void assertReceivedMessage(Message receivedMessage, String messagePattern, int messageIndex) + { + assertNotNull("Expected message [" + messageIndex + "] is not received!", receivedMessage); + assertTrue("Failure to receive message [" + messageIndex + "], expected TextMessage but received " + + receivedMessage, receivedMessage instanceof TextMessage); + String expectedText = MessageFormat.format(messagePattern, messageIndex); + String receivedText = null; + try + { + receivedText = ((TextMessage) receivedMessage).getText(); + } + catch (JMSException e) + { + fail("JMSException occured while getting message text:" + e.getMessage()); + } + assertEquals("Failover is broken! Expected [" + expectedText + "] but got [" + receivedText + "]", + expectedText, receivedText); + } + + /** + * Causes failover and waits till connection is re-established. + */ + private void causeFailure() + { + causeFailure(getFailingPort(), DEFAULT_FAILOVER_TIME * 2); + } + + /** + * Causes failover by stopping broker on given port and waits till + * connection is re-established during given time interval. + * + * @param port + * broker port + * @param delay + * time interval to wait for connection re-establishement + */ + private void causeFailure(int port, long delay) + { + failBroker(port); + + awaitForFailoverCompletion(delay); + } + + private void awaitForFailoverCompletion(long delay) + { + _logger.info("Awaiting Failover completion.."); + try + { + if (!_failoverComplete.await(delay, TimeUnit.MILLISECONDS)) + { + fail("Failover did not complete"); + } + } + catch (InterruptedException e) + { + fail("Test was interrupted:" + e.getMessage()); + } + } + + private void assertFailoverException() + { + // TODO: assert exception is received (once implemented) + // along with error code and/or expected exception type + } + + @Override + public void bytesSent(long count) + { + } + + @Override + public void bytesReceived(long count) + { + } + + @Override + public boolean preFailover(boolean redirect) + { + return true; + } + + @Override + public boolean preResubscribe() + { + return true; + } + + @Override + public void failoverComplete() + { + _failoverComplete.countDown(); + } + + @Override + public void onException(JMSException e) + { + _exceptionListenerException = e; + } + + private class FailoverTestMessageListener implements MessageListener + { + // message counter + private AtomicInteger _counter = new AtomicInteger(); + + private List<Message> _receivedMessage = new ArrayList<Message>(); + + private volatile CountDownLatch _endLatch; + + public FailoverTestMessageListener() throws JMSException + { + _endLatch = new CountDownLatch(1); + } + + @Override + public void onMessage(Message message) + { + _receivedMessage.add(message); + if (_counter.incrementAndGet() % _messageNumber == 0) + { + _endLatch.countDown(); + } + } + + public void reset() + { + _receivedMessage.clear(); + _endLatch = new CountDownLatch(1); + _counter.set(0); + } + + public List<Message> getReceivedMessages() + { + return _receivedMessage; + } + + public Object awaitForEnd() throws InterruptedException + { + return _endLatch.await((long) _messageNumber, TimeUnit.SECONDS); + } + + public int getMessageCounter() + { + return _counter.get(); + } + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java index 471ebb16fc..d754979ab9 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java @@ -32,9 +32,9 @@ import org.apache.qpid.transport.Connection; public class SSLTest extends QpidBrokerTestCase { - private static final String KEYSTORE = TEST_RESOURCES_DIR + "/ssl/java_client_keystore.jks"; + private static final String KEYSTORE = "test-profiles/test_resources/ssl/java_client_keystore.jks"; private static final String KEYSTORE_PASSWORD = "password"; - private static final String TRUSTSTORE = TEST_RESOURCES_DIR + "/ssl/java_client_truststore.jks"; + private static final String TRUSTSTORE = "test-profiles/test_resources/ssl/java_client_truststore.jks"; private static final String TRUSTSTORE_PASSWORD = "password"; private static final String CERT_ALIAS_APP1 = "app1"; private static final String CERT_ALIAS_APP2 = "app2"; diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java index 2864d8e994..12a1682212 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java @@ -106,7 +106,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging }); //Remove the connection close from any 0-10 connections - _monitor.reset(); + _monitor.markDiscardPoint(); // Get a managedConnection ManagedConnection mangedConnection = _jmxUtils.getManagedObject(ManagedConnection.class, "org.apache.qpid:type=VirtualHost.Connection,*"); @@ -147,7 +147,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging */ public void testCreateExchangeDirectTransientViaManagementConsole() throws IOException, JMException { - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createExchange("test", getName(), "direct", false); @@ -171,7 +171,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testCreateExchangeTopicTransientViaManagementConsole() throws IOException, JMException { //Remove any previous exchange declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createExchange("test", getName(), "topic", false); @@ -196,7 +196,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testCreateExchangeFanoutTransientViaManagementConsole() throws IOException, JMException { //Remove any previous exchange declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createExchange("test", getName(), "fanout", false); @@ -221,7 +221,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testCreateExchangeHeadersTransientViaManagementConsole() throws IOException, JMException { //Remove any previous exchange declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createExchange("test", getName(), "headers", false); @@ -265,7 +265,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testCreateQueueTransientViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createQueue("test", getName(), null, false); @@ -308,7 +308,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testQueueDeleteViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createQueue("test", getName(), null, false); @@ -354,7 +354,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testBindingCreateOnDirectViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createQueue("test", getName(), null, false); @@ -381,7 +381,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testBindingCreateOnTopicViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createQueue("test", getName(), null, false); @@ -408,7 +408,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testBindingCreateOnFanoutViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createQueue("test", getName(), null, false); @@ -455,7 +455,7 @@ public class ManagementActorLoggingTest extends AbstractTestLogging public void testUnRegisterExchangeViaManagementConsole() throws IOException, JMException { //Remove any previous queue declares - _monitor.reset(); + _monitor.markDiscardPoint(); _jmxUtils.createExchange("test", getName(), "direct", false); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java index f56f428f0b..484c2afeb5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java @@ -349,7 +349,7 @@ public class AbstractTestLogging extends QpidBrokerTestCase public boolean waitForMessage(String message, long wait) throws FileNotFoundException, IOException { - return _monitor.waitForMessage(message, wait, true); + return _monitor.waitForMessage(message, wait); } /** diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java index a2487b49bf..aef98b8a2a 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java @@ -136,7 +136,7 @@ public class AlertingTest extends AbstractTestLogging stopBroker(); // Rest the monitoring clearing the current output file. - _monitor.reset(); + _monitor.markDiscardPoint(); startBroker(); wasAlertFired(); } @@ -169,7 +169,7 @@ public class AlertingTest extends AbstractTestLogging stopBroker(); - _monitor.reset(); + _monitor.markDiscardPoint(); // Change max message count to 5, start broker and make sure that that's triggered at the right time setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".queues.maximumMessageCount", "5"); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java index 97914f84a5..be2da128bc 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java @@ -55,7 +55,7 @@ public class BindingLoggingTest extends AbstractTestLogging { super.setUp(); //Ignore broker startup messages - _monitor.reset(); + _monitor.markDiscardPoint(); _connection = getConnection(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java index e901903eb4..7969ffc059 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java @@ -246,7 +246,7 @@ public class BrokerLoggingTest extends AbstractTestLogging if (isJavaBroker() && isExternalBroker()) { // Get custom -l value used during testing for the broker startup - String customLog4j = _brokerCommand.substring(_brokerCommand.indexOf("-l") + 2); + String customLog4j = _brokerCommand.substring(_brokerCommand.indexOf("-l") + 2).trim(); String TESTID = "BRK-1007"; diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java index 16c529316a..d45bde2d98 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java @@ -496,7 +496,7 @@ public class DerbyMessageStoreLoggingTest extends MemoryMessageStoreLoggingTest stopBroker(); // Clear our monitor - _monitor.reset(); + _monitor.markDiscardPoint(); startBroker(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java index 32adc49521..602bdb66b5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java @@ -58,7 +58,7 @@ public class DurableQueueLoggingTest extends AbstractTestLogging { super.setUp(); //Ensure we only have logs from our test - _monitor.reset(); + _monitor.markDiscardPoint(); _connection = getConnection(); _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java index b3c080b2e3..ec96f778f6 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java @@ -125,7 +125,7 @@ public class ExchangeLoggingTest extends AbstractTestLogging public void testExchangeCreate() throws JMSException, IOException { //Ignore broker startup messages - _monitor.reset(); + _monitor.markDiscardPoint(); _session.createConsumer(_queue); // Ensure we have received the EXH log msg. @@ -179,7 +179,7 @@ public class ExchangeLoggingTest extends AbstractTestLogging public void testExchangeDelete() throws Exception, IOException { //Ignore broker startup messages - _monitor.reset(); + _monitor.markDiscardPoint(); //create the exchange by creating a consumer _session.createConsumer(_queue); @@ -220,7 +220,7 @@ public class ExchangeLoggingTest extends AbstractTestLogging public void testDiscardedMessage() throws Exception { //Ignore broker startup messages - _monitor.reset(); + _monitor.markDiscardPoint(); if (!isBroker010()) { diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java index b8a42c0ab3..76ebda0ebd 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java @@ -53,7 +53,7 @@ public class QueueLoggingTest extends AbstractTestLogging { super.setUp(); //Remove broker startup logging messages - _monitor.reset(); + _monitor.markDiscardPoint(); _connection = getConnection(); _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java index 6e156f091e..b6efe53580 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java @@ -58,7 +58,7 @@ public class SubscriptionLoggingTest extends AbstractTestLogging { super.setUp(); //Remove broker startup logging messages - _monitor.reset(); + _monitor.markDiscardPoint(); _connection = getConnection(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java index f78b327209..a724e6c66e 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java @@ -64,7 +64,7 @@ public class ProducerFlowControlTest extends AbstractTestLogging _jmxUtilConnected=false; super.setUp(); - _monitor.reset(); + _monitor.markDiscardPoint(); producerConnection = getConnection(); producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java index 8aa5b6d9de..32b0185f88 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java @@ -83,14 +83,8 @@ public abstract class AbstractACLTestCase extends QpidBrokerTestCase implements @Override public void setUp() throws Exception { - if (QpidHome == null) - { - fail("QPID_HOME not set"); - } - // Initialise ACLs. - _configFile = new File(QpidHome, "etc" + File.separator + getConfig()); - + _configFile = new File("build" + File.separator + "etc" + File.separator + getConfig()); // Initialise ACL files for (String virtualHost : getHostList()) { @@ -156,7 +150,7 @@ public abstract class AbstractACLTestCase extends QpidBrokerTestCase implements */ public void setUpACLFile(String virtualHost) throws IOException, ConfigurationException { - String path = QpidHome + File.separator + "etc"; + String path = "build" + File.separator + "etc"; String className = StringUtils.substringBeforeLast(getClass().getSimpleName().toLowerCase(), "test"); String testName = StringUtils.substringAfter(getName(), "test").toLowerCase(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java index 782ca22965..254e1fe6ac 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java @@ -91,9 +91,9 @@ public class ExternalACLTest extends AbstractACLTestCase //send a message to each queue (also causing an exchange declare) MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null); ((org.apache.qpid.jms.MessageProducer) sender).send(namedQueue, sess.createTextMessage("test"), - DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false); ((org.apache.qpid.jms.MessageProducer) sender).send(tempQueue, sess.createTextMessage("test"), - DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false); //consume the messages from the queues consumer.receive(2000); @@ -309,7 +309,7 @@ public class ExternalACLTest extends AbstractACLTestCase // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not // queue existence. ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sess.createTextMessage("test"), - DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false); conn.close(); } @@ -337,7 +337,7 @@ public class ExternalACLTest extends AbstractACLTestCase // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not // queue existence. ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), - DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false); // Test the connection with a valid consumer // This may fail as the session may be closed before the queue or the consumer created. @@ -608,7 +608,7 @@ public class ExternalACLTest extends AbstractACLTestCase // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not // queue existence. ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), - DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false); // Test the connection with a valid consumer // This may not work as the session may be closed before the queue or consumer creation can occur. diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java index 2d99a44532..044a0af335 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java @@ -35,16 +35,8 @@ public class FirewallConfigTest extends QpidBrokerTestCase @Override protected void setUp() throws Exception { - // do setup - final String QPID_HOME = System.getProperty("QPID_HOME"); - - if (QPID_HOME == null) - { - fail("QPID_HOME not set"); - } - // Setup initial config file. - _configFile = new File(QPID_HOME, "etc/config-systests-firewall.xml"); + _configFile = new File("build/etc/config-systests-firewall.xml"); // Setup temporary config file _tmpConfig = File.createTempFile("config-systests-firewall", ".xml"); @@ -86,7 +78,7 @@ public class FirewallConfigTest extends QpidBrokerTestCase public void testVhostAllowBrokerDeny() throws Exception { - _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-2.xml"); + _configFile = new File("build/etc/config-systests-firewall-2.xml"); super.setUp(); @@ -119,7 +111,7 @@ public class FirewallConfigTest extends QpidBrokerTestCase public void testVhostDenyBrokerAllow() throws Exception { - _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-3.xml"); + _configFile = new File("build/etc/config-systests-firewall-3.xml"); super.setUp(); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java index b5bb74327e..8c3c247e2b 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java @@ -1116,10 +1116,10 @@ public class AddressBasedDestinationTest extends QpidBrokerTestCase public void testDestinationOnSend() throws Exception { Session ssn = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE); - MessageConsumer cons = ssn.createConsumer(ssn.createTopic("amq.topic/test")); + MessageConsumer cons = ssn.createConsumer(ssn.createTopic("ADDR:amq.topic/test")); MessageProducer prod = ssn.createProducer(null); - Queue queue = ssn.createQueue("amq.topic/test"); + Queue queue = ssn.createQueue("ADDR:amq.topic/test"); prod.send(queue,ssn.createTextMessage("A")); Message msg = cons.receive(1000); @@ -1147,7 +1147,7 @@ public class AddressBasedDestinationTest extends QpidBrokerTestCase Destination replyToDest = AMQDestination.createDestination(replyTo); MessageConsumer replyToCons = session.createConsumer(replyToDest); - Destination dest = session.createQueue("amq.direct/test"); + Destination dest = session.createQueue("ADDR:amq.direct/test"); MessageConsumer cons = session.createConsumer(dest); MessageProducer prod = session.createProducer(dest); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java index 1a23eee8ab..6189c37306 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java @@ -63,7 +63,7 @@ public class SyncWaitTimeoutDelayTest extends SyncWaitDelayTest catch (JMSException e) { assertTrue("Wrong exception type received.", e.getLinkedException() instanceof AMQTimeoutException); - assertTrue("Wrong message received on exception.", e.getMessage().startsWith("Failed to commit")); + assertTrue("Wrong message received on exception.", e.getMessage().startsWith("Exception during commit")); // As we are using Nano time ensure to multiply up the millis. assertTrue("Timeout was more than 30s default", (System.nanoTime() - start) < (1000000L * 1000 * 30)); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/ClientAcknowledgeTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/ClientAcknowledgeTest.java new file mode 100644 index 0000000000..06be5cf456 --- /dev/null +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/ClientAcknowledgeTest.java @@ -0,0 +1,82 @@ +/* + * 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.test.unit.ack; + +import javax.jms.Connection; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; + +import org.apache.qpid.test.utils.QpidBrokerTestCase; + +public class ClientAcknowledgeTest extends QpidBrokerTestCase +{ + private static final long ONE_DAY_MS = 1000l * 60 * 60 * 24; + private Connection _connection; + private Queue _queue; + private Session _consumerSession; + private MessageConsumer _consumer; + private MessageProducer _producer; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _queue = getTestQueue(); + _connection = getConnection(); + } + + /** + * Test that message.acknowledge actually acknowledges, regardless of + * the flusher thread period, by restarting the broker after calling + * acknowledge, and then verifying after restart that the message acked + * is no longer present. This test requires a persistent store. + */ + public void testClientAckWithLargeFlusherPeriod() throws Exception + { + setTestClientSystemProperty("qpid.session.max_ack_delay", Long.toString(ONE_DAY_MS)); + _consumerSession = _connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); + _consumer = _consumerSession.createConsumer(_queue); + _connection.start(); + + _producer = _consumerSession.createProducer(_queue); + _producer.send(createNextMessage(_consumerSession, 1)); + _producer.send(createNextMessage(_consumerSession, 2)); + + Message message = _consumer.receive(1000l); + assertNotNull("Message has not been received", message); + assertEquals("Unexpected message is received", 1, message.getIntProperty(INDEX)); + message.acknowledge(); + + //restart broker to allow verification of the acks + //without explicitly closing connection (which acks) + restartBroker(); + + // try to receive the message again, which should fail (as it was ackd) + _connection = getConnection(); + _connection.start(); + _consumerSession = _connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); + _consumer = _consumerSession.createConsumer(_queue); + message = _consumer.receive(1000l); + assertNotNull("Message has not been received", message); + assertEquals("Unexpected message is received", 2, message.getIntProperty(INDEX)); + } +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java index 5e7ba5482d..66ca1d8345 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java @@ -46,7 +46,7 @@ public class RecoverTest extends FailoverBaseCase { static final Logger _logger = LoggerFactory.getLogger(RecoverTest.class); - private Exception _error; + private volatile Exception _error; private AtomicInteger count; protected AMQConnection _connection; @@ -249,14 +249,13 @@ public class RecoverTest extends FailoverBaseCase { if (!message.getJMSRedelivered()) { - setError( - new Exception("Message not marked as redelivered on what should be second delivery attempt")); + setError(new Exception("Message not marked as redelivered on what should be second delivery attempt")); } } else { - System.err.println(message); - fail("Message delivered too many times!: " + count); + _logger.error(message.toString()); + setError(new Exception("Message delivered too many times!: " + count)); } } catch (JMSException e) diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java index c6b8069300..3c7962d873 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java @@ -21,14 +21,13 @@ package org.apache.qpid.test.unit.basic.close; import org.apache.qpid.test.utils.QpidBrokerTestCase; import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQQueue; -import org.apache.qpid.url.AMQBindingURL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; +import javax.jms.Queue; import javax.jms.Session; public class CloseTest extends QpidBrokerTestCase @@ -41,7 +40,7 @@ public class CloseTest extends QpidBrokerTestCase Session session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); - AMQQueue queue = new AMQQueue(new AMQBindingURL("test-queue")); + Queue queue = session.createQueue("test-queue"); MessageConsumer consumer = session.createConsumer(queue); MessageProducer producer_not_used_but_created_for_testing = session.createProducer(queue); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java index e79fe69199..53a7533869 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java @@ -20,16 +20,6 @@ */ package org.apache.qpid.test.unit.client; -import java.io.BufferedWriter; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.jms.Connection; -import javax.jms.ExceptionListener; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSSLConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSSLConnectionTest.java index 53a433c543..5f3daa407a 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSSLConnectionTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSSLConnectionTest.java @@ -26,9 +26,9 @@ import org.apache.qpid.client.AMQConnectionURL; public class AMQSSLConnectionTest extends AMQConnectionTest { - private static final String KEYSTORE = TEST_RESOURCES_DIR + "/ssl/java_client_keystore.jks"; + private static final String KEYSTORE = "test-profiles/test_resources/ssl/java_client_keystore.jks"; private static final String KEYSTORE_PASSWORD = "password"; - private static final String TRUSTSTORE = TEST_RESOURCES_DIR + "/ssl/java_client_truststore.jks"; + private static final String TRUSTSTORE = "test-profiles/test_resources/ssl/java_client_truststore.jks"; private static final String TRUSTSTORE_PASSWORD = "password"; @Override diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java index fd28b86762..8ad8fa77d7 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java @@ -20,14 +20,10 @@ */ package org.apache.qpid.test.unit.client.message; -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQDestination; -import org.apache.qpid.client.AMQQueue; -import org.apache.qpid.client.AMQSession; -import org.apache.qpid.test.utils.QpidBrokerTestCase; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import javax.jms.JMSException; import javax.jms.Message; @@ -35,10 +31,13 @@ import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.ObjectMessage; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.utils.QpidBrokerTestCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ObjectMessageTest extends QpidBrokerTestCase implements MessageListener { @@ -67,7 +66,7 @@ public class ObjectMessageTest extends QpidBrokerTestCase implements MessageList connection.start(); // create a publisher - producer = session.createProducer(destination, false, false, true); + producer = session.createProducer(destination, false, false); A a1 = new A(1, "A"); A a2 = new A(2, "a"); B b = new B(1, "B"); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java deleted file mode 100644 index 3ec7937812..0000000000 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java +++ /dev/null @@ -1,403 +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.test.unit.publish; - -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQDestination; -import org.apache.qpid.client.AMQSession; -import org.apache.qpid.jms.ConnectionListener; -import org.apache.qpid.test.utils.FailoverBaseCase; - -import javax.jms.Connection; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageListener; -import javax.jms.MessageProducer; -import javax.jms.Queue; -import javax.jms.Session; -import javax.jms.TransactionRolledBackException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * QPID-1816 : Whilst testing Acknoledgement after failover this completes testing - * of the client after failover. When we have a dirty session we should receive - * an error if we attempt to publish. This test ensures that both in the synchronous - * and asynchronous message delivery paths we receive the expected exceptions at - * the expected time. - */ -public class DirtyTransactedPublishTest extends FailoverBaseCase implements ConnectionListener -{ - protected CountDownLatch _failoverCompleted = new CountDownLatch(1); - - protected int NUM_MESSAGES; - protected Connection _connection; - protected Queue _queue; - protected Session _consumerSession; - protected MessageConsumer _consumer; - protected MessageProducer _producer; - - private static final String MSG = "MSG"; - private static final String SEND_FROM_ON_MESSAGE_TEXT = "sendFromOnMessage"; - protected CountDownLatch _receviedAll; - protected AtomicReference<Exception> _causeOfFailure = new AtomicReference<Exception>(null); - - @Override - protected void setUp() throws Exception - { - super.setUp(); - NUM_MESSAGES = 10; - - _queue = getTestQueue(); - - //Create Producer put some messages on the queue - _connection = getConnection(); - } - - /** - * Initialise the test variables - * @param transacted is this a transacted test - * @param mode if not trasacted then what ack mode to use - * @throws Exception if there is a setup issue. - */ - protected void init(boolean transacted, int mode) throws Exception - { - _consumerSession = _connection.createSession(transacted, mode); - _consumer = _consumerSession.createConsumer(_queue); - _producer = _consumerSession.createProducer(_queue); - - // These should all end up being prefetched by session - sendMessage(_consumerSession, _queue, 1); - - assertEquals("Wrong number of messages on queue", 1, - ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue)); - } - - /** - * If a transacted session has failed over whilst it has uncommitted sent - * data then we need to throw a TransactedRolledbackException on commit() - * - * The alternative would be to maintain a replay buffer so that the message - * could be resent. This is not currently implemented - * - * @throws Exception if something goes wrong. - */ - public void testDirtySendingSynchronousTransacted() throws Exception - { - Session producerSession = _connection.createSession(true, Session.SESSION_TRANSACTED); - - // Ensure we get failover notifications - ((AMQConnection) _connection).setConnectionListener(this); - - MessageProducer producer = producerSession.createProducer(_queue); - - // Create and send message 0 - Message msg = producerSession.createMessage(); - msg.setIntProperty(INDEX, 0); - producer.send(msg); - - // DON'T commit message .. fail connection - - failBroker(getFailingPort()); - - // Ensure destination exists for sending - producerSession.createConsumer(_queue).close(); - - // Send the next message - msg.setIntProperty(INDEX, 1); - try - { - producer.send(msg); - fail("Should fail with Qpid as we provide early warning of the dirty session via a JMSException."); - } - catch (JMSException jmse) - { - assertEquals("Early warning of dirty session not correct", - "Failover has occurred and session is dirty so unable to send.", jmse.getMessage()); - } - - // Ignore that the session is dirty and attempt to commit to validate the - // exception is thrown. AND that the above failure notification did NOT - // clean up the session. - - try - { - producerSession.commit(); - fail("Session is dirty we should get an TransactionRolledBackException"); - } - catch (TransactionRolledBackException trbe) - { - // Normal path. - } - - // Resending of messages should now work ok as the commit was forcilbly rolledback - msg.setIntProperty(INDEX, 0); - producer.send(msg); - msg.setIntProperty(INDEX, 1); - producer.send(msg); - - producerSession.commit(); - - assertEquals("Wrong number of messages on queue", 2, - ((AMQSession) producerSession).getQueueDepth((AMQDestination) _queue)); - } - - /** - * If a transacted session has failed over whilst it has uncommitted sent - * data then we need to throw a TransactedRolledbackException on commit() - * - * The alternative would be to maintain a replay buffer so that the message - * could be resent. This is not currently implemented - * - * @throws Exception if something goes wrong. - */ - public void testDirtySendingOnMessageTransacted() throws Exception - { - NUM_MESSAGES = 1; - _receviedAll = new CountDownLatch(NUM_MESSAGES); - ((AMQConnection) _connection).setConnectionListener(this); - - init(true, Session.SESSION_TRANSACTED); - - _consumer.setMessageListener(new MessageListener() - { - - public void onMessage(Message message) - { - try - { - // Create and send message 0 - Message msg = _consumerSession.createMessage(); - msg.setIntProperty(INDEX, 0); - _producer.send(msg); - - // DON'T commit message .. fail connection - - failBroker(getFailingPort()); - - // rep - repopulateBroker(); - - // Destination will exist as this failBroker will populate - // the queue with 1 message - - // Send the next message - msg.setIntProperty(INDEX, 1); - try - { - _producer.send(msg); - fail("Should fail with Qpid as we provide early warning of the dirty session via a JMSException."); - } - catch (JMSException jmse) - { - assertEquals("Early warning of dirty session not correct", - "Failover has occurred and session is dirty so unable to send.", jmse.getMessage()); - } - - // Ignore that the session is dirty and attempt to commit to validate the - // exception is thrown. AND that the above failure notification did NOT - // clean up the session. - - try - { - _consumerSession.commit(); - fail("Session is dirty we should get an TransactionRolledBackException"); - } - catch (TransactionRolledBackException trbe) - { - // Normal path. - } - - // Resend messages - msg.setIntProperty(INDEX, 0); - msg.setStringProperty(MSG, SEND_FROM_ON_MESSAGE_TEXT); - _producer.send(msg); - msg.setIntProperty(INDEX, 1); - msg.setStringProperty(MSG, SEND_FROM_ON_MESSAGE_TEXT); - _producer.send(msg); - - _consumerSession.commit(); - - // Stop this consumer .. can't do _consumer.stop == DEADLOCK - // this doesn't seem to stop dispatcher running - _connection.stop(); - - // Signal that the onMessage send part of test is complete - // main thread can validate that messages are correct - _receviedAll.countDown(); - - } - catch (Exception e) - { - fail(e); - } - - } - - }); - - _connection.start(); - - if (!_receviedAll.await(10000L, TimeUnit.MILLISECONDS)) - { - // Check to see if we ended due to an exception in the onMessage handler - Exception cause = _causeOfFailure.get(); - if (cause != null) - { - cause.printStackTrace(); - fail(cause.getMessage()); - } - else - { - fail("All messages not received:" + _receviedAll.getCount() + "/" + NUM_MESSAGES); - } - } - - // Check to see if we ended due to an exception in the onMessage handler - Exception cause = _causeOfFailure.get(); - if (cause != null) - { - cause.printStackTrace(); - fail(cause.getMessage()); - } - - _consumer.close(); - _consumerSession.close(); - - _consumerSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - _connection.start(); - - // Validate that we could send the messages as expected. - assertEquals("Wrong number of messages on queue", 3, - ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue)); - - MessageConsumer consumer = _consumerSession.createConsumer(_queue); - - //Validate the message sent to setup the failed over broker. - Message message = consumer.receive(1000); - assertNotNull("Message " + 0 + " not received.", message); - assertEquals("Incorrect message received", 0, message.getIntProperty(INDEX)); - - // Validate the two messages sent from within the onMessage - for (int index = 0; index <= 1; index++) - { - message = consumer.receive(1000); - assertNotNull("Message " + index + " not received.", message); - assertEquals("Incorrect message received", index, message.getIntProperty(INDEX)); - assertEquals("Incorrect message text for message:" + index, SEND_FROM_ON_MESSAGE_TEXT, message.getStringProperty(MSG)); - } - - assertNull("Extra message received.", consumer.receiveNoWait()); - - _consumerSession.close(); - - assertEquals("Wrong number of messages on queue", 0, - ((AMQSession) getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE)).getQueueDepth((AMQDestination) _queue)); - } - - private void repopulateBroker() throws Exception - { - // Repopulate this new broker so we can test what happends after failover - - //Get the connection to the first (main port) broker. - Connection connection = getConnection(); - // Use a transaction to send messages so we can be sure they arrive. - Session session = connection.createSession(true, Session.SESSION_TRANSACTED); - // ensure destination is created. - session.createConsumer(_queue).close(); - - sendMessage(session, _queue, NUM_MESSAGES); - - assertEquals("Wrong number of messages on queue", NUM_MESSAGES, - ((AMQSession) session).getQueueDepth((AMQDestination) _queue)); - - connection.close(); - } - - // AMQConnectionListener Interface.. used so we can validate that we - // actually failed over. - - public void bytesSent(long count) - { - } - - public void bytesReceived(long count) - { - } - - public boolean preFailover(boolean redirect) - { - //Allow failover - return true; - } - - public boolean preResubscribe() - { - //Allow failover - return true; - } - - public void failoverComplete() - { - _failoverCompleted.countDown(); - } - - /** - * Override so we can block until failover has completd - * - * @param port int the port of the broker to fail. - */ - @Override - public void failBroker(int port) - { - super.failBroker(port); - - try - { - if (!_failoverCompleted.await(DEFAULT_FAILOVER_TIME, TimeUnit.MILLISECONDS)) - { - fail("Failover did not occur in specified time:" + DEFAULT_FAILOVER_TIME); - } - } - catch (InterruptedException e) - { - fail("Failover was interuppted"); - } - } - - /** - * Pass the given exception back to the waiting thread to fail the test run. - * - * @param e The exception that is causing the test to fail. - */ - protected void fail(Exception e) - { - _causeOfFailure.set(e); - // End the test. - while (_receviedAll.getCount() != 0) - { - _receviedAll.countDown(); - } - } -} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java deleted file mode 100644 index 46e5d214f5..0000000000 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java +++ /dev/null @@ -1,82 +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.test.unit.transacted; - -/** - * This verifies that changing the {@code transactionTimeout} configuration will alter - * the behaviour of the transaction open and idle logging, and that when the connection - * will be closed. - */ -public class TransactionTimeoutConfigurationTest extends TransactionTimeoutTestCase -{ - @Override - protected void configure() throws Exception - { - // Setup housekeeping every second - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.checkPeriod", "100"); - - // Set transaction timout properties. - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "200"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "1000"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "100"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "500"); - } - - public void testProducerIdleCommit() throws Exception - { - try - { - send(5, 0); - - sleep(2.0f); - - _psession.commit(); - fail("should fail"); - } - catch (Exception e) - { - _exception = e; - } - - monitor(5, 0); - - check(IDLE); - } - - public void testProducerOpenCommit() throws Exception - { - try - { - send(5, 0.3f); - - _psession.commit(); - fail("should fail"); - } - catch (Exception e) - { - _exception = e; - } - - monitor(6, 3); - - check(OPEN); - } -} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java index db508143f9..fd8beffbe6 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java @@ -30,6 +30,8 @@ public class TransactionTimeoutDisabledTest extends TransactionTimeoutTestCase { // Setup housekeeping every second setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.checkPeriod", "100"); + + // No transaction timeout configuration. } public void testProducerIdleCommit() throws Exception @@ -47,7 +49,7 @@ public class TransactionTimeoutDisabledTest extends TransactionTimeoutTestCase fail("Should have succeeded"); } - assertTrue("Listener should not have received exception", _caught.getCount() == 1); + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); monitor(0, 0); } @@ -65,7 +67,7 @@ public class TransactionTimeoutDisabledTest extends TransactionTimeoutTestCase fail("Should have succeeded"); } - assertTrue("Listener should not have received exception", _caught.getCount() == 1); + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); monitor(0, 0); } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java index c912d6a323..f554b0089e 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java @@ -30,152 +30,125 @@ package org.apache.qpid.test.unit.transacted; */ public class TransactionTimeoutTest extends TransactionTimeoutTestCase { - public void testProducerIdle() throws Exception + + protected void configure() throws Exception { - try + // Setup housekeeping every second + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.checkPeriod", "100"); + + if (getName().contains("ProducerIdle")) { - sleep(2.0f); - - _psession.commit(); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "0"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "0"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "500"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "1500"); } - catch (Exception e) + else if (getName().contains("ProducerOpen")) { - fail("Should have succeeded"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "1000"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "2000"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "0"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "0"); } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - - monitor(0, 0); - } - - public void testProducerIdleCommit() throws Exception - { - try + else { - send(5, 0); - - sleep(2.0f); - - _psession.commit(); - fail("should fail"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "1000"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "2000"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "500"); + setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "1000"); } - catch (Exception e) - { - _exception = e; - } - - monitor(5, 0); - - check(IDLE); } - - public void testProducerOpenCommit() throws Exception + + public void testProducerIdle() throws Exception { - try - { - send(6, 0.5f); - - _psession.commit(); - fail("should fail"); - } - catch (Exception e) - { - _exception = e; - } - - monitor(0, 10); - - check(OPEN); + sleep(2.0f); + + _psession.commit(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + + monitor(0, 0); } - - public void testProducerIdleCommitTwice() throws Exception + + public void testProducerIdleCommit() throws Exception { + send(5, 0); + // Idle for more than idleClose to generate idle-warns and cause a close. + sleep(2.0f); + try { - send(5, 0); - - sleep(1.0f); - _psession.commit(); - - send(5, 0); - - sleep(2.0f); - - _psession.commit(); - fail("should fail"); + fail("Exception not thrown"); } catch (Exception e) { _exception = e; } - + monitor(10, 0); - + check(IDLE); } - - public void testProducerOpenCommitTwice() throws Exception + + public void testProducerIdleCommitTwice() throws Exception { + send(5, 0); + // Idle for less than idleClose to generate idle-warns + sleep(1.0f); + + _psession.commit(); + + send(5, 0); + // Now idle for more than idleClose to generate more idle-warns and cause a close. + sleep(2.0f); + try { - send(5, 0); - - sleep(1.0f); - _psession.commit(); - - send(6, 0.5f); - - _psession.commit(); - fail("should fail"); + fail("Exception not thrown"); } catch (Exception e) { _exception = e; } - - // the presistent store generates more idle messages? - monitor(isBrokerStorePersistent() ? 10 : 5, 10); - - check(OPEN); + + monitor(15, 0); + + check(IDLE); } - + public void testProducerIdleRollback() throws Exception { + send(5, 0); + // Now idle for more than idleClose to generate more idle-warns and cause a close. + sleep(2.0f); try { - send(5, 0); - - sleep(2.0f); - _psession.rollback(); - fail("should fail"); + fail("Exception not thrown"); } catch (Exception e) { _exception = e; } - - monitor(5, 0); - + + monitor(10, 0); + check(IDLE); } - + public void testProducerIdleRollbackTwice() throws Exception { + send(5, 0); + // Idle for less than idleClose to generate idle-warns + sleep(1.0f); + _psession.rollback(); + send(5, 0); + // Now idle for more than idleClose to generate more idle-warns and cause a close. + sleep(2.0f); try { - send(5, 0); - - sleep(1.0f); - - _psession.rollback(); - - send(5, 0); - - sleep(2.0f); - _psession.rollback(); fail("should fail"); } @@ -183,153 +156,153 @@ public class TransactionTimeoutTest extends TransactionTimeoutTestCase { _exception = e; } - - monitor(10, 0); + + monitor(15, 0); check(IDLE); } - - public void testConsumerCommitClose() throws Exception + + public void testProducerOpenCommit() throws Exception { try { - send(1, 0); - + // Sleep between sends to cause open warns and then cause a close. + send(6, 0.5f); _psession.commit(); - - expect(1, 0); - - _csession.commit(); - - sleep(3.0f); - - _csession.close(); + fail("Exception not thrown"); } catch (Exception e) { - fail("should have succeeded: " + e.getMessage()); + _exception = e; } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - - monitor(0, 0); + + monitor(0, 10); + + check(OPEN); } - public void testConsumerIdleReceiveCommit() throws Exception + public void testProducerOpenCommitTwice() throws Exception { + send(5, 0); + sleep(1.0f); + _psession.commit(); + try { - send(1, 0); - + // Now sleep between sends to cause open warns and then cause a close. + send(6, 0.5f); _psession.commit(); - - sleep(2.0f); - - expect(1, 0); - - sleep(2.0f); - - _csession.commit(); + fail("Exception not thrown"); } catch (Exception e) { - fail("Should have succeeded"); + _exception = e; } + + monitor(0, 10); - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - + check(OPEN); + } + + public void testConsumerCommitClose() throws Exception + { + send(1, 0); + + _psession.commit(); + + expect(1, 0); + + _csession.commit(); + + sleep(3.0f); + + _csession.close(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + monitor(0, 0); } + public void testConsumerIdleReceiveCommit() throws Exception + { + send(1, 0); + + _psession.commit(); + + sleep(2.0f); + + expect(1, 0); + + sleep(2.0f); + + _csession.commit(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + + monitor(0, 0); + } + public void testConsumerIdleCommit() throws Exception { - try - { - send(1, 0); - - _psession.commit(); - - expect(1, 0); - - sleep(2.0f); - - _csession.commit(); - } - catch (Exception e) - { - fail("Should have succeeded"); - } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - + send(1, 0); + + _psession.commit(); + + expect(1, 0); + + sleep(2.0f); + + _csession.commit(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + monitor(0, 0); } public void testConsumerIdleRollback() throws Exception { - try - { - send(1, 0); - - _psession.commit(); - - expect(1, 0); - - sleep(2.0f); - - _csession.rollback(); - } - catch (Exception e) - { - fail("Should have succeeded"); - } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - + send(1, 0); + + _psession.commit(); + + expect(1, 0); + + sleep(2.0f); + + _csession.rollback(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + monitor(0, 0); } - + public void testConsumerOpenCommit() throws Exception { - try - { - send(1, 0); - - _psession.commit(); - - sleep(3.0f); - - _csession.commit(); - } - catch (Exception e) - { - fail("Should have succeeded"); - } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); - + send(1, 0); + + _psession.commit(); + + sleep(3.0f); + + _csession.commit(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + monitor(0, 0); } public void testConsumerOpenRollback() throws Exception { - try - { - send(1, 0); - - _psession.commit(); - - sleep(3.0f); - - _csession.rollback(); - } - catch (Exception e) - { - fail("Should have succeeded"); - } - - assertTrue("Listener should not have received exception", _caught.getCount() == 1); + send(1, 0); + _psession.commit(); + + sleep(3.0f); + + _csession.rollback(); + + assertEquals("Listener should not have received exception", 0, getNumberOfDeliveredExceptions()); + monitor(0, 0); } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java index ef2de5c592..2b90d38049 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java @@ -23,6 +23,7 @@ package org.apache.qpid.test.unit.transacted; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import javax.jms.DeliveryMode; import javax.jms.ExceptionListener; @@ -49,7 +50,7 @@ import org.apache.qpid.util.LogMonitor; /** * The {@link TestCase} for transaction timeout testing. */ -public class TransactionTimeoutTestCase extends QpidBrokerTestCase implements ExceptionListener +public abstract class TransactionTimeoutTestCase extends QpidBrokerTestCase implements ExceptionListener { public static final String VIRTUALHOST = "test"; public static final String TEXT = "0123456789abcdefghiforgettherest"; @@ -64,31 +65,16 @@ public class TransactionTimeoutTestCase extends QpidBrokerTestCase implements Ex protected Queue _queue; protected MessageConsumer _consumer; protected MessageProducer _producer; - protected CountDownLatch _caught = new CountDownLatch(1); + private CountDownLatch _exceptionLatch = new CountDownLatch(1); + protected AtomicInteger _exceptionCount = new AtomicInteger(0); protected String _message; protected Exception _exception; protected AMQConstant _code; - - protected void configure() throws Exception - { - // Setup housekeeping every second - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.checkPeriod", "100"); - - /* - * Set transaction timout properties. The XML in the virtualhosts configuration is as follows: - * - * <transactionTimeout> - * <openWarn>1000</openWarn> - * <openClose>2000</openClose> - * <idleWarn>500</idleWarn> - * <idleClose>1500</idleClose> - * </transactionTimeout> - */ - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "1000"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "2000"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "500"); - setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "1000"); - } + + /** + * Subclasses must implement this to configure transaction timeout parameters. + */ + protected abstract void configure() throws Exception; protected void setUp() throws Exception { @@ -233,7 +219,7 @@ public class TransactionTimeoutTestCase extends QpidBrokerTestCase implements Ex */ protected void check(String reason)throws InterruptedException { - assertTrue("Should have caught exception in listener", _caught.await(1, TimeUnit.SECONDS)); + assertTrue("Should have caught exception in listener", _exceptionLatch.await(1, TimeUnit.SECONDS)); assertNotNull("Should have thrown exception to client", _exception); assertTrue("Exception message should contain '" + reason + "': " + _message, _message.contains(reason + " transaction timed out")); assertNotNull("Exception should have an error code", _code); @@ -243,11 +229,18 @@ public class TransactionTimeoutTestCase extends QpidBrokerTestCase implements Ex /** @see javax.jms.ExceptionListener#onException(javax.jms.JMSException) */ public void onException(JMSException jmse) { - _caught.countDown(); + _exceptionLatch.countDown(); + _exceptionCount.incrementAndGet(); + _message = jmse.getLinkedException().getMessage(); if (jmse.getLinkedException() instanceof AMQException) { _code = ((AMQException) jmse.getLinkedException()).getErrorCode(); } } + + protected int getNumberOfDeliveredExceptions() + { + return _exceptionCount.get(); + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java index 40df024c67..bb44aea659 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java @@ -52,7 +52,6 @@ import javax.naming.NamingException; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.qpid.AMQException; import org.apache.qpid.client.AMQConnectionFactory; @@ -115,7 +114,6 @@ public class QpidBrokerTestCase extends QpidTestCase private static final String BROKER_LANGUAGE = "broker.language"; private static final String BROKER_TYPE = "broker.type"; private static final String BROKER_COMMAND = "broker.command"; - private static final String BROKER_CLEAN = "broker.clean"; private static final String BROKER_CLEAN_BETWEEN_TESTS = "broker.clean.between.tests"; private static final String BROKER_EXISTING_QPID_WORK = "broker.existing.qpid.work"; private static final String BROKER_VERSION = "broker.version"; @@ -143,10 +141,9 @@ public class QpidBrokerTestCase extends QpidTestCase protected String _brokerLanguage = System.getProperty(BROKER_LANGUAGE, JAVA); protected BrokerType _brokerType = BrokerType.valueOf(System.getProperty(BROKER_TYPE, "").toUpperCase()); protected String _brokerCommand = System.getProperty(BROKER_COMMAND); - private String _brokerClean = System.getProperty(BROKER_CLEAN, null); private Boolean _brokerCleanBetweenTests = Boolean.getBoolean(BROKER_CLEAN_BETWEEN_TESTS); private final AmqpProtocolVersion _brokerVersion = AmqpProtocolVersion.valueOf(System.getProperty(BROKER_VERSION, "")); - protected String _output = System.getProperty(TEST_OUTPUT); + protected String _output = System.getProperty(TEST_OUTPUT, System.getProperty("java.io.tmpdir")); protected Boolean _brokerPersistent = Boolean.getBoolean(BROKER_PERSITENT); private String _brokerProtocolExcludes = System.getProperty(BROKER_PROTOCOL_EXCLUDES); @@ -217,8 +214,13 @@ public class QpidBrokerTestCase extends QpidTestCase if (redirected) { _outputFile = new File(String.format("%s/TEST-%s.out", _output, qname)); - out = new PrintStream(_outputFile); + out = new PrintStream(new FileOutputStream(_outputFile), true); err = new PrintStream(String.format("%s/TEST-%s.err", _output, qname)); + + // This is relying on behaviour specific to log4j 1.2.12. If we were to upgrade to 1.2.13 or + // beyond we must change either code (or config) to ensure that ConsoleAppender#setFollow + // is set to true otherwise log4j logging will not respect the following reassignment. + System.setOut(out); System.setErr(err); @@ -259,14 +261,9 @@ public class QpidBrokerTestCase extends QpidTestCase if(_brokerCleanBetweenTests) { - try - { - cleanBroker(); - } - catch (Exception e) - { - _logger.error("exception cleaning up broker", e); - } + final String qpidWork = System.getProperty("QPID_WORK"); + cleanBrokerWork(qpidWork); + createBrokerWork(qpidWork); } _logger.info("========== stop " + getTestName() + " =========="); @@ -298,11 +295,11 @@ public class QpidBrokerTestCase extends QpidTestCase String existingQpidWorkPath = System.getProperty(BROKER_EXISTING_QPID_WORK); if(existingQpidWorkPath != null && !existingQpidWorkPath.equals("")) { - cleanBroker(); + String qpidWork = getQpidWork(_brokerType, getPort()); File existing = new File(existingQpidWorkPath); - File qpidWork = new File(getQpidWork(_brokerType, getPort())); - FileUtils.copyRecursive(existing, qpidWork); + cleanBrokerWork(qpidWork); + FileUtils.copyRecursive(existing, new File(qpidWork)); } startBroker(); @@ -494,25 +491,22 @@ public class QpidBrokerTestCase extends QpidTestCase } else if (!_brokerType.equals(BrokerType.EXTERNAL)) { + // Add the port to QPID_WORK to ensure unique working dirs for multi broker tests + final String qpidWork = getQpidWork(_brokerType, port); String cmd = getBrokerCommand(port); _logger.info("starting external broker: " + cmd); ProcessBuilder pb = new ProcessBuilder(cmd.split("\\s+")); pb.redirectErrorStream(true); - Map<String, String> env = pb.environment(); - String qpidHome = System.getProperty(QPID_HOME); env.put(QPID_HOME, qpidHome); - //Augment Path with bin directory in QPID_HOME. env.put("PATH", env.get("PATH").concat(File.pathSeparator + qpidHome + "/bin")); //Add the test name to the broker run. // DON'T change PNAME, qpid.stop needs this value. env.put("QPID_PNAME", "-DPNAME=QPBRKR -DTNAME=\"" + getTestName() + "\""); - // Add the port to QPID_WORK to ensure unique working dirs for multi broker tests - env.put("QPID_WORK", getQpidWork(_brokerType, port)); - + env.put("QPID_WORK", qpidWork); // Use the environment variable to set amqj.logging.level for the broker // The value used is a 'server' value in the test configuration to @@ -563,6 +557,10 @@ public class QpidBrokerTestCase extends QpidTestCase env.put("QPID_OPTS", QPID_OPTS); } } + + // cpp broker requires that the work directory is created + createBrokerWork(qpidWork); + Process process = pb.start();; Piper p = new Piper(process.getInputStream(), @@ -577,7 +575,7 @@ public class QpidBrokerTestCase extends QpidTestCase _logger.info("broker failed to become ready (" + p.ready + "):" + p.getStopLine()); //Ensure broker has stopped process.destroy(); - cleanBroker(); + cleanBrokerWork(qpidWork); throw new RuntimeException("broker failed to become ready:" + p.getStopLine()); } @@ -587,7 +585,7 @@ public class QpidBrokerTestCase extends QpidTestCase //test that the broker is still running and hasn't exited unexpectedly int exit = process.exitValue(); _logger.info("broker aborted: " + exit); - cleanBroker(); + cleanBrokerWork(qpidWork); throw new RuntimeException("broker aborted: " + exit); } catch (IllegalThreadStateException e) @@ -655,21 +653,28 @@ public class QpidBrokerTestCase extends QpidTestCase public String getTestConfigFile() { - String path = _output == null ? System.getProperty("java.io.tmpdir") : _output; - return path + "/" + getTestQueueName() + "-config.xml"; + return _output + "/" + getTestQueueName() + "-config.xml"; } public String getTestVirtualhostsFile() { - String path = _output == null ? System.getProperty("java.io.tmpdir") : _output; - return path + "/" + getTestQueueName() + "-virtualhosts.xml"; + return _output + "/" + getTestQueueName() + "-virtualhosts.xml"; + } + + private String relativeToQpidHome(String file) + { + return file.replace(System.getProperty(QPID_HOME,"QPID_HOME") + "/",""); } protected void saveTestConfiguration() throws ConfigurationException { - // Specifiy the test config file + // Specify the test config file String testConfig = getTestConfigFile(); - setSystemProperty("test.config", testConfig); + String relative = relativeToQpidHome(testConfig); + + setSystemProperty("test.config", relative); + _logger.info("Set test.config property to: " + relative); + _logger.info("Saving test virtualhosts file at: " + testConfig); // Create the file if configuration does not exist if (_testConfiguration.isEmpty()) @@ -681,9 +686,13 @@ public class QpidBrokerTestCase extends QpidTestCase protected void saveTestVirtualhosts() throws ConfigurationException { - // Specifiy the test virtualhosts file + // Specify the test virtualhosts file String testVirtualhosts = getTestVirtualhostsFile(); - setSystemProperty("test.virtualhosts", testVirtualhosts); + String relative = relativeToQpidHome(testVirtualhosts); + + setSystemProperty("test.virtualhosts", relative); + _logger.info("Set test.virtualhosts property to: " + relative); + _logger.info("Saving test virtualhosts file at: " + testVirtualhosts); // Create the file if configuration does not exist if (_testVirtualhosts.isEmpty()) @@ -693,30 +702,33 @@ public class QpidBrokerTestCase extends QpidTestCase _testVirtualhosts.save(testVirtualhosts); } - public void cleanBroker() + protected void cleanBrokerWork(final String qpidWork) { - if (_brokerClean != null) + if (qpidWork != null) { - _logger.info("clean: " + _brokerClean); + _logger.info("Cleaning broker work dir: " + qpidWork); - try + File file = new File(qpidWork); + if (file.exists()) { - ProcessBuilder pb = new ProcessBuilder(_brokerClean.split("\\s+")); - pb.redirectErrorStream(true); - Process clean = pb.start(); - new Piper(clean.getInputStream(),_brokerOutputStream).start(); - - clean.waitFor(); - - _logger.info("clean exited: " + clean.exitValue()); - } - catch (IOException e) - { - throw new RuntimeException(e); + final boolean success = FileUtils.delete(file, true); + if(!success) + { + throw new RuntimeException("Failed to recursively delete beneath : " + file); + } } - catch (InterruptedException e) + } + } + + protected void createBrokerWork(final String qpidWork) + { + if (qpidWork != null) + { + final File dir = new File(qpidWork); + dir.mkdirs(); + if (!dir.isDirectory()) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to created Qpid work directory : " + qpidWork); } } } @@ -730,7 +742,7 @@ public class QpidBrokerTestCase extends QpidTestCase { port = getPort(port); - _logger.info("stopping broker: " + getBrokerCommand(port)); + _logger.info("stopping broker on port : " + port); BrokerHolder broker = _brokers.remove(port); broker.shutdown(); } @@ -906,7 +918,7 @@ public class QpidBrokerTestCase extends QpidTestCase } /** - * Add an environtmen variable for the external broker environment + * Add an environment variable for the external broker environment * * @param property the property to set * @param value the value to set it to @@ -990,9 +1002,9 @@ public class QpidBrokerTestCase extends QpidTestCase * Get the default connection factory for the currently used broker * Default factory is "local" * - * @return A conection factory + * @return A connection factory * - * @throws Exception if there is an error getting the tactory + * @throws Exception if there is an error getting the factory */ public AMQConnectionFactory getConnectionFactory() throws NamingException { @@ -1016,7 +1028,7 @@ public class QpidBrokerTestCase extends QpidTestCase * * @param factoryName The factory name * - * @return A conection factory + * @return A connection factory * * @throws Exception if there is an error getting the tactory */ @@ -1054,7 +1066,7 @@ public class QpidBrokerTestCase extends QpidTestCase { _logger.info("get connection"); Connection con = getConnectionFactory().createConnection(username, password); - //add the connection in the lis of connections + //add the connection in the list of connections _connections.add(con); return con; } @@ -1063,7 +1075,7 @@ public class QpidBrokerTestCase extends QpidTestCase { _logger.info("get Connection"); Connection con = getConnectionFactory().createConnection(username, password, id); - //add the connection in the lis of connections + //add the connection in the list of connections _connections.add(con); return con; } @@ -1154,7 +1166,7 @@ public class QpidBrokerTestCase extends QpidTestCase /** * Send messages to the given destination. * - * If session is transacted then messages will be commited before returning + * If session is transacted then messages will be committed before returning * * @param session the session to use for sending * @param destination where to send them to @@ -1162,7 +1174,7 @@ public class QpidBrokerTestCase extends QpidTestCase * * @param batchSize the batchSize in which to commit, 0 means no batching, * but a single commit at the end - * @return the sent messgse + * @return the sent message * * @throws Exception */ @@ -1175,7 +1187,7 @@ public class QpidBrokerTestCase extends QpidTestCase /** * Send messages to the given destination. * - * If session is transacted then messages will be commited before returning + * If session is transacted then messages will be committed before returning * * @param session the session to use for sending * @param destination where to send them to @@ -1184,7 +1196,7 @@ public class QpidBrokerTestCase extends QpidTestCase * @param offset offset allows the INDEX value of the message to be adjusted. * @param batchSize the batchSize in which to commit, 0 means no batching, * but a single commit at the end - * @return the sent messgse + * @return the sent message * * @throws Exception */ diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java index a5e2b80f64..c09e63308c 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java @@ -27,11 +27,9 @@ import org.apache.log4j.SimpleLayout; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.ArrayList; +import java.io.LineNumberReader; import java.util.List; import java.util.LinkedList; @@ -45,10 +43,12 @@ import java.util.LinkedList; public class LogMonitor { // The file that the log statements will be written to. - private File _logfile; + private final File _logfile; // The appender we added to the get messages - private FileAppender _appender; + private final FileAppender _appender; + + private int _linesToSkip = 0; /** * Create a new LogMonitor that creates a new Log4j Appender and monitors @@ -78,6 +78,7 @@ public class LogMonitor if (file != null && file.exists()) { _logfile = file; + _appender = null; } else { @@ -99,13 +100,13 @@ public class LogMonitor * @param wait the time in ms to wait for the message to occur * @return true if the message was found * - * @throws java.io.FileNotFoundException if the Log file can nolonger be found + * @throws java.io.FileNotFoundException if the Log file can no longer be found * @throws IOException thrown when reading the log file */ public List<String> waitAndFindMatches(String message, long wait) throws FileNotFoundException, IOException { - if (waitForMessage(message, wait, true)) + if (waitForMessage(message, wait)) { return findMatches(message); } @@ -116,7 +117,9 @@ public class LogMonitor } /** - * Checks the log for instances of the search string. + * Checks the log for instances of the search string. If the caller + * has previously called {@link #markDiscardPoint()}, lines up until the discard + * point are not considered. * * The pattern parameter can take any valid argument used in String.contains() * @@ -130,66 +133,99 @@ public class LogMonitor */ public List<String> findMatches(String pattern) throws IOException { - return FileUtils.searchFile(_logfile, pattern); + + List<String> results = new LinkedList<String>(); + + LineNumberReader reader = new LineNumberReader(new FileReader(_logfile)); + try + { + while (reader.ready()) + { + String line = reader.readLine(); + if (reader.getLineNumber() > _linesToSkip && line.contains(pattern)) + { + results.add(line); + } + } + } + finally + { + reader.close(); + } + + return results; } /** - * Checks the log file for a given message to appear. + * Checks the log file for a given message to appear. If the caller + * has previously called {@link #markDiscardPoint()}, lines up until the discard + * point are not considered. * * @param message the message to wait for in the log * @param wait the time in ms to wait for the message to occur - * - * @param printFileOnFailure should we print the contents that have been - * read if we fail ot find the message. * @return true if the message was found * - * @throws java.io.FileNotFoundException if the Log file can nolonger be found + * @throws java.io.FileNotFoundException if the Log file can no longer be found * @throws IOException thrown when reading the log file */ - public boolean waitForMessage(String message, long wait, boolean printFileOnFailure) + public boolean waitForMessage(String message, long wait) throws FileNotFoundException, IOException { // Loop through alerts until we're done or wait ms seconds have passed, // just in case the logfile takes a while to flush. - BufferedReader reader = new BufferedReader(new FileReader(_logfile)); - boolean found = false; - long endtime = System.currentTimeMillis() + wait; - ArrayList<String> contents = new ArrayList<String>(); - while (!found && System.currentTimeMillis() < endtime) + LineNumberReader reader = null; + try { - while (reader.ready()) + reader = new LineNumberReader(new FileReader(_logfile)); + + boolean found = false; + long endtime = System.currentTimeMillis() + wait; + while (!found && System.currentTimeMillis() < endtime) { - String line = reader.readLine(); - contents.add(line); - if (line.contains(message)) + boolean ready = true; + while (ready = reader.ready()) { - found = true; + String line = reader.readLine(); + + if (reader.getLineNumber() > _linesToSkip) + { + if (line.contains(message)) + { + found = true; + break; + } + } + } + if (!ready) + { + try + { + Thread.sleep(50); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } } } + return found; + } - if (!found && printFileOnFailure) + finally { - for (String line : contents) + if (reader != null) { - System.out.println(line); + reader.close(); } } - return found; } - - public boolean waitForMessage(String message, long alertLogWaitPeriod) throws FileNotFoundException, IOException - { - return waitForMessage(message, alertLogWaitPeriod, true); - } - - /** * Read the log file in to memory as a String * * @return the current contents of the log file * - * @throws java.io.FileNotFoundException if the Log file can nolonger be found + * @throws java.io.FileNotFoundException if the Log file can no longer be found * @throws IOException thrown when reading the log file */ public String readFile() throws FileNotFoundException, IOException @@ -208,14 +244,37 @@ public class LogMonitor } /** - * Clears the log file and writes: 'Log Monitor Reset' at the start of the file + * Marks the discard point in the log file. * - * @throws java.io.FileNotFoundException if the Log file can nolonger be found + * @throws java.io.FileNotFoundException if the Log file can no longer be found * @throws IOException thrown if there is a problem with the log file */ - public void reset() throws FileNotFoundException, IOException + public void markDiscardPoint() throws FileNotFoundException, IOException + { + _linesToSkip = countLinesInFile(); + } + + private int countLinesInFile() throws IOException { - new FileOutputStream(_logfile).getChannel().truncate(0); + int lineCount = 0; + BufferedReader br = null; + try + { + br = new BufferedReader(new FileReader(_logfile)); + while(br.readLine() != null) + { + lineCount++; + } + + return lineCount; + } + finally + { + if (br != null) + { + br.close(); + } + } } /** diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java index a99abe4b94..89f707fbef 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java @@ -155,7 +155,7 @@ public class LogMonitorTest extends TestCase String notLogged = "This text was not logged"; - validateLogDoesNotContainsMessage(_monitor, notLogged); + validateLogDoesNotContainMessage(_monitor, notLogged); } public void testWaitForMessage_Timeout() throws IOException @@ -168,28 +168,27 @@ public class LogMonitorTest extends TestCase // Verify that we can time out waiting for a message assertFalse("Message was logged ", - _monitor.waitForMessage(message, TIME_OUT / 2, false)); + _monitor.waitForMessage(message, TIME_OUT / 2)); // Verify that the message did eventually get logged. assertTrue("Message was never logged.", _monitor.waitForMessage(message, TIME_OUT)); } - public void testReset() throws IOException + public void testDiscardPoint() throws IOException { - String message = getName() + ": Test Message"; - - Logger.getRootLogger().warn(message); - - validateLogContainsMessage(_monitor, message); + String firstMessage = getName() + ": Test Message1"; + Logger.getRootLogger().warn(firstMessage); - String LOG_RESET_TEXT = "Log Monitor Reset"; + validateLogContainsMessage(_monitor, firstMessage); - validateLogDoesNotContainsMessage(_monitor, LOG_RESET_TEXT); + _monitor.markDiscardPoint(); - _monitor.reset(); + validateLogDoesNotContainMessage(_monitor, firstMessage); - assertEquals("", _monitor.readFile()); + String secondMessage = getName() + ": Test Message2"; + Logger.getRootLogger().warn(secondMessage); + validateLogContainsMessage(_monitor, secondMessage); } public void testRead() throws IOException @@ -214,7 +213,7 @@ public class LogMonitorTest extends TestCase * * @throws IOException if a problems occurs */ - protected void validateLogDoesNotContainsMessage(LogMonitor log, String message) + protected void validateLogDoesNotContainMessage(LogMonitor log, String message) throws IOException { List<String> results = log.findMatches(message); diff --git a/qpid/java/test-profiles/CPPExcludes b/qpid/java/test-profiles/CPPExcludes index 2b58a0684d..d05f42e4b7 100755 --- a/qpid/java/test-profiles/CPPExcludes +++ b/qpid/java/test-profiles/CPPExcludes @@ -142,7 +142,6 @@ org.apache.qpid.server.failover.MessageDisappearWithIOExceptionTest#* // These are recent test additions that are failing with the c++ broker // Temporarily disabling until properly investigated. -org.apache.qpid.test.unit.publish.DirtyTransactedPublishTest#* org.apache.qpid.test.unit.ack.FailoverBeforeConsumingRecoverTest#* org.apache.qpid.test.client.RollbackOrderTest#testOrderingAfterRollbackOnMessage#* diff --git a/qpid/java/test-profiles/CPPTransientExcludes b/qpid/java/test-profiles/CPPTransientExcludes index 47f24db19c..a214cf5b5c 100644 --- a/qpid/java/test-profiles/CPPTransientExcludes +++ b/qpid/java/test-profiles/CPPTransientExcludes @@ -27,3 +27,6 @@ org.apache.qpid.test.unit.xa.TopicTest#testMultiMessagesDurSubCrash org.apache.qpid.test.unit.xa.TopicTest#testRecover org.apache.qpid.test.unit.xa.QueueTest#testRecover org.apache.qpid.test.unit.xa.QueueTest#testSendAndRecover + +// test requires a persistent store +org.apache.qpid.test.unit.ack.ClientAcknowledgeTest#testClientAckWithLargeFlusherPeriod diff --git a/qpid/java/test-profiles/Java010Excludes b/qpid/java/test-profiles/Java010Excludes index fe0a53bdfc..e7718b982d 100755 --- a/qpid/java/test-profiles/Java010Excludes +++ b/qpid/java/test-profiles/Java010Excludes @@ -55,9 +55,6 @@ org.apache.qpid.server.queue.ProducerFlowControlTest#* //QPID-1950 : Commit to test this failure. This is a MINA only failure so it cannot be tested when using 010. org.apache.qpid.server.failover.MessageDisappearWithIOExceptionTest#* -//QPID-3421: tests are failing on 0.10 test profile -org.apache.qpid.test.unit.publish.DirtyTransactedPublishTest#* - //QPID-1864: rollback with subscriptions does not work in 0-10 yet org.apache.qpid.test.client.RollbackOrderTest#testOrderingAfterRollbackOnMessage diff --git a/qpid/java/test-profiles/JavaBDBExcludes b/qpid/java/test-profiles/JavaBDBExcludes new file mode 100644 index 0000000000..3fac01cb6d --- /dev/null +++ b/qpid/java/test-profiles/JavaBDBExcludes @@ -0,0 +1,22 @@ +// +// 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. +// + +//This test is subclassed within the bdbstore module to enable it to run and +//also add some bdb-specific tests. It is excluded to prevent running twice. +org.apache.qpid.server.store.MessageStoreTest#* diff --git a/qpid/java/test-profiles/JavaDerbyExcludes b/qpid/java/test-profiles/JavaDerbyExcludes new file mode 100644 index 0000000000..3caa360d48 --- /dev/null +++ b/qpid/java/test-profiles/JavaDerbyExcludes @@ -0,0 +1,21 @@ +// +// 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. +// + +org.apache.qpid.server.store.berkeleydb.BDBMessageStoreTest#* +org.apache.qpid.server.store.berkeleydb.BDBUpgradeTest#* diff --git a/qpid/java/test-profiles/JavaTransientExcludes b/qpid/java/test-profiles/JavaTransientExcludes index 2ea46795d9..67190a6fcc 100644 --- a/qpid/java/test-profiles/JavaTransientExcludes +++ b/qpid/java/test-profiles/JavaTransientExcludes @@ -19,6 +19,7 @@ //These tests require a persistent store org.apache.qpid.server.store.PersistentStoreTest#* +org.apache.qpid.test.unit.ack.ClientAcknowledgeTest#testClientAckWithLargeFlusherPeriod org.apache.qpid.test.unit.ct.DurableSubscriberTest#* @@ -30,3 +31,6 @@ org.apache.qpid.server.store.MessageStoreTest#testQueuePersistence org.apache.qpid.server.store.MessageStoreTest#testDurableQueueRemoval org.apache.qpid.server.store.MessageStoreTest#testExchangePersistence org.apache.qpid.server.store.MessageStoreTest#testDurableExchangeRemoval + +org.apache.qpid.server.store.berkeleydb.BDBMessageStoreTest#* +org.apache.qpid.server.store.berkeleydb.BDBUpgradeTest#* diff --git a/qpid/java/test-profiles/cpp.testprofile b/qpid/java/test-profiles/cpp.testprofile index a2114f82ba..7cc7fdc821 100644 --- a/qpid/java/test-profiles/cpp.testprofile +++ b/qpid/java/test-profiles/cpp.testprofile @@ -33,7 +33,7 @@ broker.modules= broker.args= broker.type=spawned -broker.command=${broker.executable} -p @PORT --data-dir ${build.data}/@PORT -t --auth no --no-module-dir ${broker.modules} ${broker.args} +broker.command=${broker.executable} -p @PORT --data-dir ${project.root}/build/work/@PORT -t --auth no --no-module-dir ${broker.modules} ${broker.args} profile.excludes=CPPPrefetchExcludes CPPTransientExcludes test.excludes=Excludes CPPExcludes ${profile}.excludes ${profile.excludes} cpp.excludes diff --git a/qpid/java/test-profiles/java-bdb-spawn.0-10.testprofile b/qpid/java/test-profiles/java-bdb-spawn.0-10.testprofile new file mode 100644 index 0000000000..cba348b67f --- /dev/null +++ b/qpid/java/test-profiles/java-bdb-spawn.0-10.testprofile @@ -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. +# + +broker.language=java +broker.type=spawned +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml +broker.ready=BRK-1004 +broker.stopped=Exception +broker.config=build/etc/config-systests-bdb.xml +messagestore.class.name=org.apache.qpid.server.store.berkeleydb.BDBMessageStore +profile.excludes=JavaExcludes JavaPersistentExcludes Java010Excludes JavaBDBExcludes +broker.clean.between.tests=true +broker.persistent=true +broker.version=v0_10 + diff --git a/qpid/java/test-profiles/java-bdb-spawn.0-9-1.testprofile b/qpid/java/test-profiles/java-bdb-spawn.0-9-1.testprofile new file mode 100644 index 0000000000..b04fea21b6 --- /dev/null +++ b/qpid/java/test-profiles/java-bdb-spawn.0-9-1.testprofile @@ -0,0 +1,36 @@ +# +# 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. +# + +broker.language=java +broker.type=spawned +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml +broker.ready=BRK-1004 +broker.stopped=Exception +broker.config=build/etc/config-systests-bdb.xml +messagestore.class.name=org.apache.qpid.server.store.berkeleydb.BDBMessageStore +profile.excludes=JavaExcludes JavaPersistentExcludes JavaPre010Excludes JavaBDBExcludes +broker.clean.between.tests=true +broker.persistent=true +broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT +broker.version=v0_9_1 +# +# Do not enable. Allow client to attempt 0-10 and negotiate downwards +# +#qpid.amqp.version=0-91 + diff --git a/qpid/java/test-profiles/java-bdb.0-10.testprofile b/qpid/java/test-profiles/java-bdb.0-10.testprofile new file mode 100644 index 0000000000..3ef93a68cb --- /dev/null +++ b/qpid/java/test-profiles/java-bdb.0-10.testprofile @@ -0,0 +1,32 @@ +# +# 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. +# + +broker.language=java +broker.type=internal +#broker.command only used for the second broker during failover tests in this profile +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml +broker.ready=BRK-1004 +broker.stopped=Exception +broker.config=build/etc/config-systests-bdb.xml +messagestore.class.name=org.apache.qpid.server.store.berkeleydb.BDBMessageStore +profile.excludes=JavaExcludes JavaPersistentExcludes Java010Excludes JavaBDBExcludes +broker.clean.between.tests=true +broker.persistent=true +broker.version=v0_10 + diff --git a/qpid/java/test-profiles/java-bdb.0-9-1.testprofile b/qpid/java/test-profiles/java-bdb.0-9-1.testprofile new file mode 100644 index 0000000000..101d38f4b9 --- /dev/null +++ b/qpid/java/test-profiles/java-bdb.0-9-1.testprofile @@ -0,0 +1,37 @@ +# +# 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. +# + +broker.language=java +broker.type=internal +#broker.command only used for the second broker during failover tests in this profile +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml +broker.ready=BRK-1004 +broker.stopped=Exception +broker.config=build/etc/config-systests-bdb.xml +messagestore.class.name=org.apache.qpid.server.store.berkeleydb.BDBMessageStore +profile.excludes=JavaExcludes JavaPersistentExcludes JavaPre010Excludes JavaBDBExcludes +broker.clean.between.tests=true +broker.persistent=true +broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT +broker.version=v0_9_1 +# +# Do not enable. Allow client to attempt 0-10 and negotiate downwards +# +#qpid.amqp.version=0-91 + diff --git a/qpid/java/test-profiles/java-dby-spawn.0-10.testprofile b/qpid/java/test-profiles/java-dby-spawn.0-10.testprofile index f51f5a26ac..5bd6f330d5 100644 --- a/qpid/java/test-profiles/java-dby-spawn.0-10.testprofile +++ b/qpid/java/test-profiles/java-dby-spawn.0-10.testprofile @@ -19,12 +19,11 @@ broker.language=java broker.version=v0_10 broker.type=spawned -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception -broker.config=${project.root}/build/etc/config-systests-derby.xml +broker.config=build/etc/config-systests-derby.xml messagestore.class.name=org.apache.qpid.server.store.DerbyMessageStore -profile.excludes=JavaPersistentExcludes Java010Excludes +profile.excludes=JavaPersistentExcludes JavaDerbyExcludes Java010Excludes broker.clean.between.tests=true broker.persistent=true diff --git a/qpid/java/test-profiles/java-dby-spawn.0-9-1.testprofile b/qpid/java/test-profiles/java-dby-spawn.0-9-1.testprofile index 1580cec1c5..e7212d30f8 100644 --- a/qpid/java/test-profiles/java-dby-spawn.0-9-1.testprofile +++ b/qpid/java/test-profiles/java-dby-spawn.0-9-1.testprofile @@ -19,14 +19,13 @@ broker.version=v0_9_1 broker.language=java broker.type=spawned -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception -broker.config=${project.root}/build/etc/config-systests-derby.xml +broker.config=build/etc/config-systests-derby.xml broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT messagestore.class.name=org.apache.qpid.server.store.DerbyMessageStore -profile.excludes=JavaPersistentExcludes JavaPre010Excludes +profile.excludes=JavaPersistentExcludes JavaDerbyExcludes JavaPre010Excludes broker.clean.between.tests=true broker.persistent=true # diff --git a/qpid/java/test-profiles/java-dby.0-10.testprofile b/qpid/java/test-profiles/java-dby.0-10.testprofile index 5a7b9b5cdc..9c23d18f45 100644 --- a/qpid/java/test-profiles/java-dby.0-10.testprofile +++ b/qpid/java/test-profiles/java-dby.0-10.testprofile @@ -19,12 +19,12 @@ broker.language=java broker.version=v0_10 broker.type=internal -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +#broker.command only used for the second broker during failover tests in this profile +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception -broker.config=${project.root}/build/etc/config-systests-derby.xml +broker.config=build/etc/config-systests-derby.xml messagestore.class.name=org.apache.qpid.server.store.DerbyMessageStore -profile.excludes=JavaPersistentExcludes Java010Excludes +profile.excludes=JavaPersistentExcludes JavaDerbyExcludes Java010Excludes broker.clean.between.tests=true broker.persistent=true diff --git a/qpid/java/test-profiles/java-dby.0-9-1.testprofile b/qpid/java/test-profiles/java-dby.0-9-1.testprofile index b4d506df05..83f43d8dbf 100644 --- a/qpid/java/test-profiles/java-dby.0-9-1.testprofile +++ b/qpid/java/test-profiles/java-dby.0-9-1.testprofile @@ -19,14 +19,14 @@ broker.version=v0_9_1 broker.language=java broker.type=internal -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +#broker.command only used for the second broker during failover tests in this profile +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception -broker.config=${project.root}/build/etc/config-systests-derby.xml +broker.config=build/etc/config-systests-derby.xml broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT messagestore.class.name=org.apache.qpid.server.store.DerbyMessageStore -profile.excludes=JavaPersistentExcludes JavaPre010Excludes +profile.excludes=JavaPersistentExcludes JavaDerbyExcludes JavaPre010Excludes broker.clean.between.tests=true broker.persistent=true # diff --git a/qpid/java/test-profiles/java-mms-spawn.0-10.testprofile b/qpid/java/test-profiles/java-mms-spawn.0-10.testprofile index 4fdac783cc..5e5d2e8a6b 100644 --- a/qpid/java/test-profiles/java-mms-spawn.0-10.testprofile +++ b/qpid/java/test-profiles/java-mms-spawn.0-10.testprofile @@ -19,8 +19,7 @@ broker.version=v0_10 broker.language=java broker.type=spawned -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception # diff --git a/qpid/java/test-profiles/java-mms-spawn.0-9-1.testprofile b/qpid/java/test-profiles/java-mms-spawn.0-9-1.testprofile index 4563600ba1..05b1f89452 100644 --- a/qpid/java/test-profiles/java-mms-spawn.0-9-1.testprofile +++ b/qpid/java/test-profiles/java-mms-spawn.0-9-1.testprofile @@ -19,8 +19,7 @@ broker.version=v0_9_1 broker.language=java broker.type=spawned -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT diff --git a/qpid/java/test-profiles/java-mms.0-10.testprofile b/qpid/java/test-profiles/java-mms.0-10.testprofile index 3ccc6dfd3b..209d384422 100644 --- a/qpid/java/test-profiles/java-mms.0-10.testprofile +++ b/qpid/java/test-profiles/java-mms.0-10.testprofile @@ -19,8 +19,8 @@ broker.language=java broker.version=v0_10 broker.type=internal -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +#broker.command only used for the second broker during failover tests in this profile +broker.command=build/bin/qpid-server -p @PORT -m @MPORT -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception diff --git a/qpid/java/test-profiles/java-mms.0-9-1.testprofile b/qpid/java/test-profiles/java-mms.0-9-1.testprofile index cec02f3aa6..37efa097bb 100644 --- a/qpid/java/test-profiles/java-mms.0-9-1.testprofile +++ b/qpid/java/test-profiles/java-mms.0-9-1.testprofile @@ -20,8 +20,7 @@ broker.language=java broker.version=v0_9_1 broker.type=internal #broker.command only used for the second broker during failover tests in this profile -broker.command=${project.root}/build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l ${test.profiles}/log4j-test.xml -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work +broker.command=build/bin/qpid-server -p @PORT -m @MPORT @EXCLUDES -c @CONFIG_FILE -l test-profiles/log4j-test.xml broker.ready=BRK-1004 broker.stopped=Exception broker.protocol.excludes=--exclude-0-10 @PORT --exclude-0-10 @SSL_PORT diff --git a/qpid/java/test-profiles/testprofile.defaults b/qpid/java/test-profiles/testprofile.defaults index 151e904930..bd2faa7915 100644 --- a/qpid/java/test-profiles/testprofile.defaults +++ b/qpid/java/test-profiles/testprofile.defaults @@ -19,9 +19,8 @@ java.naming.factory.initial=org.apache.qpid.jndi.PropertiesFileInitialContextFactory java.naming.provider.url=${test.profiles}/test-provider.properties -broker.clean=${test.profiles}/clean-dir ${build.data} ${project.root}/build/work broker.ready=Listening on TCP -broker.config=${project.root}/build/etc/config-systests.xml +broker.config=build/etc/config-systests.xml messagestore.class.name=org.apache.qpid.server.store.MemoryMessageStore broker.protocol.excludes= broker.persistent=false |