diff options
author | Martin Ritchie <ritchiem@apache.org> | 2009-04-14 15:54:16 +0000 |
---|---|---|
committer | Martin Ritchie <ritchiem@apache.org> | 2009-04-14 15:54:16 +0000 |
commit | ab8ba0dfc8979ac2fbba266b42e16f71c52276fe (patch) | |
tree | 80ffdaea6a17003b904f569748544ac8d4829b2a | |
parent | 3e7f6b624a7131c352e2240495da116f060fafc9 (diff) | |
download | qpid-python-ab8ba0dfc8979ac2fbba266b42e16f71c52276fe.tar.gz |
QPID-1807 : Add 0.5-fix broker and update SlowMessageStore to use MessageStores rather than TransactionLogs
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@764850 13f79535-47bb-0310-9956-ffa450edef68
369 files changed, 60356 insertions, 66 deletions
diff --git a/qpid/java/broker/bin/create-example-ssl-stores.bat b/qpid/java/broker/bin/create-example-ssl-stores.bat new file mode 100644 index 0000000000..5419c098d5 --- /dev/null +++ b/qpid/java/broker/bin/create-example-ssl-stores.bat @@ -0,0 +1,36 @@ +@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+@REM Create example keystore for broker and trust store for client/management console.
+@REM
+@REM Use generated qpid.keystore as the brokers keystore
+@REM Use generated qpid.truststore as client/consoles truststore
+@REM All passwords have value: password
+
+@REM Create Broker Keystore:
+keytool -genkey -alias qpidBroker -keyalg RSA -validity 365 -keystore qpid.keystore -storepass password -keypass password -dname "CN=hostname, OU=OrgUnit, O=Org, L=City, C=US"
+
+@REM Export Self Signed Cert:
+keytool -export -alias qpidBroker -keystore qpid.keystore -file qpidBroker.cer -storepass password
+
+@REM Import Broker Cert Into MC TrustStore:
+keytool -import -alias qpidBrokerCert -file qpidBroker.cer -keystore qpid.truststore -storepass password -noprompt
+
+@REM Delete the cert
+del qpidBroker.cer
\ No newline at end of file diff --git a/qpid/java/broker/bin/create-example-ssl-stores.sh b/qpid/java/broker/bin/create-example-ssl-stores.sh new file mode 100644 index 0000000000..bfcb3dfecf --- /dev/null +++ b/qpid/java/broker/bin/create-example-ssl-stores.sh @@ -0,0 +1,38 @@ +#!/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. +# + +# Create example keystore for broker and trust store for client/management console. +# +# Use generated qpid.keystore as the brokers keystore +# Use generated qpid.truststore as client/consoles truststore +# All passwords have value: password + +#Create Broker Keystore: +keytool -genkey -alias qpidBroker -keyalg RSA -validity 365 -keystore qpid.keystore \ +-storepass password -keypass password -dname "CN=hostname, OU=OrgUnit, O=Org, L=City, C=US" + +#Export Self Signed Cert: +keytool -export -alias qpidBroker -keystore qpid.keystore -file qpidBroker.cer -storepass password + +#Import Broker Cert Into MC TrustStore: +keytool -import -alias qpidBrokerCert -file qpidBroker.cer -keystore qpid.truststore -storepass password -noprompt + +#Delete the cert +rm qpidBroker.cer diff --git a/qpid/java/broker/bin/msTool.sh b/qpid/java/broker/bin/msTool.sh new file mode 100755 index 0000000000..e190a0a46a --- /dev/null +++ b/qpid/java/broker/bin/msTool.sh @@ -0,0 +1,60 @@ +#!/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. +# + +die() { + if [[ $1 = -usage ]]; then + shift + usage=true + else + usage=false + fi + echo "$@" + $usage && echo + $usage && usage + exit 1 +} + +cygwin=false +if [[ "$(uname -a | fgrep Cygwin)" != "" ]]; then + cygwin=true +fi + +if [ -z "$QPID_TOOLS" ]; then + if [ -z "$QPID_HOME" ]; then + die "QPID_TOOLS must be set" + else + QPID_TOOLS=$QPID_HOME + fi +fi + +if $cygwin; then + QPID_TOOLS=$(cygpath -w $QPID_TOOLS) +fi + +# Set classpath to include Qpid jar with all required jars in manifest +QPID_LIBS=$QPID_TOOLS/lib/qpid-all.jar + +# Set other variables used by the qpid-run script before calling +export JAVA=java \ + JAVA_VM=-server \ + JAVA_OPTS=-Dlog4j.configuration=file:$QPID_TOOLS/etc/mstool-log4j.xml \ + QPID_CLASSPATH=$QPID_LIBS + +. qpid-run org.apache.qpid.tools.messagestore.MessageStoreTool "$@" diff --git a/qpid/java/broker/bin/qpid-passwd b/qpid/java/broker/bin/qpid-passwd new file mode 100755 index 0000000000..63b30b5e71 --- /dev/null +++ b/qpid/java/broker/bin/qpid-passwd @@ -0,0 +1,35 @@ +#!/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
+
+# Set classpath to include Qpid jar with all required jars in manifest
+QPID_LIBS=$QPID_HOME/lib/qpid-all.jar
+
+# Set other variables used by the qpid-run script before calling
+export JAVA=java \
+ JAVA_VM=-server \
+ JAVA_MEM=-Xmx1024m \
+ QPID_CLASSPATH=$QPID_LIBS
+
+. qpid-run org.apache.qpid.tools.security.Passwd "$@"
diff --git a/qpid/java/broker/bin/qpid-server b/qpid/java/broker/bin/qpid-server new file mode 100755 index 0000000000..e5a9e998e2 --- /dev/null +++ b/qpid/java/broker/bin/qpid-server @@ -0,0 +1,39 @@ +#!/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 + +# Set classpath to include Qpid jar with all required jars in manifest +QPID_LIBS=$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/bdbstore-launch.jar + +# Set other variables used by the qpid-run script before calling +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + JAVA_GC="-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError" \ + QPID_CLASSPATH=$QPID_LIBS \ + QPID_RUN_LOG=2 + +QPID_OPTS="$QPID_OPTS -Damqj.read_write_pool_size=32" + +. qpid-run org.apache.qpid.server.Main "$@" diff --git a/qpid/java/broker/bin/qpid-server-bdb.bat b/qpid/java/broker/bin/qpid-server-bdb.bat new file mode 100755 index 0000000000..8964e577df --- /dev/null +++ b/qpid/java/broker/bin/qpid-server-bdb.bat @@ -0,0 +1,22 @@ +@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+set BDBSTORE_HOME=c:\qpid\trunk\java\bdbstore
+set QPID_MODULE_JARS=%BDBSTORE_HOME%\target\qpid-bdbstore-1.0-incubating-M2-SNAPSHOT.jar;%BDBSTORE_HOME%\lib\bdb\je-3.1.0.jar
+.\qpid-server.bat
\ No newline at end of file diff --git a/qpid/java/broker/bin/qpid-server.bat b/qpid/java/broker/bin/qpid-server.bat new file mode 100755 index 0000000000..2687baa111 --- /dev/null +++ b/qpid/java/broker/bin/qpid-server.bat @@ -0,0 +1,203 @@ +@REM
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM
+
+@echo off
+REM Script to run the Qpid Java Broker
+
+rem Guess QPID_HOME if not defined
+set CURRENT_DIR=%cd%
+if not "%QPID_HOME%" == "" goto gotHome
+set QPID_HOME=%CURRENT_DIR%
+echo %QPID_HOME%
+if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome
+cd ..
+set QPID_HOME=%cd%
+cd %CURRENT_DIR%
+:gotHome
+if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome
+echo The QPID_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program
+goto end
+:okHome
+
+REM set QPID_WORK if not set
+if not "%QPID_WORK%" == "" goto okQpidWork
+if "%HOME%" == "" goto noHome
+set QPID_WOKR=%HOME%
+goto okQpidWork
+
+:noHome
+set QPID_WORK=c:\Temp
+if not exist %QPID_WORK% md %QPID_WORK%
+:okQpidWork
+
+if not "%JAVA_HOME%" == "" goto gotJavaHome
+echo The JAVA_HOME environment variable is not defined
+echo This environment variable is needed to run this program
+goto end
+:gotJavaHome
+if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
+goto okJavaHome
+:noJavaHome
+echo The JAVA_HOME environment variable is not defined correctly
+echo This environment variable is needed to run this program.
+goto end
+:okJavaHome
+
+REM set loggin level if not set
+if "%AMQJ_LOGGING_LEVEL%" == "" set AMQJ_LOGGING_LEVEL=info
+
+REM Set the default system properties that we'll use now that they have
+REM all been initialised
+set SYSTEM_PROPS=-Damqj.logging.level=%AMQJ_LOGGING_LEVEL% -DQPID_HOME=%QPID_HOME% -DQPID_WORK=%QPID_WORK%
+
+if "%EXTERNAL_CLASSPATH%" == "" set EXTERNAL_CLASSPATH=%CLASSPATH%
+
+REM Use QPID_CLASSPATH if set
+if "%QPID_CLASSPATH%" == "" goto noQpidClasspath
+set CLASSPATH=%QPID_CLASSPATH%
+echo Using CLASSPATH: %CLASSPATH%
+goto afterQpidClasspath
+
+:noQpidClasspath
+echo Warning: Qpid classpath not set. CLASSPATH set to %QPID_HOME%\lib\qpid-all.jar
+set CLASSPATH=%QPID_HOME%\lib\qpid-all.jar
+:afterQpidClasspath
+
+REM start parsing -run arguments
+set QPID_ARGS=
+if "%1" == "" goto endRunArgs
+:runLoop
+set var=%1
+if "%var:~0,5%" == "-run:" goto runFound
+set QPID_ARGS=%QPID_ARGS% %1
+:beforeRunShift
+shift
+if not "%1"=="" goto runLoop
+goto endRunArgs
+
+:runFound
+if "%var%" == "-run:debug" goto runDebug
+if "%var%" == "-run:jpda" goto runJdpa
+if "%var:~0,24%" == "-run:external-classpath-" goto runExternalClasspath
+if "%var%" == "-run:print-classpath" goto runPrintCP
+if "%var%" == "-run:help" goto runHelp
+echo "unrecognized -run option '%var%'. For using external classpaths use -run:external-classpath-option"
+goto end
+
+:runDebug
+REM USAGE: print the classpath and command before running it
+set debug=true
+goto beforeRunShift
+
+:runJdpa
+REM USAGE: adds debugging options to the java command, use
+REM USAGE: JDPA_TRANSPORT and JPDA_ADDRESS to customize the debugging
+REM USAGE: behavior and use JPDA_OPTS to override it entirely
+if "%JPDA_OPTS%" == "" goto beforeRunShift
+if "%JPDA_TRANSPORT%" == "" set JPDA_TRANSPORT=-dt_socket
+if "%JPDA_ADDRESS%" == "" set JPDA_ADDRESS=8000
+set JPDA_OPTS="-Xdebug -Xrunjdwp:transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=n"
+set QPID_OPTS="%QPID_OPTS% %JPDA_OPTS%"
+goto beforeRunShift
+
+:runExternalClasspath
+echo Using external classpath %var%
+REM USAGE: Format is -run:external-classpath-first/last/ignore/only as equals special in DOS
+REM USAGE: controls how the CLASSPATH environment variable is used by
+REM USAGE: this script, value can be one of ignore (the default), first,
+REM USAGE: last, and only
+if "%var%" == "-run:external-classpath-ignore" goto beforeRunShift
+if "%var%" == "-run:external-classpath-first" goto extCPFirst
+if "%var%" == "-run:external-classpath-last" goto extCPLast
+if "%var%" == "-run:external-classpath-only" goto extCPOnly
+echo Invalid value provided for external classpath.
+goto end
+
+:extCPFirst
+set CLASSPATH=%EXTERNAL_CLASSPATH%;%CLASSPATH%
+goto beforeRunShift
+
+:extCPLast
+set CLASSPATH=%CLASSPATH%;%EXTERNAL_CLASSPATH%
+goto beforeRunShift
+
+:extCPonly
+set CLASSPATH=%EXTERNAL_CLASSPATH%
+goto beforeRunShift
+
+:runPrintCP
+REM USAGE: print the classpath
+echo %CLASSPATH%
+goto beforeRunShift
+
+:runHelp
+REM USAGE: print this message
+echo -------------------------------------------------------------------------------------------
+echo -run:option where option can be the following.
+echo debug : Prints classpath and command before running it
+echo jpda : Adds remote debugging info using JPDA_OPTS. Use JPDA_TRANSPORT and JPDA_ADDRESS to
+echo customize, JPDA_OPTS to override
+echo external-classpath : Valid values are: ignore, first, last and only.
+echo print-classpath : Prints classpath before running command
+echo help : Prints this message
+echo --------------------------------------------------------------------------------------------
+goto end
+
+REM end parsing -run arguments
+:endRunArgs
+
+set JAVA_VM=-server
+set JAVA_MEM=-Xmx1024m
+set JAVA_GC=-XX:+UseConcMarkSweepGC
+rem removing the following vm arg from JAVA_GC as it is supported on ly in Java 1.6
+rem -XX:+HeapDumpOnOutOfMemoryError"
+
+REM Use QPID_JAVA_GC if set
+if "%QPID_JAVA_GC%" == "" goto noQpidJavaGC
+set JAVA_GC=%QPID_JAVA_GC%
+echo Using QPID_JAVA_GC setting: %QPID_JAVA_GC%
+goto afteQpidJavaGC
+
+:noQPidJavaGC
+echo Info: QPID_JAVA_GC not set. Defaulting to JAVA_GC %JAVA_GC%
+:afterQpidJavaGC
+
+REM Use QPID_JAVA_MEM if set
+if "%QPID_JAVA_MEM%" == "" goto noQpidJavaMem
+set JAVA_MEM=%QPID_JAVA_MEM%
+echo Using QPID_JAVA_MEM setting: %QPID_JAVA_MEM%
+goto afterQpidJavaMem
+
+:noQpidJavaMem
+echo Info: QPID_JAVA_MEM not set. Defaulting to JAVA_MEM %JAVA_MEM%
+:after QpidJavaMem
+
+
+rem QPID_OPTS intended to hold any -D props for use
+rem user must enclose any value for QPID_OPTS in double quotes
+:runCommand
+set MODULE_JARS=%QPID_MODULE_JARS%
+set COMMAND="%JAVA_HOME%\bin\java" %JAVA_VM% %JAVA_MEM% %JAVA_GC% %QPID_OPTS% %SYSTEM_PROPS% -cp "%CLASSPATH%;%MODULE_JARS%" org.apache.qpid.server.Main %QPID_ARGS%
+
+if "%debug%" == "true" echo %CLASSPATH%;%LAUNCH_JAR%;%MODULE_JARS%
+if "%debug%" == "true" echo %COMMAND%
+%COMMAND%
+
+:end
diff --git a/qpid/java/broker/bin/qpid.start b/qpid/java/broker/bin/qpid.start new file mode 100755 index 0000000000..78c34e70b4 --- /dev/null +++ b/qpid/java/broker/bin/qpid.start @@ -0,0 +1,21 @@ +#!/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.
+#
+
+exec qpid-server -run:debug "$@"
\ No newline at end of file diff --git a/qpid/java/broker/bin/qpid.stop b/qpid/java/broker/bin/qpid.stop new file mode 100755 index 0000000000..316f8dff46 --- /dev/null +++ b/qpid/java/broker/bin/qpid.stop @@ -0,0 +1,178 @@ +#!/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. +# + +# qpid.stop Script +# +# Script checks for a given pid running DEFAULT_SEARCH and attempts to quit it +# + +MAX_ATTEMPTS=2 +SLEEP_DELAY=1 +DEFAULT_SEARCH="PNAME=QPBRKR" + +if [ -z "$QPID_STOP_SEARCH" ]; then + SEARCH=$DEFAULT_SEARCH; +else + SEARCH=$QPID_STOP_SEARCH; +fi + +# +# Forcably Quit the specified PID($1) +# +forceQuit() +{ +kill -9 $1 +} + +# +# Gracefully ask the PID($1) to quit +# +quit() +{ +kill $1 +} + +# +# grep for the session ID ($1) and return 0 for successful quit and 1 for process alive +# +lookup_pid() +{ +result=`ps -e | grep $1 | wc -l` +} + +# +# grep ps for all instances of $SEARCH for the current user and collect PIDs +# +lookup_all_pids() +{ +pids=`pgrep -f -U $USER $SEARCH` +result_all=`echo -n $pids | wc -w` +} + +# +# check that the PID passed in is for a Qpid broker owned by this user and alive +# +validate_pid() +{ +result=`pgrep -fl $SEARCH | grep $1 | wc -l` +} + +# +# Show the PS output for given set of pids +# +showPids() +{ +ps -o user,pid,args -p $pids +} + +# +# Sleep and then check then lookup the PID($1) to ensure it has quit +# +check() +{ +echo "Waiting $SLEEP_DELAY second for $1 to exit" +sleep $SLEEP_DELAY +lookup_pid $1 +} + +# +# Verify the PID($1) is available +# +verifyPid() +{ +validate_pid $1 +if [[ $[$result] == 1 ]] ; then + brokerspid=$1 +else + echo "Unable to locate Qpid Broker Process with PID $1. Check PID and try again." + exit -1 +fi +} + +# +# Stops all Qpid brokers for current user +# +qpid_stopall_brokers() +{ +for pid in $pids ; do + lookup_pid $pid; + brokerspid=$pid; + stop_broker $pid; +done +} + +# +# Stops Qpid broker with brokerspid id +# +stop_broker() +{ +# Attempt to quit the process MAX_ATTEMPTS Times +attempt=0 +while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do + quit $brokerspid + check $brokerspid + attempt=$[$attempt + 1] +done + +# Check that it has quit +if [[ $[$result] == 0 ]] ; then + echo "Process quit" +else + + attempt=0 + # Now attempt to force quit the process + while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do + forceQuit $brokerspid + check $brokerspid + attempt=$[$attempt + 1] + done + + # Output final status + if [[ $[$result] > 0 && $[$attempt] == $[$MAX_ATTEMPTS] ]] ; then + echo "Stopped trying to kill process: $brokerspid" + echo "Attempted to stop $attempt times" + else + echo "Done " + fi +fi + +} + +# +# Main Run +# + +# Check if we are killing all qpid pids or just one. +# Now uses local function qpid_stopall_brokers +if [[ $# == 0 ]] ; then + lookup_all_pids + if [[ $[$result_all] > 0 ]] ; then + echo "Killing All Qpid Brokers for user: '$USER'" + qpid_stopall_brokers + else + echo "No Qpid Brokers found running for user: " $USER + fi + exit $result +else + verifyPid $1 + stop_broker + exit $result +fi + diff --git a/qpid/java/broker/bin/qpid.stopall b/qpid/java/broker/bin/qpid.stopall new file mode 100755 index 0000000000..b0ad506629 --- /dev/null +++ b/qpid/java/broker/bin/qpid.stopall @@ -0,0 +1,27 @@ +#!/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. +# + +# qpid.stopall script +# +# Script attempts to stop all PROGRAMs running under the current user +# Utilises qpid.stop to perform the actual stopping +# + +qpid.stop $* diff --git a/qpid/java/broker/build.xml b/qpid/java/broker/build.xml new file mode 100644 index 0000000000..26dcde2918 --- /dev/null +++ b/qpid/java/broker/build.xml @@ -0,0 +1,57 @@ +<!-- + - + - 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="AMQ Broker" default="build"> + + <property name="module.depends" value="common"/> + <property name="module.main" value="org.apache.qpid.server.Main"/> + + <import file="../module.xml"/> + + <property name="output.dir" value="${module.precompiled}/org/apache/qpid/server/filter/jms/selector"/> + + + <target name="precompile"> + <mkdir dir="${output.dir}"/> + <javacc target="src/main/grammar/SelectorParser.jj" + outputdirectory="${output.dir}" + javacchome="${project.root}/lib"/> + </target> + + <target name="copy-etc-release" if="module.etc.exists" description="copy etc directory if it exists to build tree"> + <copy todir="${module.release}/etc" failonerror="false" flatten="true"> + <fileset dir="${module.etc}" excludes="*.conf,*.jpp"/> + </copy> + </target> + + <target name="copy-bin-release" description="copy dependencies into module release"> + <copy todir="${module.release}/bin" failonerror="true"> + <fileset dir="${module.bin}"/> + </copy> + <copy todir="${module.release}/bin" failonerror="true" flatten="true"> + <fileset dir="${basedir}/../common/bin"/> + </copy> + <chmod dir="${module.release}/bin" perm="ugo+rx" includes="**/*"/> + + </target> + + <target name="release-bin" depends="release-bin-tasks"/> + +</project> diff --git a/qpid/java/broker/etc/access b/qpid/java/broker/etc/access new file mode 100644 index 0000000000..58b7443fa9 --- /dev/null +++ b/qpid/java/broker/etc/access @@ -0,0 +1,19 @@ +#
+# 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.
+
+guest:localhost(rw),test(rw)
\ No newline at end of file diff --git a/qpid/java/broker/etc/acl.config.xml b/qpid/java/broker/etc/acl.config.xml new file mode 100644 index 0000000000..a2b723fc63 --- /dev/null +++ b/qpid/java/broker/etc/acl.config.xml @@ -0,0 +1,230 @@ +<?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> + <prefix>${QPID_HOME}</prefix> + <work>${QPID_WORK}</work> + <conf>${prefix}/etc</conf> + <connector> + <!-- Uncomment out this block and edit the keystorePath and keystorePassword + to enable SSL support + <ssl> + <enabled>true</enabled> + <sslOnly>true</sslOnly> + <keystorePath>/path/to/keystore.ks</keystorePath> + <keystorePassword>keystorepass</keystorePassword> + </ssl>--> + <qpidnio>false</qpidnio> + <!-- I've had the 0.0 and 0.1 Reader threads continually throwing IOException when client closes--> + <protectio>false</protectio> + <transport>nio</transport> + <port>5672</port> + <sslport>8672</sslport> + <socketReceiveBuffer>32768</socketReceiveBuffer> + <socketSendBuffer>32768</socketSendBuffer> + </connector> + <management> + <enabled>false</enabled> + <jmxport>8999</jmxport> + <security-enabled>false</security-enabled> + </management> + <advanced> + <filterchain enableExecutorPool="true"/> + <enablePooledAllocator>false</enablePooledAllocator> + <enableDirectBuffers>false</enableDirectBuffers> + <framesize>65535</framesize> + <compressBufferOnQueue>false</compressBufferOnQueue> + <enableJMSXUserID>false</enableJMSXUserID> + </advanced> + + <security> + <principal-databases> + <!-- Example use of Base64 encoded MD5 hashes for authentication via CRAM-MD5-Hashed --> + <principal-database> + <name>passwordfile</name> + <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class> + <attributes> + <attribute> + <name>passwordFile</name> + <value>${conf}/passwd</value> + </attribute> + </attributes> + </principal-database> + </principal-databases> + + <access> + <class>org.apache.qpid.server.security.access.plugins.DenyAll</class> + </access> + + <jmx> + <access>${conf}/jmxremote.access</access> + <principal-database>passwordfile</principal-database> + </jmx> + </security> + + <virtualhosts> + <directory>${conf}/virtualhosts</directory> + + <virtualhost> + <name>test</name> + <test> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + + <queues> + <exchange>amq.direct</exchange> + <!-- 4Mb --> + <maximumQueueDepth>4235264</maximumQueueDepth> + <!-- 2Mb --> + <maximumMessageSize>2117632</maximumMessageSize> + <!-- 10 mins --> + <maximumMessageAge>600000</maximumMessageAge> + </queues> + + + <security> + <access> + <class>org.apache.qpid.server.security.access.plugins.SimpleXML</class> + </access> + <access_control_list> + <!-- This section grants pubish rights to an exchange + routing key pair --> + <publish> + <exchanges> + <exchange> + <name>amq.direct</name> + <routing_keys> + + <!-- Allow clients to publish requests --> + <routing_key> + <value>example.RequestQueue</value> + <users> + <user>client</user> + </users> + </routing_key> + + <!-- Allow the processor to respond to a client on their Temporary Topic --> + <routing_key> + <value>tmp_*</value> + <users> + <user>server</user> + </users> + </routing_key> + <routing_key> + <value>TempQueue*</value> + <users> + <user>server</user> + </users> + </routing_key> + </routing_keys> + + </exchange> + </exchanges> + </publish> + + <!-- This section grants users the ability to consume from the broker --> + <consume> + <queues> + + <!-- Allow the clients to consume from their temporary queues--> + <queue> + <temporary/> + <users> + <user>client</user> + </users> + </queue> + + + <!-- Only allow the server to consume from the Request Queue--> + <queue> + <name>example.RequestQueue</name> + <users> + <user>server</user> + </users> + </queue> + + + </queues> + </consume> + + <!-- This section grants clients the ability to create queues and exchanges --> + <create> + <queues> + <!-- Allow clients to create temporary queues--> + <queue> + <temporary/> + <exchanges> + <exchange> + <name>amq.direct</name> + <users> + <user>client</user> + </users> + </exchange> + </exchanges> + </queue> + <!-- Allow the server to create the Request Queue--> + <queue> + <name>example.RequestQueue</name> + <users> + <user>server</user> + </users> + </queue> + + </queues> + </create> + + + </access_control_list> + + </security> + </test> + </virtualhost> + + + <virtualhost> + <name>development</name> + <development> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </development> + </virtualhost> + + <virtualhost> + <name>localhost</name> + <localhost> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </localhost> + </virtualhost> + + </virtualhosts> + + <heartbeat> + <delay>0</delay> + <timeoutFactor>2.0</timeoutFactor> + </heartbeat> + + <virtualhosts>${conf}/virtualhosts.xml</virtualhosts> +</broker> + + diff --git a/qpid/java/broker/etc/config.xml b/qpid/java/broker/etc/config.xml new file mode 100644 index 0000000000..e0045c1e74 --- /dev/null +++ b/qpid/java/broker/etc/config.xml @@ -0,0 +1,138 @@ +<?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> + <prefix>${QPID_HOME}</prefix> + <work>${QPID_WORK}</work> + <conf>${prefix}/etc</conf> + <connector> + <!-- Uncomment out this block and edit the keystorePath and keystorePassword + to enable SSL support + <ssl> + <enabled>true</enabled> + <sslOnly>true</sslOnly> + <keystorePath>/path/to/keystore.ks</keystorePath> + <keystorePassword>keystorepass</keystorePassword> + </ssl>--> + <qpidnio>false</qpidnio> + <protectio> + <enabled>false</enabled> + </protectio> + <transport>nio</transport> + <port>5672</port> + <sslport>8672</sslport> + <socketReceiveBuffer>32768</socketReceiveBuffer> + <socketSendBuffer>32768</socketSendBuffer> + </connector> + <management> + <enabled>true</enabled> + <jmxport>8999</jmxport> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> + </management> + <advanced> + <filterchain enableExecutorPool="true"/> + <enablePooledAllocator>false</enablePooledAllocator> + <enableDirectBuffers>false</enableDirectBuffers> + <framesize>65535</framesize> + <compressBufferOnQueue>false</compressBufferOnQueue> + <enableJMSXUserID>false</enableJMSXUserID> + </advanced> + + <security> + <principal-databases> + <!-- Example use of Base64 encoded MD5 hashes for authentication via CRAM-MD5-Hashed --> + <principal-database> + <name>passwordfile</name> + <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class> + <attributes> + <attribute> + <name>passwordFile</name> + <value>${conf}/passwd</value> + </attribute> + </attributes> + </principal-database> + </principal-databases> + + <access> + <class>org.apache.qpid.server.security.access.plugins.AllowAll</class> + </access> + + <msg-auth>false</msg-auth> + + <jmx> + <access>${conf}/jmxremote.access</access> + <principal-database>passwordfile</principal-database> + </jmx> + </security> + + <virtualhosts> + <directory>${conf}/virtualhosts</directory> + + <virtualhost> + <name>localhost</name> + <localhost> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + + <housekeeping> + <expiredMessageCheckPeriod>20000</expiredMessageCheckPeriod> + </housekeeping> + + </localhost> + </virtualhost> + + <virtualhost> + <name>development</name> + <development> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </development> + </virtualhost> + + <virtualhost> + <name>test</name> + <test> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </test> + </virtualhost> + + </virtualhosts> + <heartbeat> + <delay>0</delay> + <timeoutFactor>2.0</timeoutFactor> + </heartbeat> + <queue> + <auto_register>true</auto_register> + </queue> + + <virtualhosts>${conf}/virtualhosts.xml</virtualhosts> +</broker> + + diff --git a/qpid/java/broker/etc/debug.log4j.xml b/qpid/java/broker/etc/debug.log4j.xml new file mode 100644 index 0000000000..fc0bd9f34f --- /dev/null +++ b/qpid/java/broker/etc/debug.log4j.xml @@ -0,0 +1,114 @@ +<?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"> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="null" threshold="null"> + <appender class="org.apache.log4j.QpidCompositeRollingAppender" name="ArchivingFileAppender"> + <!-- Ensure that logs allways have the dateFormat set--> + <param name="StaticLogFileName" value="false"/> + <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/> + <param name="Append" value="false"/> + <!-- Change the direction so newer files have bigger numbers --> + <!-- So log.1 is written then log.2 etc This prevents a lot of file renames at log rollover --> + <param name="CountDirection" value="1"/> + <!-- Use default 10MB --> + <!--param name="MaxFileSize" value="100000"/--> + <param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm"/> + <!-- Unlimited number of backups --> + <param name="MaxSizeRollBackups" value="-1"/> + <!-- Compress(gzip) the backup files--> + <param name="CompressBackupFiles" value="true"/> + <!-- Compress the backup files using a second thread --> + <param name="CompressAsync" value="true"/> + <!-- Start at zero numbered files--> + <param name="ZeroBased" value="true"/> + <!-- Backup Location --> + <param name="backupFilesToPath" value="${QPID_WORK}/backup/log"/> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <appender class="org.apache.log4j.FileAppender" name="FileAppender"> + <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/> + <param name="Append" value="false"/> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <appender class="org.apache.log4j.FileAppender" name="AlertFile"> + <param name="File" value="${QPID_WORK}/log/alert.log"/> + <param name="Append" value="false"/> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <appender class="org.apache.log4j.ConsoleAppender" name="STDOUT"> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <category additivity="true" name="Qpid.Broker"> + <priority value="debug"/> + <appender-ref ref="AlertFile"/> + <!--appender-ref ref="STDOUT"/--> + </category> + + + <category additivity="true" name="org.apache.qpid.server.queue.AMQQueueMBean"> + <priority value="info"/> + <appender-ref ref="AlertFile"/> + </category> + + + <!-- Provide warnings to standard output --> + <!--category additivity="true" name="org.apache.qpid"> + <priority value="warn"/> + <appender-ref ref="STDOUT"/> + </category--> + + + <!-- Additional level settings for debugging --> + <!-- Each class in the Broker is a category that can have its logging level adjusted. --> + <!-- This will provide more details if available about that classes processing. --> + <!--category additivity="true" name="org.apache.qpid.server.txn"> + <priority value="debug"/> + </category>--> + + <!--<category additivity="true" name="org.apache.qpid.server.store"> + <priority value="debug"/> + </category--> + + <!-- Log all info events to file --> + <root> + <priority value="debug"/> + <appender-ref ref="STDOUT"/> + <!--appender-ref ref="FileAppender"/--> + </root> + +</log4j:configuration> diff --git a/qpid/java/broker/etc/jmxremote.access b/qpid/java/broker/etc/jmxremote.access new file mode 100644 index 0000000000..1a51a6991b --- /dev/null +++ b/qpid/java/broker/etc/jmxremote.access @@ -0,0 +1,23 @@ +#
+# 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.
+
+#Generated by JMX Console : Last edited by user:admin
+#Tue Jun 12 16:46:39 BST 2007
+admin=admin
+guest=readonly
+user=readwrite
diff --git a/qpid/java/broker/etc/log4j.xml b/qpid/java/broker/etc/log4j.xml new file mode 100644 index 0000000000..a395d0fd56 --- /dev/null +++ b/qpid/java/broker/etc/log4j.xml @@ -0,0 +1,98 @@ +<?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"> + +<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="null" threshold="null"> + <appender class="org.apache.log4j.QpidCompositeRollingAppender" name="ArchivingFileAppender"> + <!-- Ensure that logs allways have the dateFormat set--> + <param name="StaticLogFileName" value="false"/> + <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/> + <param name="Append" value="false"/> + <!-- Change the direction so newer files have bigger numbers --> + <!-- So log.1 is written then log.2 etc This prevents a lot of file renames at log rollover --> + <param name="CountDirection" value="1"/> + <!-- Use default 10MB --> + <!--param name="MaxFileSize" value="100000"/--> + <param name="DatePattern" value="'.'yyyy-MM-dd-HH-mm"/> + <!-- Unlimited number of backups --> + <param name="MaxSizeRollBackups" value="-1"/> + <!-- Compress(gzip) the backup files--> + <param name="CompressBackupFiles" value="true"/> + <!-- Compress the backup files using a second thread --> + <param name="CompressAsync" value="true"/> + <!-- Start at zero numbered files--> + <param name="ZeroBased" value="true"/> + <!-- Backup Location --> + <param name="backupFilesToPath" value="${QPID_WORK}/backup/log"/> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <appender class="org.apache.log4j.FileAppender" name="FileAppender"> + <param name="File" value="${QPID_WORK}/log/${logprefix}qpid${logsuffix}.log"/> + <param name="Append" value="false"/> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <appender class="org.apache.log4j.ConsoleAppender" name="STDOUT"> + + <layout class="org.apache.log4j.PatternLayout"> + <param name="ConversionPattern" value="%d %-5p [%t] %C{2} (%F:%L) - %m%n"/> + </layout> + </appender> + + <!-- Qpid.Broker log is a special log category used to log only useful broker startup details --> + <category additivity="true" name="Qpid.Broker"> + <priority value="debug"/> + <appender-ref ref="STDOUT"/> + </category> + + <category additivity="true" name="org.apache.qpid.server.queue.AMQQueueMBean"> + <priority value="info"/> + </category> + + <!-- Provide warnings to standard output --> + <category additivity="true" name="org.apache.qpid"> + <priority value="warn"/> + </category> + + + <!-- Examples of additional logging settings --> + <!-- Used to generate extra debug. See debug.log4j.xml --> + + <!--<category additivity="true" name="org.apache.qpid.server.store"> + <priority value="debug"/> + </category--> + + + <!-- Log all info events to file --> + <root> + <priority value="info"/> + <appender-ref ref="FileAppender"/> + <!--appender-ref ref="ArchivingFileAppender"/--> + </root> + +</log4j:configuration> diff --git a/qpid/java/broker/etc/md5passwd b/qpid/java/broker/etc/md5passwd new file mode 100644 index 0000000000..6a149919de --- /dev/null +++ b/qpid/java/broker/etc/md5passwd @@ -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.
+#
+guest:CE4DQ6BIb/BVMN9scFyLtA==
+admin:ISMvKXpXpadDiUoOSoAfww==
+user:aBzonUodYLhwSa8s9A10sA==
diff --git a/qpid/java/broker/etc/mstool-log4j.xml b/qpid/java/broker/etc/mstool-log4j.xml new file mode 100644 index 0000000000..8c46010e2d --- /dev/null +++ b/qpid/java/broker/etc/mstool-log4j.xml @@ -0,0 +1,54 @@ +<?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 [%t] %C{2} (%F:%L) - %m%n"/--> + <param name="ConversionPattern" value="%d %-5p [%t] (%F:%L) - %m%n"/> + </layout> + </appender> + + <category name="org.apache.qpid.tools"> + <priority value="info"/> + </category> + + <category name="org.apache.qpid"> + <priority value="error"/> + </category> + + <category name="org.apache.qpid.server.security"> + <priority value="error"/> + </category> + + <category name="org.apache.qpid.server.management"> + <priority value="error"/> + </category> + + + <root> + <priority value="info"/> + <appender-ref ref="STDOUT"/> + </root> +</log4j:configuration> diff --git a/qpid/java/broker/etc/passwd b/qpid/java/broker/etc/passwd new file mode 100644 index 0000000000..7aca438551 --- /dev/null +++ b/qpid/java/broker/etc/passwd @@ -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. +# +guest:guest +client:guest +server:guest + diff --git a/qpid/java/broker/etc/passwdVhost b/qpid/java/broker/etc/passwdVhost new file mode 100644 index 0000000000..48ce8299b6 --- /dev/null +++ b/qpid/java/broker/etc/passwdVhost @@ -0,0 +1,19 @@ +# +# 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. +# +guest:guest:localhost,test diff --git a/qpid/java/broker/etc/persistent_config.xml b/qpid/java/broker/etc/persistent_config.xml new file mode 100644 index 0000000000..67ef28117d --- /dev/null +++ b/qpid/java/broker/etc/persistent_config.xml @@ -0,0 +1,121 @@ +<?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. + - + + This is an example config using the BDBMessageStore available from + the Red Hat Messaging project at etp.108.redhat.com and distributed under GPL. + --> + +<broker> + <prefix>${QPID_HOME}</prefix> + <work>${QPID_WORK}</work> + <conf>${prefix}/etc</conf> + <connector> + <transport>nio</transport> + <port>5672</port> + <sslport>8672</sslport> + <socketReceiveBuffer>32768</socketReceiveBuffer> + <socketSendBuffer>32768</socketSendBuffer> + </connector> + <management> + <enabled>true</enabled> + <jmxport>8999</jmxport> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> + </management> + <advanced> + <filterchain enableExecutorPool="true"/> + <enablePooledAllocator>false</enablePooledAllocator> + <enableDirectBuffers>false</enableDirectBuffers> + <framesize>65535</framesize> + <compressBufferOnQueue>false</compressBufferOnQueue> + </advanced> + + <security> + <principal-databases> + <principal-database> + <name>passwordfile</name> + <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class> + <attributes> + <attribute> + <name>passwordFile</name> + <value>${conf}/passwd</value> + </attribute> + </attributes> + </principal-database> + </principal-databases> + + <access> + <class>org.apache.qpid.server.security.access.plugins.AllowAll</class> + </access> + <jmx> + <access>${conf}/jmxremote.access</access> + <principal-database>passwordfile</principal-database> + </jmx> + </security> + + <virtualhosts> + <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> + <heartbeat> + <delay>0</delay> + <timeoutFactor>2.0</timeoutFactor> + </heartbeat> + <queue> + <auto_register>true</auto_register> + </queue> + + <virtualhosts>${conf}/virtualhosts.xml</virtualhosts> +</broker> + + diff --git a/qpid/java/broker/etc/qpid-server.conf b/qpid/java/broker/etc/qpid-server.conf new file mode 100644 index 0000000000..8a16849b04 --- /dev/null +++ b/qpid/java/broker/etc/qpid-server.conf @@ -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. +# + +QPID_LIBS=$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/bdbstore-launch.jar + +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + CLASSPATH=$QPID_LIBS diff --git a/qpid/java/broker/etc/qpid-server.conf.jpp b/qpid/java/broker/etc/qpid-server.conf.jpp new file mode 100644 index 0000000000..3ed2431ef3 --- /dev/null +++ b/qpid/java/broker/etc/qpid-server.conf.jpp @@ -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. +# + +QPID_LIBS=$(build-classpath backport-util-concurrent \ + commons-beanutils \ + commons-beanutils-core \ + commons-cli \ + commons-codec \ + commons-collections \ + commons-configuration \ + commons-digester \ + commons-lang \ + commons-logging \ + commons-logging-api \ + dom4j \ + geronimo-jms-1.1-api \ + isorelax \ + jaxen \ + log4j \ + mina/core \ + mina/filter-ssl \ + mina/java5 \ + msv-msv \ + qpid-broker \ + qpid-client \ + qpid-common \ + relaxngDatatype \ + slf4j) + +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + CLASSPATH=$QPID_LIBS diff --git a/qpid/java/broker/etc/qpid.passwd b/qpid/java/broker/etc/qpid.passwd new file mode 100644 index 0000000000..dbfb9d1923 --- /dev/null +++ b/qpid/java/broker/etc/qpid.passwd @@ -0,0 +1,23 @@ +#
+# 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.
+#
+guest:CE4DQ6BIb/BVMN9scFyLtA==
+admin:ISMvKXpXpadDiUoOSoAfww==
+user:CE4DQ6BIb/BVMN9scFyLtA==
+server:CE4DQ6BIb/BVMN9scFyLtA==
+client:CE4DQ6BIb/BVMN9scFyLtA==
diff --git a/qpid/java/broker/etc/transient_config.xml b/qpid/java/broker/etc/transient_config.xml new file mode 100644 index 0000000000..a21afe7d21 --- /dev/null +++ b/qpid/java/broker/etc/transient_config.xml @@ -0,0 +1,118 @@ +<?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. + - + + This is an example config file that uses the MemoryMessageStore. + As a result it is aimed at brokers sending transient messages. + + --> +<broker> + <prefix>${QPID_HOME}</prefix> + <work>${QPID_WORK}</work> + <conf>${prefix}/etc</conf> + <connector> + <transport>nio</transport> + <port>5672</port> + <sslport>8672</sslport> + <socketReceiveBuffer>32768</socketReceiveBuffer> + <socketSendBuffer>32768</socketSendBuffer> + </connector> + <management> + <enabled>true</enabled> + <jmxport>8999</jmxport> + <ssl> + <enabled>true</enabled> + <!-- Update below path to your keystore location, eg ${conf}/qpid.keystore --> + <keyStorePath>${prefix}/../test_resources/ssl/keystore.jks</keyStorePath> + <keyStorePassword>password</keyStorePassword> + </ssl> + </management> + <advanced> + <filterchain enableExecutorPool="true"/> + <enablePooledAllocator>false</enablePooledAllocator> + <enableDirectBuffers>false</enableDirectBuffers> + <framesize>65535</framesize> + <compressBufferOnQueue>false</compressBufferOnQueue> + </advanced> + + <security> + <principal-databases> + <principal-database> + <name>passwordfile</name> + <class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class> + <attributes> + <attribute> + <name>passwordFile</name> + <value>${conf}/passwd</value> + </attribute> + </attributes> + </principal-database> + </principal-databases> + + <access> + <class>org.apache.qpid.server.security.access.plugins.AllowAll</class> + </access> + <jmx> + <access>${conf}/jmxremote.access</access> + <principal-database>passwordfile</principal-database> + </jmx> + </security> + + <virtualhosts> + <virtualhost> + <name>localhost</name> + <localhost> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </localhost> + </virtualhost> + + <virtualhost> + <name>development</name> + <development> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </development> + </virtualhost> + + <virtualhost> + <name>test</name> + <test> + <store> + <class>org.apache.qpid.server.store.MemoryMessageStore</class> + </store> + </test> + </virtualhost> + + </virtualhosts> + <heartbeat> + <delay>0</delay> + <timeoutFactor>2.0</timeoutFactor> + </heartbeat> + <queue> + <auto_register>true</auto_register> + </queue> + + <virtualhosts>${conf}/virtualhosts.xml</virtualhosts> +</broker> + + diff --git a/qpid/java/broker/etc/virtualhosts.xml b/qpid/java/broker/etc/virtualhosts.xml new file mode 100644 index 0000000000..f62ec3f5d7 --- /dev/null +++ b/qpid/java/broker/etc/virtualhosts.xml @@ -0,0 +1,123 @@ +<?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> + <default>test</default> + <virtualhost> + <name>localhost</name> + <localhost> + <exchanges> + <exchange> + <type>direct</type> + <name>test.direct</name> + <durable>true</durable> + </exchange> + <exchange> + <type>topic</type> + <name>test.topic</name> + </exchange> + </exchanges> + <queues> + <exchange>amq.direct</exchange> + <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb --> + <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb --> + <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins --> + + <queue> + <name>queue</name> + </queue> + <queue> + <name>ping</name> + </queue> + <queue> + <name>test-queue</name> + <test-queue> + <exchange>test.direct</exchange> + <durable>true</durable> + </test-queue> + </queue> + <queue> + <name>test-ping</name> + <test-ping> + <exchange>test.direct</exchange> + </test-ping> + </queue> + + </queues> + </localhost> + </virtualhost> + + + <virtualhost> + <name>development</name> + <development> + <queues> + <minimumAlertRepeatGap>30000</minimumAlertRepeatGap> + <maximumMessageCount>5000</maximumMessageCount> + <queue> + <name>queue</name> + <queue> + <exchange>amq.direct</exchange> + <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb --> + <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb --> + <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins --> + </queue> + </queue> + <queue> + <name>ping</name> + <ping> + <exchange>amq.direct</exchange> + <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb --> + <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb --> + <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins --> + </ping> + </queue> + </queues> + </development> + </virtualhost> + <virtualhost> + <name>test</name> + <test> + <queues> + <minimumAlertRepeatGap>30000</minimumAlertRepeatGap> + <maximumMessageCount>5000</maximumMessageCount> + <queue> + <name>queue</name> + <queue> + <exchange>amq.direct</exchange> + <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb --> + <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb --> + <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins --> + </queue> + </queue> + <queue> + <name>ping</name> + <ping> + <exchange>amq.direct</exchange> + <maximumQueueDepth>4235264</maximumQueueDepth> <!-- 4Mb --> + <maximumMessageSize>2117632</maximumMessageSize> <!-- 2Mb --> + <maximumMessageAge>600000</maximumMessageAge> <!-- 10 mins --> + </ping> + </queue> + </queues> + </test> + </virtualhost> +</virtualhosts> diff --git a/qpid/java/broker/python-test.xml b/qpid/java/broker/python-test.xml new file mode 100755 index 0000000000..5c263e3169 --- /dev/null +++ b/qpid/java/broker/python-test.xml @@ -0,0 +1,56 @@ +<?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. +--> + +<!-- ====================================================================== --> +<!-- Ant build file (http://ant.apache.org/) for Ant 1.6.2 or above. --> +<!-- ====================================================================== --> + +<project basedir="." default="default"> + + <target name="default" > + <echo message="Used via maven to run python tests."/> + </target> + + <dirname property="broker.dir" file="${ant.file.python-test}"/> + + <property name="pythondir" value="${broker.dir}/../../python"/> + + <target name="run-tests" unless="skip-python-tests"> + + <echo message="Starting Broker with command"/> + + <java classname="org.apache.qpid.server.RunBrokerWithCommand" + fork="true" + dir="${pythondir}" + failonerror="true" + > + <arg value="${command}"/> + <arg value="-p"/> + <arg value="2110"/> + <arg value="-m"/> + <arg value="2111"/> + + <classpath refid="maven.test.classpath"/> + <sysproperty key="QPID_HOME" value="${broker.dir}"/> + <sysproperty key="QPID_WORK" value="${broker.dir}${file.separator}target"/> + </java> + + </target> +</project> diff --git a/qpid/java/broker/scripts/resetAlerting.sh b/qpid/java/broker/scripts/resetAlerting.sh new file mode 100644 index 0000000000..57a38f3ed1 --- /dev/null +++ b/qpid/java/broker/scripts/resetAlerting.sh @@ -0,0 +1,95 @@ +#!/bin/bash +# +# Alerting Rest Scripts to renabled the alerts on the queue. +# +# Defaults to Localhost broker +# + +if [ -z "$QPID_ALERT_HOME" ]; then + export QPID_ALERT_HOME=$(dirname $(dirname $(readlink -f $0))) + export PATH=${PATH}:${QPID_ALERT_HOME}/bin +fi + +USERNAME=$1 +PASSWORD=$2 +HOSTNAME=$3 +PORT=$4 + +CLI="$QPID_ALERT_HOME/bin/qpid-cli -h ${HOSTNAME:-localhost} -p ${PORT:-8999}" +AUTH= +if [ -n $USERNAME ] ; then + if [ "$USERNAME" == "-h" ] ; then + echo "resetAlerting.sh: [<username> <password> [<hostname> [<port>]]]" + exit 0 + fi + if [ -n $PASSWORD ] ; then + AUTH="-u $USERNAME -w $PASSWORD" + else + echo "Password must be specified with username" + fi +fi + + +OUTPUT=0 + +runCommand() +{ + RET=`$CLI $1 $AUTH` +} + +resetQueue() +{ + vhost=$1 + queue=$2 + runCommand "get -o queue -v $vhost -n $queue -a MaximumQueueDepth" + rawQDepth=$RET + # Note that MaxQueueDepth is returned as Kb but set as b! + queueDepth=$[ $rawQDepth * 1024 ] + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageAge" + messageAge=$RET + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageCount" + messageCount=$RET + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageSize" + messageSize=$RET + + if [ $OUTPUT == 1 ] ; then + echo Current Values: + echo MaximumQueueDepth : $queueDepth + echo MaximumMessageAge : $messageAge + echo MaximumMessageCount : $messageCount + echo MaximumMessageSize : $messageSize + fi + + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageSize -s $messageSize" + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageAge -s $messageAge" + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageCount -s $messageCount" + runCommand "set -o queue -v $vhost -n $queue -a MaximumQueueDepth -s $queueDepth" +} + +resetVirtualHost() +{ + vhost=$1 + ignore=0 + for queue in `$CLI list -o queue -v $vhost $AUTH |grep '|' | cut -d '|' -f 1 ` ; do + + if [ $ignore == 0 ] ; then + ignore=1 + else + resetQueue $vhost $queue + fi + + done +} + +VHOST=`$CLI list -o virtualhost $AUTH` +COUNT=`echo $VHOST | grep -c VirtualHost` +if [ $COUNT -gt 0 ] ; then + for vhost in `echo $VHOST |grep VirtualHost|cut -d '=' -f 3` ; do + + echo "Resetting alert levels for $vhost"; + resetVirtualHost $vhost; + done + echo "Alerting levels reset" +else + echo $VHOST +fi diff --git a/qpid/java/broker/src/main/grammar/SelectorParser.jj b/qpid/java/broker/src/main/grammar/SelectorParser.jj new file mode 100644 index 0000000000..c9e01cd01f --- /dev/null +++ b/qpid/java/broker/src/main/grammar/SelectorParser.jj @@ -0,0 +1,621 @@ +/*
+ *
+ * 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.
+ *
+ */
+
+ //
+ // Original File from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html>
+ //
+
+// ----------------------------------------------------------------------------
+// OPTIONS
+// ----------------------------------------------------------------------------
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+
+ // some performance optimizations
+ OPTIMIZE_TOKEN_MANAGER = true;
+ ERROR_REPORTING = false;
+}
+
+// ----------------------------------------------------------------------------
+// PARSER
+// ----------------------------------------------------------------------------
+
+PARSER_BEGIN(SelectorParser)
+/*
+ *
+ * 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.filter.jms.selector;
+
+import java.io.StringReader;
+import java.util.ArrayList;
+
+import org.apache.qpid.AMQInvalidArgumentException;
+import org.apache.qpid.server.filter.ArithmeticExpression;
+import org.apache.qpid.server.filter.BooleanExpression;
+import org.apache.qpid.server.filter.ComparisonExpression;
+import org.apache.qpid.server.filter.ConstantExpression;
+import org.apache.qpid.server.filter.Expression;
+import org.apache.qpid.server.filter.LogicExpression;
+import org.apache.qpid.server.filter.PropertyExpression;
+import org.apache.qpid.server.filter.UnaryExpression;
+
+/**
+ * JMS Selector Parser generated by JavaCC
+ *
+ * Do not edit this .java file directly - it is autogenerated from SelectorParser.jj
+ */
+public class SelectorParser {
+
+ public SelectorParser() {
+ this(new StringReader(""));
+ }
+
+ public BooleanExpression parse(String sql) throws AMQInvalidArgumentException {
+ this.ReInit(new StringReader(sql));
+
+ try {
+ return this.JmsSelector();
+ }
+ catch (Throwable e) {
+ throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql,e);
+ }
+
+ }
+
+ private BooleanExpression asBooleanExpression(Expression value) throws ParseException {
+ if (value instanceof BooleanExpression) {
+ return (BooleanExpression) value;
+ }
+ if (value instanceof PropertyExpression) {
+ return UnaryExpression.createBooleanCast( value );
+ }
+ throw new ParseException("Expression will not result in a boolean value: " + value);
+ }
+
+
+}
+
+PARSER_END(SelectorParser)
+
+// ----------------------------------------------------------------------------
+// Tokens
+// ----------------------------------------------------------------------------
+
+/* White Space */
+SPECIAL_TOKEN :
+{
+ " " | "\t" | "\n" | "\r" | "\f"
+}
+
+/* Comments */
+SKIP:
+{
+ <LINE_COMMENT: "--" (~["\n","\r"])* ("\n"|"\r"|"\r\n") >
+}
+
+SKIP:
+{
+ <BLOCK_COMMENT: "/*" (~["*"])* "*" ("*" | (~["*","/"] (~["*"])* "*"))* "/">
+}
+
+/* Reserved Words */
+TOKEN [IGNORE_CASE] :
+{
+ < NOT : "NOT">
+ | < AND : "AND">
+ | < OR : "OR">
+ | < BETWEEN : "BETWEEN">
+ | < LIKE : "LIKE">
+ | < ESCAPE : "ESCAPE">
+ | < IN : "IN">
+ | < IS : "IS">
+ | < TRUE : "TRUE" >
+ | < FALSE : "FALSE" >
+ | < NULL : "NULL" >
+ | < XPATH : "XPATH" >
+ | < XQUERY : "XQUERY" >
+}
+
+/* Literals */
+TOKEN [IGNORE_CASE] :
+{
+
+ < DECIMAL_LITERAL: ["1"-"9"] (["0"-"9"])* (["l","L"])? >
+ | < HEX_LITERAL: "0" ["x","X"] (["0"-"9","a"-"f","A"-"F"])+ >
+ | < OCTAL_LITERAL: "0" (["0"-"7"])* >
+ | < FLOATING_POINT_LITERAL:
+ (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)? // matches: 5.5 or 5. or 5.5E10 or 5.E10
+ | "." (["0"-"9"])+ (<EXPONENT>)? // matches: .5 or .5E10
+ | (["0"-"9"])+ <EXPONENT> // matches: 5E10
+ >
+ | < #EXPONENT: "E" (["+","-"])? (["0"-"9"])+ >
+ | < STRING_LITERAL: "'" ( ("''") | ~["'"] )* "'" >
+}
+
+TOKEN [IGNORE_CASE] :
+{
+ < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
+ | < QUOTED_ID : "\"" ( ("\"\"") | ~["\""] )* "\"" >
+}
+
+// ----------------------------------------------------------------------------
+// Grammer
+// ----------------------------------------------------------------------------
+BooleanExpression JmsSelector() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = orExpression()
+ )
+ {
+ return asBooleanExpression(left);
+ }
+
+}
+
+Expression orExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = andExpression()
+ (
+ <OR> right = andExpression()
+ {
+ left = LogicExpression.createOR(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+
+}
+
+
+Expression andExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = equalityExpression()
+ (
+ <AND> right = equalityExpression()
+ {
+ left = LogicExpression.createAND(asBooleanExpression(left), asBooleanExpression(right));
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression equalityExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ (
+ left = comparisonExpression()
+ (
+
+ "=" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createEqual(left, right);
+ }
+ |
+ "<>" right = comparisonExpression()
+ {
+ left = ComparisonExpression.createNotEqual(left, right);
+ }
+ |
+ LOOKAHEAD(2)
+ <IS> <NULL>
+ {
+ left = ComparisonExpression.createIsNull(left);
+ }
+ |
+ <IS> <NOT> <NULL>
+ {
+ left = ComparisonExpression.createIsNotNull(left);
+ }
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression comparisonExpression() :
+{
+ Expression left;
+ Expression right;
+ Expression low;
+ Expression high;
+ String t, u;
+ boolean not;
+ ArrayList list;
+}
+{
+ (
+ left = addExpression()
+ (
+
+ ">" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThan(left, right);
+ }
+ |
+ ">=" right = addExpression()
+ {
+ left = ComparisonExpression.createGreaterThanEqual(left, right);
+ }
+ |
+ "<" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThan(left, right);
+ }
+ |
+ "<=" right = addExpression()
+ {
+ left = ComparisonExpression.createLessThanEqual(left, right);
+ }
+ |
+ {
+ u=null;
+ }
+ <LIKE> t = stringLitteral()
+ [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createLike(left, t, u);
+ }
+ |
+ LOOKAHEAD(2)
+ {
+ u=null;
+ }
+ <NOT> <LIKE> t = stringLitteral() [ <ESCAPE> u = stringLitteral() ]
+ {
+ left = ComparisonExpression.createNotLike(left, t, u);
+ }
+ |
+ <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createBetween(left, low, high);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <BETWEEN> low = addExpression() <AND> high = addExpression()
+ {
+ left = ComparisonExpression.createNotBetween(left, low, high);
+ }
+ |
+ <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createInFilter(left, list);
+ }
+ |
+ LOOKAHEAD(2)
+ <NOT> <IN>
+ "("
+ t = stringLitteral()
+ {
+ list = new ArrayList();
+ list.add( t );
+ }
+ (
+ ","
+ t = stringLitteral()
+ {
+ list.add( t );
+ }
+
+ )*
+ ")"
+ {
+ left = ComparisonExpression.createNotInFilter(left, list);
+ }
+
+ )*
+ )
+ {
+ return left;
+ }
+}
+
+Expression addExpression() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = multExpr()
+ (
+ LOOKAHEAD( ("+"|"-") multExpr())
+ (
+ "+" right = multExpr()
+ {
+ left = ArithmeticExpression.createPlus(left, right);
+ }
+ |
+ "-" right = multExpr()
+ {
+ left = ArithmeticExpression.createMinus(left, right);
+ }
+ )
+
+ )*
+ {
+ return left;
+ }
+}
+
+Expression multExpr() :
+{
+ Expression left;
+ Expression right;
+}
+{
+ left = unaryExpr()
+ (
+ "*" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMultiply(left, right);
+ }
+ |
+ "/" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createDivide(left, right);
+ }
+ |
+ "%" right = unaryExpr()
+ {
+ left = ArithmeticExpression.createMod(left, right);
+ }
+
+ )*
+ {
+ return left;
+ }
+}
+
+
+Expression unaryExpr() :
+{
+ String s=null;
+ Expression left=null;
+}
+{
+ (
+ LOOKAHEAD( "+" unaryExpr() )
+ "+" left=unaryExpr()
+ |
+ "-" left=unaryExpr()
+ {
+ left = UnaryExpression.createNegate(left);
+ }
+ |
+ <NOT> left=unaryExpr()
+ {
+ left = UnaryExpression.createNOT( asBooleanExpression(left) );
+ }
+ |
+ <XPATH> s=stringLitteral()
+ {
+ left = UnaryExpression.createXPath( s );
+ }
+ |
+ <XQUERY> s=stringLitteral()
+ {
+ left = UnaryExpression.createXQuery( s );
+ }
+ |
+ left = primaryExpr()
+ )
+ {
+ return left;
+ }
+
+}
+
+Expression primaryExpr() :
+{
+ Expression left=null;
+}
+{
+ (
+ left = literal()
+ |
+ left = variable()
+ |
+ "(" left = orExpression() ")"
+ )
+ {
+ return left;
+ }
+}
+
+
+
+ConstantExpression literal() :
+{
+ Token t;
+ String s;
+ ConstantExpression left=null;
+}
+{
+ (
+ (
+ s = stringLitteral()
+ {
+ left = new ConstantExpression(s);
+ }
+ )
+ |
+ (
+ t = <DECIMAL_LITERAL>
+ {
+ left = ConstantExpression.createFromDecimal(t.image);
+ }
+ )
+ |
+ (
+ t = <HEX_LITERAL>
+ {
+ left = ConstantExpression.createFromHex(t.image);
+ }
+ )
+ |
+ (
+ t = <OCTAL_LITERAL>
+ {
+ left = ConstantExpression.createFromOctal(t.image);
+ }
+ )
+ |
+ (
+ t = <FLOATING_POINT_LITERAL>
+ {
+ left = ConstantExpression.createFloat(t.image);
+ }
+ )
+ |
+ (
+ <TRUE>
+ {
+ left = ConstantExpression.TRUE;
+ }
+ )
+ |
+ (
+ <FALSE>
+ {
+ left = ConstantExpression.FALSE;
+ }
+ )
+ |
+ (
+ <NULL>
+ {
+ left = ConstantExpression.NULL;
+ }
+ )
+ )
+ {
+ return left;
+ }
+}
+
+String stringLitteral() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ boolean first=true;
+}
+{
+ t = <STRING_LITERAL>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '\'' )
+ i++;
+ rc.append(c);
+ }
+ return rc.toString();
+ }
+}
+
+PropertyExpression variable() :
+{
+ Token t;
+ StringBuffer rc = new StringBuffer();
+ PropertyExpression left=null;
+}
+{
+ (
+ t = <ID>
+ {
+ left = new PropertyExpression(t.image);
+ }
+ |
+ t = <QUOTED_ID>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '"' )
+ i++;
+ rc.append(c);
+ }
+ return new PropertyExpression(rc.toString());
+ }
+
+
+ )
+ {
+ return left;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/log4j.properties b/qpid/java/broker/src/main/java/log4j.properties new file mode 100644 index 0000000000..6788c65463 --- /dev/null +++ b/qpid/java/broker/src/main/java/log4j.properties @@ -0,0 +1,24 @@ +# +# 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. +# +log4j.rootCategory=${amqj.logging.level}, console + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Threshold=all +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java new file mode 100644 index 0000000000..7e0c4defe1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java @@ -0,0 +1,1007 @@ +/* + * + * 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.log4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.GZIPOutputStream; + +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * <p>CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender<br> It can function as either + * or do both at the same time (making size based rolling files like RollingFileAppender until a data/time boundary is + * crossed at which time it rolls all of those files as per the DailyRollingFileAppender) based on the setting for + * <code>rollingStyle</code>.<br> <br> To use CompositeRollingAppender to roll log files as they reach a certain size + * (like RollingFileAppender), set rollingStyle=1 (@see config.size)<br> To use CompositeRollingAppender to roll log + * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)<br> To + * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set + * rollingStyle=3 (@see config.composite)<br> + * + * <p>A of few additional optional features have been added:<br> -- Attach date pattern for current log file (@see + * staticLogFileName)<br> -- Backup number increments for newer files (@see countDirection)<br> -- Infinite number of + * backups by file size (@see maxSizeRollBackups)<br> <br> <p>A few notes and warnings: For large or infinite number of + * backups countDirection > 0 is highly recommended, with staticLogFileName = false if time based rolling is also used + * -- this will reduce the number of file renamings to few or none. Changing staticLogFileName or countDirection + * without clearing the directory could have nasty side effects. If Date/Time based rolling is enabled, + * CompositeRollingAppender will attempt to roll existing files in the directory without a date/time tag based on the + * last modified date of the base log files last modification.<br> <br> <p>A maximum number of backups based on + * date/time boundries would be nice but is not yet implemented.<br> + * + * @author Kevin Steppe + * @author Heinz Richter + * @author Eirik Lygre + * @author Ceki Gülcü + * @author Martin Ritchie + */ +public class QpidCompositeRollingAppender extends FileAppender +{ + // The code assumes that the following 'time' constants are in a increasing + // sequence. + static final int TOP_OF_TROUBLE = -1; + static final int TOP_OF_MINUTE = 0; + static final int TOP_OF_HOUR = 1; + static final int HALF_DAY = 2; + static final int TOP_OF_DAY = 3; + static final int TOP_OF_WEEK = 4; + static final int TOP_OF_MONTH = 5; + + /** Style of rolling to use */ + static final int BY_SIZE = 1; + static final int BY_DATE = 2; + static final int BY_COMPOSITE = 3; + + // Not currently used + static final String S_BY_SIZE = "Size"; + static final String S_BY_DATE = "Date"; + static final String S_BY_COMPOSITE = "Composite"; + + /** The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover. */ + private String datePattern = "'.'yyyy-MM-dd"; + + /** + * The actual formatted filename that is currently being written to or will be the file transferred to on roll over + * (based on staticLogFileName). + */ + private String scheduledFilename = null; + + /** The timestamp when we shall next recompute the filename. */ + private long nextCheck = System.currentTimeMillis() - 1; + + /** Holds date of last roll over */ + Date now = new Date(); + + SimpleDateFormat sdf; + + /** Helper class to determine next rollover time */ + RollingCalendar rc = new RollingCalendar(); + + /** Current period for roll overs */ + int checkPeriod = TOP_OF_TROUBLE; + + /** The default maximum file size is 10MB. */ + protected long maxFileSize = 10 * 1024 * 1024; + + /** There is zero backup files by default. */ + protected int maxSizeRollBackups = 0; + /** How many sized based backups have been made so far */ + protected int curSizeRollBackups = 0; + + /** not yet implemented */ + protected int maxTimeRollBackups = -1; + protected int curTimeRollBackups = 0; + + /** + * By default newer files have lower numbers. (countDirection < 0) ie. log.1 is most recent, log.5 is the 5th + * backup, etc... countDirection > 0 does the opposite ie. log.1 is the first backup made, log.5 is the 5th backup + * made, etc. For infinite backups use countDirection > 0 to reduce rollOver costs. + */ + protected int countDirection = -1; + + /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ + protected int rollingStyle = BY_COMPOSITE; + protected boolean rollDate = true; + protected boolean rollSize = true; + + /** + * By default file.log is always the current file. Optionally file.log.yyyy-mm-dd for current formated datePattern + * can by the currently logging file (or file.log.curSizeRollBackup or even file.log.yyyy-mm-dd.curSizeRollBackup) + * This will make time based roll overs with a large number of backups much faster -- it won't have to rename all + * the backups! + */ + protected boolean staticLogFileName = true; + + /** FileName provided in configuration. Used for rolling properly */ + protected String baseFileName; + + /** Do we want to .gz our backup files. */ + protected boolean compress = false; + + /** Do we want to use a second thread when compressing our backup files. */ + protected boolean compressAsync = false; + + /** Do we want to start numbering files at zero. */ + protected boolean zeroBased = false; + + /** Path provided in configuration. Used for moving backup files to */ + protected String backupFilesToPath = null; + private final ConcurrentLinkedQueue<CompressJob> _compress = new ConcurrentLinkedQueue<CompressJob>(); + private AtomicBoolean _compressing = new AtomicBoolean(false); + + /** The default constructor does nothing. */ + public QpidCompositeRollingAppender() + { } + + /** + * Instantiate a <code>CompositeRollingAppender</code> and open the file designated by <code>filename</code>. The + * opened filename will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern) throws IOException + { + this(layout, filename, datePattern, true); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the ouput destination for this appender. + * + * <p>If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by + * <code>filename</code> will be truncated before being opened. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, boolean append) throws IOException + { + super(layout, filename, append); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern, boolean append) + throws IOException + { + super(layout, filename, append); + this.datePattern = datePattern; + activateOptions(); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the output destination for this appender. + * + * <p>The file will be appended to. DatePattern is default. + */ + public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException + { + super(layout, filename); + } + + /** + * The <b>DatePattern</b> takes a string in the same format as expected by {@link java.text.SimpleDateFormat}. This + * options determines the rollover schedule. + */ + public void setDatePattern(String pattern) + { + datePattern = pattern; + } + + /** Returns the value of the <b>DatePattern</b> option. */ + public String getDatePattern() + { + return datePattern; + } + + /** Returns the value of the <b>maxSizeRollBackups</b> option. */ + public int getMaxSizeRollBackups() + { + return maxSizeRollBackups; + } + + /** + * Get the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * @since 1.1 + */ + public long getMaximumFileSize() + { + return maxFileSize; + } + + /** + * <p>Set the maximum number of backup files to keep around based on file size. + * + * <p>The <b>MaxSizeRollBackups</b> option determines how many backup files are kept before the oldest is erased. + * This option takes an integer value. If set to zero, then there will be no backup files and the log file will be + * truncated when it reaches <code>MaxFileSize</code>. If a negative number is supplied then no deletions will be + * made. Note that this could result in very slow performance as a large number of files are rolled over unless + * {@link #setCountDirection} up is used. + * + * <p>The maximum applys to -each- time based group of files and -not- the total. Using a daily roll the maximum + * total files would be (#days run) * (maxSizeRollBackups) + */ + public void setMaxSizeRollBackups(int maxBackups) + { + maxSizeRollBackups = maxBackups; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaxFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>In configuration files, the <b>MaxFileSize</b> option takes an long integer in the range 0 - 2^63. You can + * specify the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed + * respectively in kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240. + */ + public void setMaxFileSize(String value) + { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) + { + qw = new CountingQuietWriter(writer, errorHandler); + } + + // Taken verbatum from DailyRollingFileAppender + int computeCheckPeriod() + { + RollingCalendar c = new RollingCalendar(); + // set sate to 1970-01-01 00:00:00 GMT + Date epoch = new Date(0); + if (datePattern != null) + { + for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) + { + String r0 = sdf.format(epoch); + c.setType(i); + Date next = new Date(c.getNextCheckMillis(epoch)); + String r1 = sdf.format(next); + // LogLog.debug("Type = "+i+", r0 = "+r0+", r1 = "+r1); + if ((r0 != null) && (r1 != null) && !r0.equals(r1)) + { + return i; + } + } + } + + return TOP_OF_TROUBLE; // Deliberately head for trouble... + } + + // Now for the new stuff + /** + * Handles append time behavior for CompositeRollingAppender. This checks if a roll over either by date (checked + * first) or time (checked second) is need and then appends to the file last. + */ + protected void subAppend(LoggingEvent event) + { + + if (rollDate) + { + long n = System.currentTimeMillis(); + if (n >= nextCheck) + { + now.setTime(n); + nextCheck = rc.getNextCheckMillis(now); + + rollOverTime(); + } + } + + if (rollSize) + { + if ((fileName != null) && (((CountingQuietWriter) qw).getCount() >= maxFileSize)) + { + rollOverSize(); + } + } + + super.subAppend(event); + } + + public void setFile(String file) + { + baseFileName = file.trim(); + fileName = file.trim(); + } + + /** + * Creates and opens the file for logging. If <code>staticLogFileName</code> is false then the fully qualified name + * is determined and used. + */ + public synchronized void setFile(String fileName, boolean append) throws IOException + { + if (!staticLogFileName) + { + scheduledFilename = fileName = fileName.trim() + sdf.format(now); + if (countDirection > 0) + { + scheduledFilename = fileName = fileName + '.' + (++curSizeRollBackups); + } + } + + super.setFile(fileName, append, bufferedIO, bufferSize); + + if (append) + { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + public int getCountDirection() + { + return countDirection; + } + + public void setCountDirection(int direction) + { + countDirection = direction; + } + + public int getRollingStyle() + { + return rollingStyle; + } + + public void setRollingStyle(int style) + { + rollingStyle = style; + switch (rollingStyle) + { + + case BY_SIZE: + rollDate = false; + rollSize = true; + break; + + case BY_DATE: + rollDate = true; + rollSize = false; + break; + + case BY_COMPOSITE: + rollDate = true; + rollSize = true; + break; + + default: + errorHandler.error("Invalid rolling Style, use 1 (by size only), 2 (by date only) or 3 (both)"); + } + } + + /* + public void setRollingStyle(String style) { + if (style == S_BY_SIZE) { + rollingStyle = BY_SIZE; + } + else if (style == S_BY_DATE) { + rollingStyle = BY_DATE; + } + else if (style == S_BY_COMPOSITE) { + rollingStyle = BY_COMPOSITE; + } + } + */ + public boolean getStaticLogFileName() + { + return staticLogFileName; + } + + public void setStaticLogFileName(boolean s) + { + staticLogFileName = s; + } + + public void setStaticLogFileName(String value) + { + setStaticLogFileName(OptionConverter.toBoolean(value, true)); + } + + public boolean getCompressBackupFiles() + { + return compress; + } + + public void setCompressBackupFiles(boolean c) + { + compress = c; + } + + public boolean getCompressAsync() + { + return compressAsync; + } + + public void setCompressAsync(boolean c) + { + compressAsync = c; + if (compressAsync) + { + executor = Executors.newFixedThreadPool(1); + + compressor = new Compressor(); + } + } + + public boolean getZeroBased() + { + return zeroBased; + } + + public void setZeroBased(boolean z) + { + zeroBased = z; + } + + public String getBackupFilesToPath() + { + return backupFilesToPath; + } + + public void setbackupFilesToPath(String path) + { + File td = new File(path); + if (!td.exists()) + { + td.mkdirs(); + } + + backupFilesToPath = path; + } + + /** + * Initializes based on exisiting conditions at time of <code> activateOptions</code>. The following is done:<br> + * <br> A) determine curSizeRollBackups<br> B) determine curTimeRollBackups (not implemented)<br> C) initiates a + * roll over if needed for crossing a date boundary since the last run. + */ + protected void existingInit() + { + + if (zeroBased) + { + curSizeRollBackups = -1; + } + + curTimeRollBackups = 0; + + // part A starts here + String filter; + if (staticLogFileName || !rollDate) + { + filter = baseFileName + ".*"; + } + else + { + filter = scheduledFilename + ".*"; + } + + File f = new File(baseFileName); + f = f.getParentFile(); + if (f == null) + { + f = new File("."); + } + + LogLog.debug("Searching for existing files in: " + f); + String[] files = f.list(); + + if (files != null) + { + for (int i = 0; i < files.length; i++) + { + if (!files[i].startsWith(baseFileName)) + { + continue; + } + + int index = files[i].lastIndexOf("."); + + if (staticLogFileName) + { + int endLength = files[i].length() - index; + if ((baseFileName.length() + endLength) != files[i].length()) + { + // file is probably scheduledFilename + .x so I don't care + continue; + } + } + + try + { + int backup = Integer.parseInt(files[i].substring(index + 1, files[i].length())); + LogLog.debug("From file: " + files[i] + " -> " + backup); + if (backup > curSizeRollBackups) + { + curSizeRollBackups = backup; + } + } + catch (Exception e) + { + // this happens when file.log -> file.log.yyyy-mm-dd which is normal + // when staticLogFileName == false + LogLog.debug("Encountered a backup file not ending in .x " + files[i]); + } + } + } + + LogLog.debug("curSizeRollBackups starts at: " + curSizeRollBackups); + // part A ends here + + // part B not yet implemented + + // part C + if (staticLogFileName && rollDate) + { + File old = new File(baseFileName); + if (old.exists()) + { + Date last = new Date(old.lastModified()); + if (!(sdf.format(last).equals(sdf.format(now)))) + { + scheduledFilename = baseFileName + sdf.format(last); + LogLog.debug("Initial roll over to: " + scheduledFilename); + rollOverTime(); + } + } + } + + LogLog.debug("curSizeRollBackups after rollOver at: " + curSizeRollBackups); + // part C ends here + + } + + /** + * Sets initial conditions including date/time roll over information, first check, scheduledFilename, and calls + * <code>existingInit</code> to initialize the current # of backups. + */ + public void activateOptions() + { + + // REMOVE removed rollDate from boolean to enable Alex's change + if (datePattern != null) + { + now.setTime(System.currentTimeMillis()); + sdf = new SimpleDateFormat(datePattern); + int type = computeCheckPeriod(); + // printPeriodicity(type); + rc.setType(type); + // next line added as this removes the name check in rollOver + nextCheck = rc.getNextCheckMillis(now); + } + else + { + if (rollDate) + { + LogLog.error("Either DatePattern or rollingStyle options are not set for [" + name + "]."); + } + } + + existingInit(); + + if (rollDate && (fileName != null) && (scheduledFilename == null)) + { + scheduledFilename = fileName + sdf.format(now); + } + + try + { + this.setFile(fileName, true); + } + catch (IOException e) + { + errorHandler.error("Cannot set file name:" + fileName); + } + + super.activateOptions(); + } + + /** + * Rollover the file(s) to date/time tagged file(s). Opens the new file (through setFile) and resets + * curSizeRollBackups. + */ + protected void rollOverTime() + { + + curTimeRollBackups++; + + this.closeFile(); // keep windows happy. + + // delete the old stuff here + + if (staticLogFileName) + { + /* Compute filename, but only if datePattern is specified */ + if (datePattern == null) + { + errorHandler.error("Missing DatePattern option in rollOver()."); + + return; + } + + // is the new file name equivalent to the 'current' one + // something has gone wrong if we hit this -- we should only + // roll over if the new file will be different from the old + String dateFormat = sdf.format(now); + if (scheduledFilename.equals(fileName + dateFormat)) + { + errorHandler.error("Compare " + scheduledFilename + " : " + fileName + dateFormat); + + return; + } + + // close current file, and rename it to datedFilename + this.closeFile(); + + // we may have to roll over a large number of backups here + String from, to; + for (int i = 1; i <= curSizeRollBackups; i++) + { + from = fileName + '.' + i; + to = scheduledFilename + '.' + i; + rollFile(from, to, false); + } + + rollFile(fileName, scheduledFilename, compress); + } + else + { + if (compress) + { + compress(fileName); + } + } + + try + { + // This will also close the file. This is OK since multiple + // close operations are safe. + curSizeRollBackups = 0; // We're cleared out the old date and are ready for the new + + // new scheduled name + scheduledFilename = fileName + sdf.format(now); + this.setFile(baseFileName, false); + } + catch (IOException e) + { + errorHandler.error("setFile(" + fileName + ", false) call failed."); + } + + } + + /** + * Renames file <code>from</code> to file <code>to</code>. It also checks for existence of target file and deletes + * if it does. + */ + protected void rollFile(String from, String to, boolean compress) + { + if (from.equals(to)) + { + if (compress) + { + LogLog.debug("Attempting to compress file with same output name."); + } + + return; + } + + File target = new File(to); + if (target.exists()) + { + LogLog.debug("deleting existing target file: " + target); + target.delete(); + } + + File file = new File(from); + if (compress) + { + compress(file, target); + } + else + { + if (!file.getPath().equals(target.getPath())) + { + file.renameTo(target); + } + } + + LogLog.debug(from + " -> " + to); + } + + protected void compress(String file) + { + File f = new File(file); + compress(f, f); + } + + private void compress(File from, File target) + { + if (compressAsync) + { + synchronized (_compress) + { + _compress.offer(new CompressJob(from, target)); + } + + startCompression(); + } + else + { + doCompress(from, target); + } + } + + private void startCompression() + { + if (_compressing.compareAndSet(false, true)) + { + executor.execute(compressor); + } + } + + /** Delete's the specified file if it exists */ + protected static void deleteFile(String fileName) + { + File file = new File(fileName); + if (file.exists()) + { + file.delete(); + } + } + + /** + * Implements roll overs base on file size. + * + * <p>If the maximum number of size based backups is reached (<code>curSizeRollBackups == maxSizeRollBackups</code) + * then the oldest file is deleted -- it's index determined by the sign of countDirection.<br> If + * <code>countDirection</code> < 0, then files {<code>File.1</code>, ..., <code>File.curSizeRollBackups -1</code>} + * are renamed to {<code>File.2</code>, ..., <code>File.curSizeRollBackups</code>}. Moreover, <code>File</code> is + * renamed <code>File.1</code> and closed.<br> + * + * A new file is created to receive further log output. + * + * <p>If <code>maxSizeRollBackups</code> is equal to zero, then the <code>File</code> is truncated with no backup + * files created. + * + * <p>If <code>maxSizeRollBackups</code> < 0, then <code>File</code> is renamed if needed and no files are deleted. + */ + + // synchronization not necessary since doAppend is alreasy synched + protected void rollOverSize() + { + File file; + + this.closeFile(); // keep windows happy. + + LogLog.debug("rolling over count=" + ((CountingQuietWriter) qw).getCount()); + LogLog.debug("maxSizeRollBackups = " + maxSizeRollBackups); + LogLog.debug("curSizeRollBackups = " + curSizeRollBackups); + LogLog.debug("countDirection = " + countDirection); + + // If maxBackups <= 0, then there is no file renaming to be done. + if (maxSizeRollBackups != 0) + { + + if (countDirection < 0) + { + // Delete the oldest file, to keep Windows happy. + if (curSizeRollBackups == maxSizeRollBackups) + { + deleteFile(fileName + '.' + maxSizeRollBackups); + curSizeRollBackups--; + } + + // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} + for (int i = curSizeRollBackups; i >= 1; i--) + { + rollFile((fileName + "." + i), (fileName + '.' + (i + 1)), false); + } + + curSizeRollBackups++; + // Rename fileName to fileName.1 + rollFile(fileName, fileName + ".1", compress); + + } // REMOVE This code branching for Alexander Cerna's request + else if (countDirection == 0) + { + // rollFile based on date pattern + curSizeRollBackups++; + now.setTime(System.currentTimeMillis()); + scheduledFilename = fileName + sdf.format(now); + rollFile(fileName, scheduledFilename, compress); + } + else + { // countDirection > 0 + if ((curSizeRollBackups >= maxSizeRollBackups) && (maxSizeRollBackups > 0)) + { + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + deleteFile(fileName + '.' + oldestFileIndex); + } + + if (staticLogFileName) + { + curSizeRollBackups++; + rollFile(fileName, fileName + '.' + curSizeRollBackups, compress); + } + else + { + if (compress) + { + compress(fileName); + } + } + } + } + + try + { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(baseFileName, false); + } + catch (IOException e) + { + LogLog.error("setFile(" + fileName + ", false) call failed.", e); + } + } + + protected synchronized void doCompress(File from, File to) + { + String toFile; + if (backupFilesToPath == null) + { + toFile = to.getPath() + ".gz"; + } + else + { + toFile = backupFilesToPath + System.getProperty("file.separator") + to.getName() + ".gz"; + } + + File target = new File(toFile); + if (target.exists()) + { + LogLog.debug("deleting existing target file: " + target); + target.delete(); + } + + try + { + // Create the GZIP output stream + GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(target)); + + // Open the input file + FileInputStream in = new FileInputStream(from); + + // Transfer bytes from the input file to the GZIP output stream + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) + { + out.write(buf, 0, len); + } + + in.close(); + + // Complete the GZIP file + out.finish(); + out.close(); + // Remove old file. + from.delete(); + } + catch (IOException e) + { + if (target.exists()) + { + target.delete(); + } + + rollFile(from.getPath(), to.getPath(), false); + } + } + + private class CompressJob + { + File _from, _to; + + CompressJob(File from, File to) + { + _from = from; + _to = to; + } + + File getFrom() + { + return _from; + } + + File getTo() + { + return _to; + } + } + + Compressor compressor = null; + + Executor executor; + + private class Compressor implements Runnable + { + public void run() + { + boolean running = true; + while (running) + { + CompressJob job = _compress.poll(); + + doCompress(job.getFrom(), job.getTo()); + + synchronized (_compress) + { + if (_compress.isEmpty()) + { + running = false; + _compressing.set(false); + } + } + } + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java new file mode 100644 index 0000000000..40ff590a0a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java @@ -0,0 +1,188 @@ +/* + * 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.configuration; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +public class Configuration +{ + public static final String QPID_HOME = "QPID_HOME"; + + final String QPIDHOME = System.getProperty(QPID_HOME); + + private static Logger _devlog = LoggerFactory.getLogger(Configuration.class); + + public static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + public static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + protected final Options _options = new Options(); + protected CommandLine _commandLine; + protected File _configFile; + + + public Configuration() + { + + } + + public void processCommandline(String[] args) throws InitException + { + try + { + _commandLine = new PosixParser().parse(_options, args); + } + catch (ParseException e) + { + throw new InitException("Unable to parse commmandline", e); + } + + final File defaultConfigFile = new File(QPIDHOME, DEFAULT_CONFIG_FILE); + setConfig(new File(_commandLine.getOptionValue("c", defaultConfigFile.getPath()))); + } + + public void setConfig(File file) + { + _configFile = file; + } + + /** + * @param option The option to set. + */ + public void setOption(Option option) + { + _options.addOption(option); + } + + /** + * getOptionValue from the configuration + * @param option variable argument, first string is option to get, second if present is the default value. + * @return the String for the given option or null if not present (if default value not specified) + */ + public String getOptionValue(String... option) + { + if (option.length == 1) + { + return _commandLine.getOptionValue(option[0]); + } + else if (option.length == 2) + { + return _commandLine.getOptionValue(option[0], option[1]); + } + return null; + } + + public void loadConfig(File file) throws InitException + { + setConfig(file); + loadConfig(); + } + + private void loadConfig() throws InitException + { + if (!_configFile.exists()) + { + String error = "File " + _configFile + " could not be found. Check the file exists and is readable."; + + if (QPIDHOME == null) + { + error = error + "\nNote: " + QPID_HOME + " is not set."; + } + + throw new InitException(error, null); + } + else + { + _devlog.debug("Using configuration file " + _configFile.getAbsolutePath()); + } + +// String logConfig = _commandLine.getOptionValue("l"); +// String logWatchConfig = _commandLine.getOptionValue("w", "0"); +// if (logConfig != null) +// { +// File logConfigFile = new File(logConfig); +// configureLogging(logConfigFile, logWatchConfig); +// } +// else +// { +// File configFileDirectory = _configFile.getParentFile(); +// File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); +// configureLogging(logConfigFile, logWatchConfig); +// } + } + + +// private void configureLogging(File logConfigFile, String logWatchConfig) +// { +// int logWatchTime = 0; +// try +// { +// logWatchTime = Integer.parseInt(logWatchConfig); +// } +// catch (NumberFormatException e) +// { +// _devlog.error("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " +// + "a non-negative integer. Using default of zero (no watching configured"); +// } +// +// if (logConfigFile.exists() && logConfigFile.canRead()) +// { +// _devlog.info("Configuring logger using configuration file " + logConfigFile.getAbsolutePath()); +// if (logWatchTime > 0) +// { +// _devlog.info("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " +// + logWatchTime + " seconds"); +// // log4j expects the watch interval in milliseconds +// DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); +// } +// else +// { +// DOMConfigurator.configure(logConfigFile.getAbsolutePath()); +// } +// } +// else +// { +// System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); +// System.err.println("Using basic log4j configuration"); +// BasicConfigurator.configure(); +// } +// } + + public File getConfigFile() + { + return _configFile; + } + + + public class InitException extends Exception + { + InitException(String msg, Throwable cause) + { + super(msg, cause); + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java new file mode 100644 index 0000000000..11d2c27eab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java @@ -0,0 +1,235 @@ +/* + * + * 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. + * + */ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.ManagedBroker; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * This MBean implements the broker management interface and exposes the + * Broker level management features like creating and deleting exchanges and queue. + */ +@MBeanDescription("This MBean exposes the broker level management features") +public class AMQBrokerManagerMBean extends AMQManagedObject implements ManagedBroker +{ + private final QueueRegistry _queueRegistry; + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + private final MessageStore _messageStore; + + private final VirtualHost.VirtualHostMBean _virtualHostMBean; + + @MBeanConstructor("Creates the Broker Manager MBean") + public AMQBrokerManagerMBean(VirtualHost.VirtualHostMBean virtualHostMBean) throws JMException + { + super(ManagedBroker.class, ManagedBroker.TYPE, ManagedBroker.VERSION); + + _virtualHostMBean = virtualHostMBean; + VirtualHost virtualHost = virtualHostMBean.getVirtualHost(); + + _queueRegistry = virtualHost.getQueueRegistry(); + _exchangeRegistry = virtualHost.getExchangeRegistry(); + _messageStore = virtualHost.getMessageStore(); + _exchangeFactory = virtualHost.getExchangeFactory(); + } + + public String getObjectInstanceName() + { + return _virtualHostMBean.getVirtualHost().getName(); + } + + /** + * Creates new exchange and registers it with the registry. + * + * @param exchangeName + * @param type + * @param durable + * @throws JMException + */ + public void createNewExchange(String exchangeName, String type, boolean durable) throws JMException + { + try + { + synchronized (_exchangeRegistry) + { + Exchange exchange = _exchangeRegistry.getExchange(new AMQShortString(exchangeName)); + if (exchange == null) + { + exchange = _exchangeFactory.createExchange(new AMQShortString(exchangeName), new AMQShortString(type), + durable, false, 0); + _exchangeRegistry.registerExchange(exchange); + } + else + { + throw new JMException("The exchange \"" + exchangeName + "\" already exists."); + } + } + } + catch (AMQException ex) + { + throw new MBeanException(ex, "Error in creating exchange " + exchangeName); + } + } + + /** + * Unregisters the exchange from registry. + * + * @param exchangeName + * @throws JMException + */ + public void unregisterExchange(String exchangeName) throws JMException + { + // TODO + // Check if the exchange is in use. + // boolean inUse = false; + // Check if there are queue-bindings with the exchange and unregister + // when there are no bindings. + try + { + _exchangeRegistry.unregisterExchange(new AMQShortString(exchangeName), false); + } + catch (AMQException ex) + { + throw new MBeanException(ex, "Error in unregistering exchange " + exchangeName); + } + } + + /** + * Creates a new queue and registers it with the registry and puts it + * in persistance storage if durable queue. + * + * @param queueName + * @param durable + * @param owner + * @throws JMException + */ + public void createNewQueue(String queueName, String owner, boolean durable) throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName)); + if (queue != null) + { + throw new JMException("The queue \"" + queueName + "\" already exists."); + } + + try + { + AMQShortString ownerShortString = null; + if (owner != null) + { + ownerShortString = new AMQShortString(owner); + } + + queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(queueName), durable, ownerShortString, false, getVirtualHost(), + null); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _messageStore.createQueue(queue); + } + + _queueRegistry.registerQueue(queue); + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.getMessage()); + jme.initCause(ex); + throw new MBeanException(jme, "Error in creating queue " + queueName); + } + } + + private VirtualHost getVirtualHost() + { + return _virtualHostMBean.getVirtualHost(); + } + + /** + * Deletes the queue from queue registry and persistant storage. + * + * @param queueName + * @throws JMException + */ + public void deleteQueue(String queueName) throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("The Queue " + queueName + " is not a registerd queue."); + } + + try + { + queue.delete(); + _messageStore.removeQueue(queue); + + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.getMessage()); + jme.initCause(ex); + throw new MBeanException(jme, "Error in deleting queue " + queueName); + } + } + + public ManagedObject getParentObject() + { + return _virtualHostMBean; + } + + // This will have a single instance for a virtual host, so not having the name property in the ObjectName + public ObjectName getObjectName() throws MalformedObjectNameException + { + return getObjectNameForSingleInstanceMBean(); + } +} // End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java new file mode 100644 index 0000000000..aa390d6c26 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java @@ -0,0 +1,909 @@ +/* + * + * 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; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.NoRouteException; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.UnauthorizedAccessException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.txn.LocalTransactionalContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; + +public class AMQChannel +{ + public static final int DEFAULT_PREFETCH = 5000; + + private static final Logger _log = Logger.getLogger(AMQChannel.class); + + private final int _channelId; + + + private final Pre0_10CreditManager _creditManager = new Pre0_10CreditManager(0l,0l); + + /** + * The delivery tag is unique per channel. This is pre-incremented before putting into the deliver frame so that + * value of this represents the <b>last</b> tag sent out + */ + private long _deliveryTag = 0; + + /** A channel has a default queue (the last declared) that is used when no queue name is explictily set */ + private AMQQueue _defaultQueue; + + /** This tag is unique per subscription to a queue. The server returns this in response to a basic.consume request. */ + private int _consumerTag; + + /** + * The current message - which may be partial in the sense that not all frames have been received yet - which has + * been received by this channel. As the frames are received the message gets updated and once all frames have been + * received the message can then be routed. + */ + private IncomingMessage _currentMessage; + + /** Maps from consumer tag to subscription instance. Allows us to unsubscribe from a queue. */ + protected final Map<AMQShortString, Subscription> _tag2SubscriptionMap = new HashMap<AMQShortString, Subscription>(); + + private final MessageStore _messageStore; + + private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH); + + private final AtomicBoolean _suspended = new AtomicBoolean(false); + + private TransactionalContext _txnContext; + + /** + * A context used by the message store enabling it to track context for a given channel even across thread + * boundaries + */ + private final StoreContext _storeContext; + + private final List<RequiredDeliveryException> _returnMessages = new LinkedList<RequiredDeliveryException>(); + + private MessageHandleFactory _messageHandleFactory = new MessageHandleFactory(); + + // Why do we need this reference ? - ritchiem + private final AMQProtocolSession _session; + private boolean _closing; + + public AMQChannel(AMQProtocolSession session, int channelId, MessageStore messageStore) + throws AMQException + { + _session = session; + _channelId = channelId; + _storeContext = new StoreContext("Session: " + session.getClientIdentifier() + "; channel: " + channelId); + + + _messageStore = messageStore; + + // by default the session is non-transactional + _txnContext = new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages); + } + + /** Sets this channel to be part of a local transaction */ + public void setLocalTransactional() + { + _txnContext = new LocalTransactionalContext(this); + } + + public boolean isTransactional() + { + // this does not look great but there should only be one "non-transactional" + // transactional context, while there could be several transactional ones in + // theory + return !(_txnContext instanceof NonTransactionalContext); + } + + public int getChannelId() + { + return _channelId; + } + + public void setPublishFrame(MessagePublishInfo info, final Exchange e) throws AMQException + { + + _currentMessage = new IncomingMessage(_messageStore.getNewMessageId(), info, _txnContext, _session); + _currentMessage.setMessageStore(_messageStore); + _currentMessage.setExchange(e); + } + + public void publishContentHeader(ContentHeaderBody contentHeaderBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content header without previously receiving a BasicPublish frame"); + } + else + { + if (_log.isDebugEnabled()) + { + _log.debug("Content header received on channel " + _channelId); + } + + _currentMessage.setContentHeaderBody(contentHeaderBody); + + _currentMessage.setExpiration(); + + routeCurrentMessage(); + + _currentMessage.routingComplete(_messageStore, _messageHandleFactory); + + deliverCurrentMessageIfComplete(); + + } + } + + private void deliverCurrentMessageIfComplete() + throws AMQException + { + // check and deliver if header says body length is zero + if (_currentMessage.allContentReceived()) + { + try + { + _currentMessage.deliverToQueues(); + } + catch (NoRouteException e) + { + _returnMessages.add(e); + } + catch(UnauthorizedAccessException ex) + { + _returnMessages.add(ex); + } + finally + { + // callback to allow the context to do any post message processing + // primary use is to allow message return processing in the non-tx case + _txnContext.messageProcessed(_session); + _currentMessage = null; + } + } + + } + + public void publishContentBody(ContentBody contentBody) throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content body without previously receiving a JmsPublishBody"); + } + + if (_log.isDebugEnabled()) + { + _log.debug(debugIdentity() + "Content body received on channel " + _channelId); + } + + try + { + + // returns true iff the message was delivered (i.e. if all data was + // received + _currentMessage.addContentBodyFrame( + _session.getMethodRegistry().getProtocolVersionMethodConverter().convertToContentChunk( + contentBody)); + + deliverCurrentMessageIfComplete(); + } + catch (AMQException e) + { + // we want to make sure we don't keep a reference to the message in the + // event of an error + _currentMessage = null; + throw e; + } + } + + protected void routeCurrentMessage() throws AMQException + { + try + { + _currentMessage.route(); + } + catch (NoRouteException e) + { + //_currentMessage.incrementReference(); + _returnMessages.add(e); + } + } + + public long getNextDeliveryTag() + { + return ++_deliveryTag; + } + + public int getNextConsumerTag() + { + return ++_consumerTag; + } + + /** + * Subscribe to a queue. We register all subscriptions in the channel so that if the channel is closed we can clean + * up all subscriptions, even if the client does not explicitly unsubscribe from all queues. + * + * @param tag the tag chosen by the client (if null, server will generate one) + * @param queue the queue to subscribe to + * @param acks Are acks enabled for this subscriber + * @param filters Filters to apply to this subscriber + * + * @param noLocal Flag stopping own messages being receivied. + * @param exclusive Flag requesting exclusive access to the queue + * @return the consumer tag. This is returned to the subscriber and used in subsequent unsubscribe requests + * + * @throws ConsumerTagNotUniqueException if the tag is not unique + * @throws AMQException if something goes wrong + */ + public AMQShortString subscribeToQueue(AMQShortString tag, AMQQueue queue, boolean acks, + FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException, ConsumerTagNotUniqueException + { + if (tag == null) + { + tag = new AMQShortString("sgen_" + getNextConsumerTag()); + } + + if (_tag2SubscriptionMap.containsKey(tag)) + { + throw new ConsumerTagNotUniqueException(); + } + + Subscription subscription = + SubscriptionFactoryImpl.INSTANCE.createSubscription(_channelId, _session, tag, acks, filters, noLocal, _creditManager); + + + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + // We add before we register as the Async Delivery process may AutoClose the subscriber + // so calling _cT2QM.remove before we have done put which was after the register succeeded. + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + + _tag2SubscriptionMap.put(tag, subscription); + + try + { + queue.registerSubscription(subscription, exclusive); + } + catch (AMQException e) + { + _tag2SubscriptionMap.remove(tag); + throw e; + } + return tag; + } + + /** + * Unsubscribe a consumer from a queue. + * @param consumerTag + * @return true if the consumerTag had a mapped queue that could be unregistered. + * @throws AMQException + */ + public boolean unsubscribeConsumer(AMQShortString consumerTag) throws AMQException + { + + Subscription sub = _tag2SubscriptionMap.remove(consumerTag); + if (sub != null) + { + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + return true; + } + else + { + _log.warn("Attempt to unsubscribe consumer with tag '"+consumerTag+"' which is not registered."); + } + return false; + } + + /** + * Called from the protocol session to close this channel and clean up. T + * + * @throws AMQException if there is an error during closure + */ + public void close() throws AMQException + { + _txnContext.rollback(); + unsubscribeAllConsumers(); + try + { + requeue(); + } + catch (AMQException e) + { + _log.error("Caught AMQException whilst attempting to reque:" + e); + } + + setClosing(true); + } + + private void setClosing(boolean closing) + { + _closing = closing; + } + + private void unsubscribeAllConsumers() throws AMQException + { + if (_log.isInfoEnabled()) + { + if (!_tag2SubscriptionMap.isEmpty()) + { + _log.info("Unsubscribing all consumers on channel " + toString()); + } + else + { + _log.info("No consumers to unsubscribe on channel " + toString()); + } + } + + for (Map.Entry<AMQShortString, Subscription> me : _tag2SubscriptionMap.entrySet()) + { + if (_log.isInfoEnabled()) + { + _log.info("Unsubscribing consumer '" + me.getKey() + "' on channel " + toString()); + } + + Subscription sub = me.getValue(); + + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + + } + + _tag2SubscriptionMap.clear(); + } + + /** + * Add a message to the channel-based list of unacknowledged messages + * + * @param entry the record of the message on the queue that was delivered + * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of the + * delivery tag) + * @param subscription The consumer that is to acknowledge this message. + */ + public void addUnacknowledgedMessage(QueueEntry entry, long deliveryTag, Subscription subscription) + { + if (_log.isDebugEnabled()) + { + if (entry.getQueue() == null) + { + _log.debug("Adding unacked message with a null queue:" + entry.debugIdentity()); + } + else + { + if (_log.isDebugEnabled()) + { + _log.debug(debugIdentity() + " Adding unacked message(" + entry.getMessage().toString() + " DT:" + deliveryTag + + ") with a queue(" + entry.getQueue() + ") for " + subscription); + } + } + } + + _unacknowledgedMessageMap.add(deliveryTag, entry); + + } + + private final String id = "(" + System.identityHashCode(this) + ")"; + + public String debugIdentity() + { + return _channelId + id; + } + + /** + * Called to attempt re-delivery all outstanding unacknowledged messages on the channel. May result in delivery to + * this same channel or to other subscribers. + * + * @throws org.apache.qpid.AMQException if the requeue fails + */ + public void requeue() throws AMQException + { + // we must create a new map since all the messages will get a new delivery tag when they are redelivered + Collection<QueueEntry> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages(); + + // Deliver these messages out of the transaction as their delivery was never + // part of the transaction only the receive. + TransactionalContext deliveryContext = null; + + if (!messagesToBeDelivered.isEmpty()) + { + if (_log.isInfoEnabled()) + { + _log.info("Requeuing " + messagesToBeDelivered.size() + " unacked messages. for " + toString()); + } + + if (!(_txnContext instanceof NonTransactionalContext)) + { + + deliveryContext = + new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages); + } + else + { + deliveryContext = _txnContext; + } + } + + for (QueueEntry unacked : messagesToBeDelivered) + { + if (!unacked.isQueueDeleted()) + { + // Mark message redelivered + unacked.getMessage().setRedelivered(true); + + // Ensure message is released for redelivery + unacked.release(); + + // Deliver Message + deliveryContext.requeue(unacked); + + } + else + { + unacked.discard(_storeContext); + } + } + + } + + /** + * Requeue a single message + * + * @param deliveryTag The message to requeue + * + * @throws AMQException If something goes wrong. + */ + public void requeue(long deliveryTag) throws AMQException + { + QueueEntry unacked = _unacknowledgedMessageMap.remove(deliveryTag); + + if (unacked != null) + { + // Mark message redelivered + unacked.getMessage().setRedelivered(true); + + // Ensure message is released for redelivery + if (!unacked.isQueueDeleted()) + { + unacked.release(); + } + + + // Deliver these messages out of the transaction as their delivery was never + // part of the transaction only the receive. + TransactionalContext deliveryContext; + if (!(_txnContext instanceof NonTransactionalContext)) + { + + deliveryContext = + new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages); + + } + else + { + deliveryContext = _txnContext; + } + + if (!unacked.isQueueDeleted()) + { + // Redeliver the messages to the front of the queue + deliveryContext.requeue(unacked); + // Deliver increments the message count but we have already deliverted this once so don't increment it again + // this was because deliver did an increment changed this. + } + else + { + _log.warn(System.identityHashCode(this) + " Requested requeue of message(" + unacked.getMessage().debugIdentity() + + "):" + deliveryTag + " but no queue defined and no DeadLetter queue so DROPPING message."); + + unacked.discard(_storeContext); + } + } + else + { + _log.warn("Requested requeue of message:" + deliveryTag + " but no such delivery tag exists." + + _unacknowledgedMessageMap.size()); + + } + + } + + /** + * Called to resend all outstanding unacknowledged messages to this same channel. + * + * @param requeue Are the messages to be requeued or dropped. + * + * @throws AMQException When something goes wrong. + */ + public void resend(final boolean requeue) throws AMQException + { + + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + if (_log.isDebugEnabled()) + { + _log.debug("unacked map Size:" + _unacknowledgedMessageMap.size()); + } + + // Process the Unacked-Map. + // Marking messages who still have a consumer for to be resent + // and those that don't to be requeued. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, requeue, _storeContext)); + + + // Process Messages to Resend + if (_log.isDebugEnabled()) + { + if (!msgToResend.isEmpty()) + { + _log.debug("Preparing (" + msgToResend.size() + ") message to resend."); + } + else + { + _log.debug("No message to resend."); + } + } + + for (Map.Entry<Long, QueueEntry> entry : msgToResend.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + + + + AMQMessage msg = message.getMessage(); + AMQQueue queue = message.getQueue(); + + // Our Java Client will always suspend the channel when resending! + // If the client has requested the messages be resent then it is + // their responsibility to ensure that thay are capable of receiving them + // i.e. The channel hasn't been server side suspended. + // if (isSuspended()) + // { + // _log.info("Channel is suspended so requeuing"); + // //move this message to requeue + // msgToRequeue.add(message); + // } + // else + // { + // release to allow it to be delivered + + // Without any details from the client about what has been processed we have to mark + // all messages in the unacked map as redelivered. + msg.setRedelivered(true); + + Subscription sub = message.getDeliveredSubscription(); + + if (sub != null) + { + + if(!queue.resend(message, sub)) + { + msgToRequeue.put(deliveryTag, message); + } + } + else + { + + if (_log.isInfoEnabled()) + { + _log.info("DeliveredSubscription not recorded so just requeueing(" + message.toString() + + ")to prevent loss"); + } + // move this message to requeue + msgToRequeue.put(deliveryTag, message); + } + } // for all messages + // } else !isSuspend + + if (_log.isInfoEnabled()) + { + if (!msgToRequeue.isEmpty()) + { + _log.info("Preparing (" + msgToRequeue.size() + ") message to requeue to."); + } + } + + // Deliver these messages out of the transaction as their delivery was never + // part of the transaction only the receive. + TransactionalContext deliveryContext; + if (!(_txnContext instanceof NonTransactionalContext)) + { + + deliveryContext = + new NonTransactionalContext(_messageStore, _storeContext, this, _returnMessages); + } + else + { + deliveryContext = _txnContext; + } + + // Process Messages to Requeue at the front of the queue + for (Map.Entry<Long, QueueEntry> entry : msgToRequeue.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + + message.release(); + message.setRedelivered(true); + + deliveryContext.requeue(message); + + _unacknowledgedMessageMap.remove(deliveryTag); + } + } + + /** + * Callback indicating that a queue has been deleted. We must update the structure of unacknowledged messages to + * remove the queue reference and also decrement any message reference counts, without actually removing the item + * since we may get an ack for a delivery tag that was generated from the deleted queue. + * + * @param queue the queue that has been deleted + * + */ + /* public void queueDeleted(final AMQQueue queue) + { + try + { + _unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor() + { + public boolean callback(UnacknowledgedMessage message) + { + if (message.getQueue() == queue) + { + try + { + message.discard(_storeContext); + message.setQueueDeleted(true); + + } + catch (AMQException e) + { + _log.error( + "Error decrementing ref count on message " + message.getMessage().getMessageId() + ": " + e, e); + throw new RuntimeException(e); + } + } + + return false; + } + + public void visitComplete() + { + } + }); + } + catch (AMQException e) + { + _log.error("Unexpected Error while handling deletion of queue", e); + throw new RuntimeException(e); + } + + } +*/ + /** + * Acknowledge one or more messages. + * + * @param deliveryTag the last delivery tag + * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only + * acknowledges the single message specified by the delivery tag + * + * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel + */ + public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException + { + _unacknowledgedMessageMap.acknowledgeMessage(deliveryTag, multiple, _txnContext); + } + + /** + * Used only for testing purposes. + * + * @return the map of unacknowledged messages + */ + public UnacknowledgedMessageMap getUnacknowledgedMessageMap() + { + return _unacknowledgedMessageMap; + } + + + public void setSuspended(boolean suspended) + { + + + boolean wasSuspended = _suspended.getAndSet(suspended); + if (wasSuspended != suspended) + { + if (wasSuspended) + { + // may need to deliver queued messages + for (Subscription s : _tag2SubscriptionMap.values()) + { + s.getQueue().deliverAsync(s); + } + } + } + } + + public boolean isSuspended() + { + return _suspended.get(); + } + + public void commit() throws AMQException + { + if (!isTransactional()) + { + throw new AMQException("Fatal error: commit called on non-transactional channel"); + } + + _txnContext.commit(); + } + + public void rollback() throws AMQException + { + _txnContext.rollback(); + } + + public String toString() + { + return "["+_session.toString()+":"+_channelId+"]"; + } + + public void setDefaultQueue(AMQQueue queue) + { + _defaultQueue = queue; + } + + public AMQQueue getDefaultQueue() + { + return _defaultQueue; + } + + public StoreContext getStoreContext() + { + return _storeContext; + } + + public void processReturns() throws AMQException + { + if (!_returnMessages.isEmpty()) + { + for (RequiredDeliveryException bouncedMessage : _returnMessages) + { + AMQMessage message = bouncedMessage.getAMQMessage(); + _session.getProtocolOutputConverter().writeReturn(message, _channelId, bouncedMessage.getReplyCode().getCode(), + new AMQShortString(bouncedMessage.getMessage())); + + message.decrementReference(_storeContext); + } + + _returnMessages.clear(); + } + } + + + public TransactionalContext getTransactionalContext() + { + return _txnContext; + } + + public boolean isClosing() + { + return _closing; + } + + public AMQProtocolSession getProtocolSession() + { + return _session; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + public void setCredit(final long prefetchSize, final int prefetchCount) + { + _creditManager.setCreditLimits(prefetchSize, prefetchCount); + } + + public List<RequiredDeliveryException> getReturnMessages() + { + return _returnMessages; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + private final ClientDeliveryMethod _clientDeliveryMethod = new ClientDeliveryMethod() + { + + public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) + throws AMQException + { + getProtocolSession().getProtocolOutputConverter().writeDeliver(entry.getMessage(), getChannelId(), deliveryTag, sub.getConsumerTag()); + } + }; + + public ClientDeliveryMethod getClientDeliveryMethod() + { + return _clientDeliveryMethod; + } + + private final RecordDeliveryMethod _recordDeliveryMethod = new RecordDeliveryMethod() + { + + public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag) + { + addUnacknowledgedMessage(entry, deliveryTag, sub); + } + }; + + public RecordDeliveryMethod getRecordDeliveryMethod() + { + return _recordDeliveryMethod; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.java new file mode 100644 index 0000000000..9a98af5689 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ConsumerTagNotUniqueException.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; + +public class ConsumerTagNotUniqueException extends Exception +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java new file mode 100644 index 0000000000..29494c4118 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java @@ -0,0 +1,108 @@ +/* + * + * 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; + +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.Map; + +public class ExtractResendAndRequeue implements UnacknowledgedMessageMap.Visitor +{ + private static final Logger _log = Logger.getLogger(ExtractResendAndRequeue.class); + + private Map<Long, QueueEntry> _msgToRequeue; + private Map<Long, QueueEntry> _msgToResend; + private boolean _requeueIfUnabletoResend; + private StoreContext _storeContext; + private UnacknowledgedMessageMap _unacknowledgedMessageMap; + + public ExtractResendAndRequeue(UnacknowledgedMessageMap unacknowledgedMessageMap, + Map<Long, QueueEntry> msgToRequeue, + Map<Long, QueueEntry> msgToResend, + boolean requeueIfUnabletoResend, + StoreContext storeContext) + { + _unacknowledgedMessageMap = unacknowledgedMessageMap; + _msgToRequeue = msgToRequeue; + _msgToResend = msgToResend; + _requeueIfUnabletoResend = requeueIfUnabletoResend; + _storeContext = storeContext; + } + + public boolean callback(final long deliveryTag, QueueEntry message) throws AMQException + { + + AMQMessage msg = message.getMessage(); + msg.setRedelivered(true); + final Subscription subscription = message.getDeliveredSubscription(); + if (subscription != null) + { + // Consumer exists + if (!subscription.isClosed()) + { + _msgToResend.put(deliveryTag, message); + } + else // consumer has gone + { + _msgToRequeue.put(deliveryTag, message); + } + } + else + { + // Message has no consumer tag, so was "delivered" to a GET + // or consumer no longer registered + // cannot resend, so re-queue. + if (!message.isQueueDeleted()) + { + if (_requeueIfUnabletoResend) + { + _msgToRequeue.put(deliveryTag, message); + } + else + { + message.discard(_storeContext); + _log.info("No DeadLetter Queue and requeue not requested so dropping message:" + message); + } + } + else + { + message.discard(_storeContext); + _log.warn("Message.queue is null and no DeadLetter Queue so dropping message:" + message); + } + } + + // false means continue processing + return false; + } + + public void visitComplete() + { + _unacknowledgedMessageMap.clear(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java new file mode 100644 index 0000000000..49619ac5b0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java @@ -0,0 +1,509 @@ +/* + * + * 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; + +import java.io.File; +import java.io.IOException; +import java.net.BindException; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import javax.management.NotCompliantMBeanException; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.FixedSizeByteBufferAllocator; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.pool.ReadWriteThreadModel; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean; +import org.apache.qpid.server.logging.management.LoggingManagementMBean; +import org.apache.qpid.server.protocol.AMQPFastProtocolHandler; +import org.apache.qpid.server.protocol.AMQPProtocolProvider; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; + +/** + * Main entry point for AMQPD. + * + */ +@SuppressWarnings({"AccessStaticViaInstance"}) +public class Main +{ + private static final Logger _logger = Logger.getLogger(Main.class); + public static final Logger _brokerLogger = Logger.getLogger("Qpid.Broker"); + + private static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + private static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + public static final String QPID_HOME = "QPID_HOME"; + private static final int IPV4_ADDRESS_LENGTH = 4; + + private static final char IPV4_LITERAL_SEPARATOR = '.'; + + protected static class InitException extends Exception + { + InitException(String msg, Throwable cause) + { + super(msg, cause); + } + } + + protected final Options options = new Options(); + protected CommandLine commandLine; + + protected Main(String[] args) + { + setOptions(options); + if (parseCommandline(args)) + { + execute(); + } + } + + protected boolean parseCommandline(String[] args) + { + try + { + commandLine = new PosixParser().parse(options, args); + + return true; + } + catch (ParseException e) + { + System.err.println("Error: " + e.getMessage()); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + + return false; + } + } + + protected void setOptions(Options options) + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = + OptionBuilder.withArgName("file").hasArg().withDescription("use given configuration file").withLongOpt("config") + .create("c"); + Option port = + OptionBuilder.withArgName("port").hasArg() + .withDescription("listen on the specified port. Overrides any value in the config file") + .withLongOpt("port").create("p"); + Option mport = + OptionBuilder.withArgName("mport").hasArg() + .withDescription("listen on the specified management port. Overrides any value in the config file") + .withLongOpt("mport").create("m"); + + + Option bind = + OptionBuilder.withArgName("bind").hasArg() + .withDescription("bind to the specified address. Overrides any value in the config file") + .withLongOpt("bind").create("b"); + Option logconfig = + OptionBuilder.withArgName("logconfig").hasArg() + .withDescription("use the specified log4j xml configuration file. By " + + "default looks for a file named " + DEFAULT_LOG_CONFIG_FILENAME + + " in the same directory as the configuration file").withLongOpt("logconfig").create("l"); + Option logwatchconfig = + OptionBuilder.withArgName("logwatch").hasArg() + .withDescription("monitor the log file configuration file for changes. Units are seconds. " + + "Zero means do not check for changes.").withLongOpt("logwatch").create("w"); + + options.addOption(help); + options.addOption(version); + options.addOption(configFile); + options.addOption(logconfig); + options.addOption(logwatchconfig); + options.addOption(port); + options.addOption(mport); + options.addOption(bind); + } + + protected void execute() + { + // note this understands either --help or -h. If an option only has a long name you can use that but if + // an option has a short name and a long name you must use the short name here. + if (commandLine.hasOption("h")) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + } + else if (commandLine.hasOption("v")) + { + String ver = QpidProperties.getVersionString(); + + StringBuilder protocol = new StringBuilder("AMQP version(s) [major.minor]: "); + + boolean first = true; + for (ProtocolVersion pv : ProtocolVersion.getSupportedProtocolVersions()) + { + if (first) + { + first = false; + } + else + { + protocol.append(", "); + } + + protocol.append(pv.getMajorVersion()).append('-').append(pv.getMinorVersion()); + + } + + System.out.println(ver + " (" + protocol + ")"); + } + else + { + try + { + startup(); + } + catch (InitException e) + { + System.out.println(e.getMessage()); + _brokerLogger.error("Initialisation Error : " + e.getMessage()); + shutdown(1); + } + catch (Throwable e) + { + System.out.println("Error initialising message broker: " + e); + _brokerLogger.error("Error initialising message broker: " + e); + e.printStackTrace(); + shutdown(1); + } + } + } + + protected void shutdown(int status) + { + ApplicationRegistry.removeAll(); + System.exit(status); + } + + protected void startup() throws Exception + { + final String QpidHome = System.getProperty(QPID_HOME); + final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE); + final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath())); + if (!configFile.exists()) + { + String error = "File " + configFile + " could not be found. Check the file exists and is readable."; + + if (QpidHome == null) + { + error = error + "\nNote: " + QPID_HOME + " is not set."; + } + + throw new InitException(error, null); + } + else + { + System.out.println("Using configuration file " + configFile.getAbsolutePath()); + } + + String logConfig = commandLine.getOptionValue("l"); + String logWatchConfig = commandLine.getOptionValue("w", "0"); + + int logWatchTime = 0; + try + { + logWatchTime = Integer.parseInt(logWatchConfig); + } + catch (NumberFormatException e) + { + System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " + + "a non-negative integer. Using default of zero (no watching configured"); + } + + File logConfigFile; + if (logConfig != null) + { + logConfigFile = new File(logConfig); + configureLogging(logConfigFile, logWatchTime); + } + else + { + File configFileDirectory = configFile.getParentFile(); + logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); + configureLogging(logConfigFile, logWatchTime); + } + + ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(configFile); + ServerConfiguration serverConfig = config.getConfiguration(); + updateManagementPort(serverConfig, commandLine.getOptionValue("m")); + + ApplicationRegistry.initialise(config); + + configureLoggingManagementMBean(logConfigFile, logWatchTime); + + ConfigurationManagementMBean configMBean = new ConfigurationManagementMBean(); + configMBean.register(); + + //fixme .. use QpidProperties.getVersionString when we have fixed the classpath issues + // that are causing the broker build to pick up the wrong properties file and hence say + // Starting Qpid Client + _brokerLogger.info("Starting Qpid Broker " + QpidProperties.getReleaseVersion() + + " build: " + QpidProperties.getBuildVersion()); + + ByteBuffer.setUseDirectBuffers(serverConfig.getEnableDirectBuffers()); + + // the MINA default is currently to use the pooled allocator although this may change in future + // once more testing of the performance of the simple allocator has been done + if (!serverConfig.getEnablePooledAllocator()) + { + ByteBuffer.setAllocator(new FixedSizeByteBufferAllocator()); + } + + if(serverConfig.getUseBiasedWrites()) + { + System.setProperty("org.apache.qpid.use_write_biased_pool","true"); + } + + int port = serverConfig.getPort(); + + String portStr = commandLine.getOptionValue("p"); + if (portStr != null) + { + try + { + port = Integer.parseInt(portStr); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + portStr, e); + } + } + + bind(port, serverConfig); + } + + /** + * Update the configuration data with the management port. + * @param configuration + * @param managementPort The string from the command line + */ + private void updateManagementPort(ServerConfiguration configuration, String managementPort) + { + if (managementPort != null) + { + try + { + configuration.setJMXManagementPort(Integer.parseInt(managementPort)); + } + catch (NumberFormatException e) + { + _logger.warn("Invalid management port: " + managementPort + " will use:" + configuration.getJMXManagementPort(), e); + } + } + } + + protected void bind(int port, ServerConfiguration config) throws BindException + { + String bindAddr = commandLine.getOptionValue("b"); + if (bindAddr == null) + { + bindAddr = config.getBind(); + } + + try + { + IoAcceptor acceptor; + + if (ApplicationRegistry.getInstance().getConfiguration().getQpidNIO()) + { + _logger.warn("Using Qpid Multithreaded IO Processing"); + acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(config.getProcessors(), new NewThreadExecutor()); + } + else + { + _logger.warn("Using Mina IO Processing"); + acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(config.getProcessors(), new NewThreadExecutor()); + } + + SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig(); + SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig(); + + sc.setReceiveBufferSize(config.getReceiveBufferSize()); + sc.setSendBufferSize(config.getWriteBufferSize()); + sc.setTcpNoDelay(config.getTcpNoDelay()); + + // if we do not use the executor pool threading model we get the default leader follower + // implementation provided by MINA + if (config.getEnableExecutorPool()) + { + sconfig.setThreadModel(ReadWriteThreadModel.getInstance()); + } + + if (!config.getEnableSSL() || !config.getSSLOnly()) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + InetSocketAddress bindAddress; + if (bindAddr.equals("wildcard")) + { + bindAddress = new InetSocketAddress(port); + } + else + { + bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port); + } + + bind(acceptor, bindAddress, handler, sconfig); + + //fixme qpid.AMQP should be using qpidproperties to get value + _brokerLogger.info("Qpid.AMQP listening on non-SSL address " + bindAddress); + } + + if (config.getEnableSSL()) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + try + { + + bind(acceptor, new InetSocketAddress(config.getSSLPort()), handler, sconfig); + + //fixme qpid.AMQP should be using qpidproperties to get value + _brokerLogger.info("Qpid.AMQP listening on SSL port " + config.getSSLPort()); + + } + catch (IOException e) + { + _brokerLogger.error("Unable to listen on SSL port: " + e, e); + } + } + + //fixme qpid.AMQP should be using qpidproperties to get value + _brokerLogger.info("Qpid Broker Ready :" + QpidProperties.getReleaseVersion() + + " build: " + QpidProperties.getBuildVersion()); + } + catch (Exception e) + { + _logger.error("Unable to bind service to registry: " + e, e); + //fixme this need tidying up + throw new BindException(e.getMessage()); + } + } + + /** + * Ensure that any bound Acceptors are recorded in the registry so they can be closed later. + * + * @param acceptor + * @param bindAddress + * @param handler + * @param sconfig + * + * @throws IOException from the acceptor.bind command + */ + private void bind(IoAcceptor acceptor, InetSocketAddress bindAddress, AMQPFastProtocolHandler handler, SocketAcceptorConfig sconfig) throws IOException + { + acceptor.bind(bindAddress, handler, sconfig); + + ApplicationRegistry.getInstance().addAcceptor(bindAddress, acceptor); + } + + public static void main(String[] args) + { + + new Main(args); + } + + private byte[] parseIP(String address) throws Exception + { + char[] literalBuffer = address.toCharArray(); + int byteCount = 0; + int currByte = 0; + byte[] ip = new byte[IPV4_ADDRESS_LENGTH]; + for (int i = 0; i < literalBuffer.length; i++) + { + char currChar = literalBuffer[i]; + if ((currChar >= '0') && (currChar <= '9')) + { + currByte = (currByte * 10) + (Character.digit(currChar, 10) & 0xFF); + } + + if (currChar == IPV4_LITERAL_SEPARATOR || (i + 1 == literalBuffer.length)) + { + ip[byteCount++] = (byte) currByte; + currByte = 0; + } + } + + if (byteCount != 4) + { + throw new Exception("Invalid IP address: " + address); + } + return ip; + } + + private void configureLogging(File logConfigFile, int logWatchTime) + { + if (logConfigFile.exists() && logConfigFile.canRead()) + { + System.out.println("Configuring logger using configuration file " + logConfigFile.getAbsolutePath()); + if (logWatchTime > 0) + { + System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " + + logWatchTime + " seconds"); + // log4j expects the watch interval in milliseconds + DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); + } + else + { + DOMConfigurator.configure(logConfigFile.getAbsolutePath()); + } + } + else + { + System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); + System.err.println("Using basic log4j configuration"); + BasicConfigurator.configure(); + } + } + + private void configureLoggingManagementMBean(File logConfigFile, int logWatchTime) throws Exception + { + LoggingManagementMBean blm = new LoggingManagementMBean(logConfigFile.getPath(),logWatchTime); + + try + { + blm.register(); + } + catch (AMQException e) + { + throw new InitException("Unable to initialise the Logging Management MBean: ", e); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java new file mode 100644 index 0000000000..e76f9c3f6c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ManagedChannel.java @@ -0,0 +1,68 @@ +/* + * + * 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; + +import java.io.IOException; + +import javax.management.JMException; + +/** + * The managed interface exposed to allow management of channels. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedChannel +{ + static final String TYPE = "Channel"; + + /** + * Tells whether the channel is transactional. + * @return true if the channel is transactional. + * @throws IOException + */ + boolean isTransactional() throws IOException; + + /** + * Tells the number of unacknowledged messages in this channel. + * @return number of unacknowledged messages. + * @throws IOException + */ + int getUnacknowledgedMessageCount() throws IOException; + + + //********** Operations *****************// + + /** + * Commits the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void commitTransactions() throws IOException, JMException; + + /** + * Rollsback the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void rollbackTransactions() throws IOException, JMException; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java new file mode 100644 index 0000000000..3f1947d65a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/RequiredDeliveryException.java @@ -0,0 +1,79 @@ +/* + * + * 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; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.queue.AMQMessage; + +/** + * Signals that a required delivery could not be made. This could be bacuse of the immediate flag being set and the + * queue having no consumers, or the mandatory flag being set and the exchange having no valid bindings. + * + * <p/>The failed message is associated with this error condition, by taking a reference to it. This enables the + * correct compensating action to be taken against the message, for example, bouncing it back to the sender. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to deliver a message that must be delivered. + * <tr><td> Associate the failed message with the error condition. <td> {@link AMQMessage} + * </table> + */ +public abstract class RequiredDeliveryException extends AMQException +{ + private AMQMessage _amqMessage; + + public RequiredDeliveryException(String message, AMQMessage payload) + { + super(message); + + setMessage(payload); + } + + + public RequiredDeliveryException(String message) + { + super(message); + } + + public void setMessage(final AMQMessage payload) + { + + // Increment the reference as this message is in the routing phase + // and so will have the ref decremented as routing fails. + // we need to keep this message around so we can return it in the + // handler. So increment here. + _amqMessage = payload.takeReference(); + + } + + public AMQMessage getAMQMessage() + { + return _amqMessage; + } + + public AMQConstant getErrorCode() + { + return getReplyCode(); + } + + public abstract AMQConstant getReplyCode(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java new file mode 100644 index 0000000000..db3a05eb52 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/TxAck.java @@ -0,0 +1,148 @@ +/* + * + * 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.ack; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.txn.TxnOp; +import org.apache.qpid.server.queue.QueueEntry; + +/** + * A TxnOp implementation for handling accumulated acks + */ +public class TxAck implements TxnOp +{ + private final UnacknowledgedMessageMap _map; + private final Map<Long, QueueEntry> _unacked = new HashMap<Long,QueueEntry>(); + private List<Long> _individual; + private long _deliveryTag; + private boolean _multiple; + + public TxAck(UnacknowledgedMessageMap map) + { + _map = map; + } + + public void update(long deliveryTag, boolean multiple) + { + _unacked.clear(); + if (!multiple) + { + if(_individual == null) + { + _individual = new ArrayList<Long>(); + } + //have acked a single message that is not part of + //the previously acked region so record + //individually + _individual.add(deliveryTag);//_multiple && !multiple + } + else if (deliveryTag > _deliveryTag) + { + //have simply moved the last acked message on a + //bit + _deliveryTag = deliveryTag; + _multiple = true; + } + } + + public void consolidate() + { + if(_unacked.isEmpty()) + { + //lookup all the unacked messages that have been acked in this transaction + if (_multiple) + { + //get all the unacked messages for the accumulated + //multiple acks + _map.collect(_deliveryTag, true, _unacked); + } + if(_individual != null) + { + //get any unacked messages for individual acks outside the + //range covered by multiple acks + for (long tag : _individual) + { + if(_deliveryTag < tag) + { + _map.collect(tag, false, _unacked); + } + } + } + } + } + + public boolean checkPersistent() throws AMQException + { + consolidate(); + //if any of the messages in unacked are persistent the txn + //buffer must be marked as persistent: + for (QueueEntry msg : _unacked.values()) + { + if (msg.getMessage().isPersistent()) + { + return true; + } + } + return false; + } + + public void prepare(StoreContext storeContext) throws AMQException + { + //make persistent changes, i.e. dequeue and decrementReference + for (QueueEntry msg : _unacked.values()) + { + //Message has been ack so discard it. This will dequeue and decrement the reference. + msg.discard(storeContext); + + } + } + + public void undoPrepare() + { + //decrementReference is annoyingly untransactional (due to + //in memory counter) so if we failed in prepare for full + //txn, this op will have to compensate by fixing the count + //in memory (persistent changes will be rolled back by store) + for (QueueEntry msg : _unacked.values()) + { + msg.getMessage().takeReference(); + } + } + + public void commit(StoreContext storeContext) + { + //remove the unacked messages from the channels map + _map.remove(_unacked); + } + + public void rollback(StoreContext storeContext) + { + } +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java new file mode 100644 index 0000000000..c80a96f967 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java @@ -0,0 +1,81 @@ +/* + * + * 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.ack; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.StoreContext; + +public interface UnacknowledgedMessageMap +{ + public interface Visitor + { + /** + * @param deliveryTag + *@param message the message being iterated over @return true to stop iteration, false to continue + * @throws AMQException + */ + boolean callback(final long deliveryTag, QueueEntry message) throws AMQException; + + void visitComplete(); + } + + void visit(Visitor visitor) throws AMQException; + + void add(long deliveryTag, QueueEntry message); + + void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs); + + boolean contains(long deliveryTag) throws AMQException; + + void remove(Map<Long,QueueEntry> msgs); + + QueueEntry remove(long deliveryTag); + + public void drainTo(long deliveryTag, StoreContext storeContext) throws AMQException; + + Collection<QueueEntry> cancelAllMessages(); + + void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) throws AMQException; + + int size(); + + void clear(); + + QueueEntry get(long deliveryTag); + + /** + * Get the set of delivery tags that are outstanding. + * + * @return a set of delivery tags + */ + Set<Long> getDeliveryTags(); + + public long getUnacknowledgeBytes(); +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java new file mode 100644 index 0000000000..c567387662 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.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.ack; + +import org.apache.qpid.server.store.StoreContext; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.txn.TransactionalContext; + +public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap +{ + private final Object _lock = new Object(); + + private long _unackedSize; + + private Map<Long, QueueEntry> _map; + + private long _lastDeliveryTag; + + private final int _prefetchLimit; + + public UnacknowledgedMessageMapImpl(int prefetchLimit) + { + _prefetchLimit = prefetchLimit; + _map = new LinkedHashMap<Long, QueueEntry>(prefetchLimit); + } + + public void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs) + { + if (multiple) + { + collect(deliveryTag, msgs); + } + else + { + msgs.put(deliveryTag, get(deliveryTag)); + } + + } + + public boolean contains(long deliveryTag) throws AMQException + { + synchronized (_lock) + { + return _map.containsKey(deliveryTag); + } + } + + public void remove(Map<Long,QueueEntry> msgs) + { + synchronized (_lock) + { + for (Long deliveryTag : msgs.keySet()) + { + remove(deliveryTag); + } + } + } + + public QueueEntry remove(long deliveryTag) + { + synchronized (_lock) + { + + QueueEntry message = _map.remove(deliveryTag); + if(message != null) + { + _unackedSize -= message.getMessage().getSize(); + + } + + return message; + } + } + + public void visit(Visitor visitor) throws AMQException + { + synchronized (_lock) + { + Set<Map.Entry<Long, QueueEntry>> currentEntries = _map.entrySet(); + for (Map.Entry<Long, QueueEntry> entry : currentEntries) + { + visitor.callback(entry.getKey().longValue(), entry.getValue()); + } + visitor.visitComplete(); + } + } + + public void add(long deliveryTag, QueueEntry message) + { + synchronized (_lock) + { + _map.put(deliveryTag, message); + _unackedSize += message.getMessage().getSize(); + _lastDeliveryTag = deliveryTag; + } + } + + public Collection<QueueEntry> cancelAllMessages() + { + synchronized (_lock) + { + Collection<QueueEntry> currentEntries = _map.values(); + _map = new LinkedHashMap<Long, QueueEntry>(_prefetchLimit); + _unackedSize = 0l; + return currentEntries; + } + } + + public void acknowledgeMessage(long deliveryTag, boolean multiple, TransactionalContext txnContext) + throws AMQException + { + synchronized (_lock) + { + txnContext.acknowledgeMessage(deliveryTag, _lastDeliveryTag, multiple, this); + } + } + + public int size() + { + synchronized (_lock) + { + return _map.size(); + } + } + + public void clear() + { + synchronized (_lock) + { + _map.clear(); + _unackedSize = 0l; + } + } + + public void drainTo(long deliveryTag, StoreContext storeContext) throws AMQException + + { + synchronized (_lock) + { + Iterator<Map.Entry<Long, QueueEntry>> it = _map.entrySet().iterator(); + while (it.hasNext()) + { + Map.Entry<Long, QueueEntry> unacked = it.next(); + + if (unacked.getKey() > deliveryTag) + { + //This should not occur now. + throw new AMQException("UnacknowledgedMessageMap is out of order:" + unacked.getKey() + + " When deliveryTag is:" + deliveryTag + "ES:" + _map.entrySet().toString()); + } + + //Message has been ack so discard it. This will dequeue and decrement the reference. + unacked.getValue().discard(storeContext); + + it.remove(); + + _unackedSize -= unacked.getValue().getMessage().getSize(); + + + if (unacked.getKey() == deliveryTag) + { + break; + } + } + } + } + + public QueueEntry get(long key) + { + synchronized (_lock) + { + return _map.get(key); + } + } + + public Set<Long> getDeliveryTags() + { + synchronized (_lock) + { + return _map.keySet(); + } + } + + private void collect(long key, Map<Long, QueueEntry> msgs) + { + synchronized (_lock) + { + for (Map.Entry<Long, QueueEntry> entry : _map.entrySet()) + { + msgs.put(entry.getKey(),entry.getValue()); + if (entry.getKey() == key) + { + break; + } + } + } + } + + public long getUnacknowledgeBytes() + { + return _unackedSize; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java new file mode 100644 index 0000000000..c7cf0c0892 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.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.configuration; + +import org.apache.commons.configuration.Configuration; + + +public class ExchangeConfiguration +{ + + private Configuration _config; + private String _name; + + public ExchangeConfiguration(String exchName, Configuration subset) + { + _name = exchName; + _config = subset; + } + + public String getName() + { + return _name; + } + + public String getType() + { + return _config.getString("type","direct"); + } + + public boolean getDurable() + { + return _config.getBoolean("durable", false); + } + + public boolean getAutoDelete() + { + return _config.getBoolean("autodelete",false); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java new file mode 100644 index 0000000000..74bb7ee969 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java @@ -0,0 +1,111 @@ +/* + * + * 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.configuration; + +import java.util.List; + +import org.apache.commons.configuration.Configuration; + +public class QueueConfiguration +{ + + private Configuration _config; + private String _name; + private VirtualHostConfiguration _vHostConfig; + + public QueueConfiguration(String name, Configuration config, VirtualHostConfiguration virtualHostConfiguration) + { + _vHostConfig = virtualHostConfiguration; + _config = config; + _name = name; + } + + public VirtualHostConfiguration getVirtualHostConfiguration() + { + return _vHostConfig; + } + + public boolean getDurable() + { + return _config.getBoolean("durable" ,false); + } + + public boolean getAutoDelete() + { + return _config.getBoolean("autodelete", false); + } + + public String getOwner() + { + return _config.getString("owner", null); + } + + public boolean getPriority() + { + return _config.getBoolean("priority", false); + } + + public int getPriorities() + { + return _config.getInt("priorities", -1); + } + + public String getExchange() + { + return _config.getString("exchange", null); + } + + public List getRoutingKeys() + { + return _config.getList("routingKey"); + } + + public String getName() + { + return _name; + } + + public int getMaximumMessageAge() + { + return _config.getInt("maximumMessageAge", _vHostConfig.getMaximumMessageAge()); + } + + public long getMaximumQueueDepth() + { + return _config.getLong("maximumQueueDepth", _vHostConfig.getMaximumQueueDepth()); + } + + public long getMaximumMessageSize() + { + return _config.getLong("maximumMessageSize", _vHostConfig.getMaximumMessageSize()); + } + + public long getMaximumMessageCount() + { + return _config.getLong("maximumMessageCount", _vHostConfig.getMaximumMessageCount()); + } + + public long getMinimumAlertRepeatGap() + { + return _config.getLong("minimumAlertRepeatGap", _vHostConfig.getMinimumAlertRepeatGap()); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java new file mode 100644 index 0000000000..5d080f8df1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SecurityConfiguration.java @@ -0,0 +1,41 @@ +/* + * 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.configuration; + +import org.apache.commons.configuration.Configuration; + +public class SecurityConfiguration +{ + + private Configuration _conf; + + public SecurityConfiguration(Configuration configuration) + { + _conf = configuration; + } + + public Configuration getConfiguration() + { + return _conf; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java new file mode 100644 index 0000000000..28a07e7ebf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java @@ -0,0 +1,519 @@ +/* + * 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.configuration; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConfigurationFactory; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sun.misc.Signal; +import sun.misc.SignalHandler; + +public class ServerConfiguration implements SignalHandler +{ + + private static Configuration _config; + + private static final int DEFAULT_FRAME_SIZE = 65536; + private static final int DEFAULT_BUFFER_READ_LIMIT_SIZE = 262144; + private static final int DEFAULT_BUFFER_WRITE_LIMIT_SIZE = 262144; + private static final int DEFAULT_PORT = 5672; + private static final int DEFAUL_SSL_PORT = 8672; + private static final long DEFAULT_HOUSEKEEPING_PERIOD = 30000L; + private static final int DEFAULT_JMXPORT = 8999; + + private static int _jmxPort = DEFAULT_JMXPORT; + + private Map<String, VirtualHostConfiguration> _virtualHosts = new HashMap<String, VirtualHostConfiguration>(); + private SecurityConfiguration _securityConfiguration = null; + + private File _configFile; + + private Logger _log = LoggerFactory.getLogger(this.getClass()); + + private ConfigurationManagementMBean _mbean; + + + // Map of environment variables to config items + private static final Map<String, String> envVarMap = new HashMap<String, String>(); + + { + envVarMap.put("QPID_PORT", "connector.port"); + envVarMap.put("QPID_ENABLEDIRECTBUFFERS", "advanced.enableDirectBuffers"); + envVarMap.put("QPID_SSLPORT", "connector.ssl.port"); + envVarMap.put("QPID_NIO", "connector.qpidnio"); + envVarMap.put("QPID_WRITEBIASED", "advanced.useWriteBiasedPool"); + envVarMap.put("QPID_JMXPORT", "management.jmxport"); + envVarMap.put("QPID_FRAMESIZE", "advanced.framesize"); + envVarMap.put("QPID_MSGAUTH", "security.msg-auth"); + envVarMap.put("QPID_AUTOREGISTER", "auto_register"); + envVarMap.put("QPID_MANAGEMENTENABLED", "management.enabled"); + envVarMap.put("QPID_HEARTBEATDELAY", "heartbeat.delay"); + envVarMap.put("QPID_HEARTBEATTIMEOUTFACTOR", "heartbeat.timeoutFactor"); + envVarMap.put("QPID_MAXIMUMMESSAGEAGE", "maximumMessageAge"); + envVarMap.put("QPID_MAXIMUMMESSAGECOUNT", "maximumMessageCount"); + envVarMap.put("QPID_MAXIMUMQUEUEDEPTH", "maximumQueueDepth"); + envVarMap.put("QPID_MAXIMUMMESSAGESIZE", "maximumMessageSize"); + envVarMap.put("QPID_MINIMUMALERTREPEATGAP", "minimumAlertRepeatGap"); + envVarMap.put("QPID_SOCKETRECEIVEBUFFER", "connector.socketReceiveBuffer"); + envVarMap.put("QPID_SOCKETWRITEBUFFER", "connector.socketWriteBuffer"); + envVarMap.put("QPID_TCPNODELAY", "connector.tcpNoDelay"); + envVarMap.put("QPID_ENABLEPOOLEDALLOCATOR", "advanced.enablePooledAllocator"); + } + + public ServerConfiguration(File configurationURL) throws ConfigurationException + { + this(parseConfig(configurationURL)); + _configFile = configurationURL; + sun.misc.Signal.handle(new sun.misc.Signal("HUP"), this); + } + + public ServerConfiguration(Configuration conf) throws ConfigurationException + { + _config = conf; + + substituteEnvironmentVariables(); + + _jmxPort = _config.getInt("management.jmxport", 8999); + _securityConfiguration = new SecurityConfiguration(conf.subset("security")); + + setupVirtualHosts(conf); + + } + + private void setupVirtualHosts(Configuration conf) throws ConfigurationException + { + List vhosts = conf.getList("virtualhosts"); + Iterator i = vhosts.iterator(); + while (i.hasNext()) + { + Object thing = i.next(); + if (thing instanceof String) + { + XMLConfiguration vhostConfiguration = new XMLConfiguration((String) thing); + List hosts = vhostConfiguration.getList("virtualhost.name"); + for (int j = 0; j < hosts.size(); j++) + { + String name = (String) hosts.get(j); + // Add the keys of the virtual host to the main config then bail out + + Configuration myConf = vhostConfiguration.subset("virtualhost." + name); + Iterator k = myConf.getKeys(); + while (k.hasNext()) + { + String key = (String) k.next(); + conf.setProperty("virtualhosts.virtualhost."+name+"."+key, myConf.getProperty(key)); + } + VirtualHostConfiguration vhostConfig = new VirtualHostConfiguration(name, conf.subset("virtualhosts.virtualhost."+name)); + _virtualHosts.put(vhostConfig.getName(), vhostConfig); + } + } + } + } + + private void substituteEnvironmentVariables() + { + for (Entry<String, String> var : envVarMap.entrySet()) + { + String val = System.getenv(var.getKey()); + if (val != null) + { + _config.setProperty(var.getValue(), val); + } + } + } + + private final static Configuration parseConfig(File file) throws ConfigurationException + { + ConfigurationFactory factory = new ConfigurationFactory(); + factory.setConfigurationFileName(file.getAbsolutePath()); + Configuration conf = factory.getConfiguration(); + Iterator keys = conf.getKeys(); + if (!keys.hasNext()) + { + keys = null; + conf = flatConfig(file); + } + return conf; + } + + // Our configuration class needs to make the interpolate method + // public so it can be called below from the config method. + private static class MyConfiguration extends CompositeConfiguration + { + public String interpolate(String obj) + { + return super.interpolate(obj); + } + } + + private final static Configuration flatConfig(File file) throws ConfigurationException + { + // We have to override the interpolate methods so that + // interpolation takes place accross the entirety of the + // composite configuration. Without doing this each + // configuration object only interpolates variables defined + // inside itself. + final MyConfiguration conf = new MyConfiguration(); + conf.addConfiguration(new SystemConfiguration() + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + conf.addConfiguration(new XMLConfiguration(file) + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + return conf; + } + + @Override + public void handle(Signal arg0) + { + try + { + reparseConfigFile(); + } + catch (ConfigurationException e) + { + _log.error("Could not reload configuration file", e); + } + } + + public void reparseConfigFile() throws ConfigurationException + { + if (_configFile != null) + { + Configuration newConfig = parseConfig(_configFile); + _securityConfiguration = new SecurityConfiguration(newConfig.subset("security")); + ApplicationRegistry.getInstance().getAccessManager().configurePlugins(_securityConfiguration); + + VirtualHostRegistry vhostRegistry = ApplicationRegistry.getInstance().getVirtualHostRegistry(); + for (String hostname : _virtualHosts.keySet()) + { + VirtualHost vhost = vhostRegistry.getVirtualHost(hostname); + SecurityConfiguration hostSecurityConfig = new SecurityConfiguration(newConfig.subset("virtualhosts.virtualhost."+hostname+".security")); + vhost.getAccessManager().configureHostPlugins(hostSecurityConfig); + } + } + } + + public String getQpidWork() + { + return System.getProperty("QPID_WORK", System.getProperty("java.io.tmpdir")); + } + + public void setJMXManagementPort(int mport) + { + _jmxPort = mport; + } + + public int getJMXManagementPort() + { + return _jmxPort; + } + + public boolean getPlatformMbeanserver() + { + return _config.getBoolean("management.platform-mbeanserver", true); + } + + public String[] getVirtualHosts() + { + return _virtualHosts.keySet().toArray(new String[_virtualHosts.size()]); + } + + public String getPluginDirectory() + { + return _config.getString("plugin-directory"); + } + + public VirtualHostConfiguration getVirtualHostConfig(String name) + { + return _virtualHosts.get(name); + } + + public List<String> getPrincipalDatabaseNames() + { + return _config.getList("security.principal-databases.principal-database.name"); + } + + public List<String> getPrincipalDatabaseClass() + { + return _config.getList("security.principal-databases.principal-database.class"); + } + + public List<String> getPrincipalDatabaseAttributeNames(int index) + { + String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.name"; + return _config.getList(name); + } + + public List<String> getPrincipalDatabaseAttributeValues(int index) + { + String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.value"; + return _config.getList(name); + } + + public List<String> getManagementPrincipalDBs() + { + return _config.getList("security.jmx.principal-database"); + } + + public List<String> getManagementAccessList() + { + return _config.getList("security.jmx.access"); + } + + public int getFrameSize() + { + return _config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE); + } + + public boolean getProtectIOEnabled() + { + return _config.getBoolean("broker.connector.protectio.enabled", false); + } + + public int getBufferReadLimit() + { + return _config.getInt("broker.connector.protectio.readBufferLimitSize", DEFAULT_BUFFER_READ_LIMIT_SIZE); + } + + public int getBufferWriteLimit() + { + return _config.getInt("broker.connector.protectio.writeBufferLimitSize", DEFAULT_BUFFER_WRITE_LIMIT_SIZE); + } + + public boolean getSynchedClocks() + { + return _config.getBoolean("advanced.synced-clocks", false); + } + + public boolean getMsgAuth() + { + return _config.getBoolean("security.msg-auth", false); + } + + public String getJMXPrincipalDatabase() + { + return _config.getString("security.jmx.principal-database"); + } + + public String getManagementKeyStorePath() + { + return _config.getString("management.ssl.keyStorePath", null); + } + + public boolean getManagementSSLEnabled() + { + return _config.getBoolean("management.ssl.enabled", true); + } + + public String getManagementKeyStorePassword() + { + return _config.getString("management.ssl.keyStorePassword"); + } + + public SecurityConfiguration getSecurityConfiguration() + { + return _securityConfiguration; + } + + public boolean getQueueAutoRegister() + { + return _config.getBoolean("queue.auto_register", true); + } + + public boolean getManagementEnabled() + { + return _config.getBoolean("management.enabled", true); + } + + public void setManagementEnabled(boolean enabled) + { + _config.setProperty("management.enabled", enabled); + } + + public int getHeartBeatDelay() + { + return _config.getInt("heartbeat.delay", 5); + } + + public double getHeartBeatTimeout() + { + return _config.getDouble("heartbeat.timeoutFactor", 2.0); + } + + public int getDeliveryPoolSize() + { + return _config.getInt("delivery.poolsize", 0); + } + + public long getMaximumMessageAge() + { + return _config.getLong("maximumMessageAge", 0); + } + + public long getMaximumMessageCount() + { + return _config.getLong("maximumMessageCount", 0); + } + + public long getMaximumQueueDepth() + { + return _config.getLong("maximumQueueDepth", 0); + } + + public long getMaximumMessageSize() + { + return _config.getLong("maximumMessageSize", 0); + } + + public long getMinimumAlertRepeatGap() + { + return _config.getLong("minimumAlertRepeatGap", 0); + } + + public int getProcessors() + { + return _config.getInt("connector.processors", 4); + } + + public int getPort() + { + return _config.getInt("connector.port", DEFAULT_PORT); + } + + public String getBind() + { + return _config.getString("connector.bind", "wildcard"); + } + + public int getReceiveBufferSize() + { + return _config.getInt("connector.socketReceiveBuffer", 32767); + } + + public int getWriteBufferSize() + { + return _config.getInt("connector.socketWriteBuffer", 32767); + } + + public boolean getTcpNoDelay() + { + return _config.getBoolean("connector.tcpNoDelay", true); + } + + public boolean getEnableExecutorPool() + { + return _config.getBoolean("advanced.filterchain[@enableExecutorPool]", false); + } + + public boolean getEnablePooledAllocator() + { + return _config.getBoolean("advanced.enablePooledAllocator", false); + } + + public boolean getEnableDirectBuffers() + { + return _config.getBoolean("advanced.enableDirectBuffers", false); + } + + public boolean getEnableSSL() + { + return _config.getBoolean("connector.ssl.enabled", false); + } + + public boolean getSSLOnly() + { + return _config.getBoolean("connector.ssl.sslOnly", true); + } + + public int getSSLPort() + { + return _config.getInt("connector.ssl.port", DEFAUL_SSL_PORT); + } + + public String getKeystorePath() + { + return _config.getString("connector.ssl.keystorePath", "none"); + } + + public String getKeystorePassword() + { + return _config.getString("connector.ssl.keystorePassword", "none"); + } + + public String getCertType() + { + return _config.getString("connector.ssl.certType", "SunX509"); + } + + public boolean getQpidNIO() + { + return _config.getBoolean("connector.qpidnio", false); + } + + public boolean getUseBiasedWrites() + { + return _config.getBoolean("advanced.useWriteBiasedPool", false); + } + + public String getDefaultVirtualHost() + { + return _config.getString("virtualhosts.default"); + } + + public void setHousekeepingExpiredMessageCheckPeriod(long value) + { + _config.setProperty("housekeeping.expiredMessageCheckPeriod", value); + } + + public long getHousekeepingCheckPeriod() + { + return _config.getLong("housekeeping.checkPeriod", + _config.getLong("housekeeping.expiredMessageCheckPeriod", + DEFAULT_HOUSEKEEPING_PERIOD)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java new file mode 100644 index 0000000000..0273a13262 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java @@ -0,0 +1,169 @@ +/* + * + * 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.configuration; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MemoryMessageStore; + +public class VirtualHostConfiguration +{ + private Configuration _config; + private String _name; + private Map<String, QueueConfiguration> _queues = new HashMap<String, QueueConfiguration>(); + private Map<String, ExchangeConfiguration> _exchanges = new HashMap<String, ExchangeConfiguration>(); + + public VirtualHostConfiguration(String name, Configuration config) throws ConfigurationException + { + _config = config; + _name = name; + Iterator i = _config.getList("queues.queue.name").iterator(); + + while (i.hasNext()) + { + String queueName = (String) i.next(); + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(_config.subset("queues.queue." + queueName)); + mungedConf.addConfiguration(_config.subset("queues")); + _queues.put(queueName, new QueueConfiguration(queueName, mungedConf, this)); + } + + i = _config.getList("exchanges.exchange.name").iterator(); + int count = 0; + while (i.hasNext()) + { + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(config.subset("exchanges.exchange(" + count++ + ")")); + mungedConf.addConfiguration(_config.subset("exchanges")); + String exchName = (String) i.next(); + _exchanges.put(exchName, new ExchangeConfiguration(exchName, mungedConf)); + } + + } + + public String getName() + { + return _name; + } + + public long getHousekeepingExpiredMessageCheckPeriod() + { + return _config.getLong("housekeeping.expiredMessageCheckPeriod", ApplicationRegistry.getInstance().getConfiguration().getHousekeepingCheckPeriod()); + } + + public String getAuthenticationDatabase() + { + return _config.getString("security.authentication.name"); + } + + public List getCustomExchanges() + { + return _config.getList("custom-exchanges.class-name"); + } + + public SecurityConfiguration getSecurityConfiguration() + { + return new SecurityConfiguration(_config.subset("security")); + } + + public Configuration getStoreConfiguration() + { + return _config.subset("store"); + } + + public String getMessageStoreClass() + { + return _config.getString("store.class", MemoryMessageStore.class.getName()); + } + + public List getExchanges() + { + return _config.getList("exchanges.exchange.name"); + } + + public String[] getQueueNames() + { + return _queues.keySet().toArray(new String[_queues.size()]); + } + + public ExchangeConfiguration getExchangeConfiguration(String exchangeName) + { + return _exchanges.get(exchangeName); + } + + public QueueConfiguration getQueueConfiguration(String queueName) + { + // We might be asked for the config for a queue we don't know about, + // such as one that's been dynamically created. Those get the defaults by default. + if (_queues.containsKey(queueName)) + { + return _queues.get(queueName); + } + else + { + return new QueueConfiguration(queueName, new PropertiesConfiguration(), this); + } + } + + public long getMemoryUsageMaximum() + { + return _config.getLong("queues.maximumMemoryUsage", 0); + } + + public long getMemoryUsageMinimum() + { + return _config.getLong("queues.minimumMemoryUsage", 0); + } + + public int getMaximumMessageAge() + { + return _config.getInt("queues.maximumMessageAge", 0); + } + + public Long getMaximumQueueDepth() + { + return _config.getLong("queues.maximumQueueDepth", 0); + } + + public Long getMaximumMessageSize() + { + return _config.getLong("queues.maximumMessageSize", 0); + } + + public Long getMaximumMessageCount() + { + return _config.getLong("queues.maximumMessageCount", 0); + } + + public Long getMinimumAlertRepeatGap() + { + return _config.getLong("queues.minimumAlertRepeatGap", 0); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.java new file mode 100644 index 0000000000..8e4bf01c6a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagement.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.configuration.management; + +import javax.management.MBeanOperationInfo; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.management.MBeanOperation; + +public interface ConfigurationManagement +{ + + String TYPE = "ConfigurationManagement"; + int VERSION = 1; + + /** + * Reload the + * @throws ConfigurationException + */ + @MBeanOperation(name="reloadSecurityConfiguration", + description = "Force a reload of the security configuration sections", + impact = MBeanOperationInfo.ACTION) + void reloadSecurityConfiguration() throws ConfigurationException; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java new file mode 100644 index 0000000000..ead6053d70 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.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.configuration.management; + +import javax.management.NotCompliantMBeanException; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class ConfigurationManagementMBean extends AMQManagedObject implements ConfigurationManagement +{ + + public ConfigurationManagementMBean() throws NotCompliantMBeanException + { + super(ConfigurationManagement.class, ConfigurationManagement.TYPE, ConfigurationManagement.VERSION); + } + + @Override + public String getObjectInstanceName() + { + return ConfigurationManagement.TYPE; + } + + @Override + public void reloadSecurityConfiguration() throws ConfigurationException + { + ApplicationRegistry.getInstance().getConfiguration().reparseConfigFile(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java new file mode 100644 index 0000000000..d287595e2d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java @@ -0,0 +1,73 @@ +/* + * + * 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.connection; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.protocol.AMQConstant; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; + +public class ConnectionRegistry implements IConnectionRegistry +{ + private List<AMQProtocolSession> _registry = new CopyOnWriteArrayList<AMQProtocolSession>(); + + private VirtualHost _virtualHost; + + public ConnectionRegistry(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public void initialise() + { + + } + + /** Close all of the currently open connections. */ + public void close() throws AMQException + { + while (!_registry.isEmpty()) + { + AMQProtocolSession connection = _registry.get(0); + + connection.closeConnection(0, new AMQConnectionException(AMQConstant.INTERNAL_ERROR, "Broker is shutting down", + 0, 0, + connection.getProtocolOutputConverter().getProtocolMajorVersion(), + connection.getProtocolOutputConverter().getProtocolMinorVersion(), + (Throwable) null), true); + } + } + + public void registerConnection(AMQProtocolSession connnection) + { + _registry.add(connnection); + } + + public void deregisterConnection(AMQProtocolSession connnection) + { + _registry.remove(connnection); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java new file mode 100644 index 0000000000..d64fde1c20 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java @@ -0,0 +1,38 @@ +/* + * + * 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.connection; + +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +public interface IConnectionRegistry +{ + + public void initialise(); + + public void close() throws AMQException; + + public void registerConnection(AMQProtocolSession connnection); + + public void deregisterConnection(AMQProtocolSession connnection); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java new file mode 100644 index 0000000000..30af09ce4b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java @@ -0,0 +1,211 @@ +/* + * + * 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.exchange; + +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.TabularType; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.ArrayType; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public abstract class AbstractExchange implements Exchange, Managable +{ + private AMQShortString _name; + + + + protected boolean _durable; + protected String _exchangeType; + protected int _ticket; + + private VirtualHost _virtualHost; + + protected ExchangeMBean _exchangeMbean; + + /** + * Whether the exchange is automatically deleted once all queues have detached from it + */ + protected boolean _autoDelete; + + /** + * Abstract MBean class. This has some of the methods implemented from + * management intrerface for exchanges. Any implementaion of an + * Exchange MBean should extend this class. + */ + protected abstract class ExchangeMBean extends AMQManagedObject implements ManagedExchange + { + // open mbean data types for representing exchange bindings + protected String[] _bindingItemNames; + protected String[] _bindingItemIndexNames; + protected OpenType[] _bindingItemTypes; + protected CompositeType _bindingDataType; + protected TabularType _bindinglistDataType; + protected TabularDataSupport _bindingList; + + public ExchangeMBean() throws NotCompliantMBeanException + { + super(ManagedExchange.class, ManagedExchange.TYPE, ManagedExchange.VERSION); + } + + protected void init() throws OpenDataException + { + _bindingItemNames = new String[]{"Binding Key", "Queue Names"}; + _bindingItemIndexNames = new String[]{_bindingItemNames[0]}; + + _bindingItemTypes = new OpenType[2]; + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + _bindingDataType = new CompositeType("Exchange Binding", "Binding key and Queue names", + _bindingItemNames, _bindingItemNames, _bindingItemTypes); + _bindinglistDataType = new TabularType("Exchange Bindings", "Exchange Bindings for " + getName(), + _bindingDataType, _bindingItemIndexNames); + } + + public ManagedObject getParentObject() + { + return _virtualHost.getManagedObject(); + } + + public String getObjectInstanceName() + { + return _name.toString(); + } + + public String getName() + { + return _name.toString(); + } + + public String getExchangeType() + { + return _exchangeType; + } + + public Integer getTicketNo() + { + return _ticket; + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + // Added exchangetype in the object name lets maangement apps to do any customization required + public ObjectName getObjectName() throws MalformedObjectNameException + { + String objNameString = super.getObjectName().toString(); + objNameString = objNameString + ",ExchangeType=" + _exchangeType; + return new ObjectName(objNameString); + } + + protected ManagedObjectRegistry getManagedObjectRegistry() + { + return ApplicationRegistry.getInstance().getManagedObjectRegistry(); + } + } // End of MBean class + + public AMQShortString getName() + { + return _name; + } + + /** + * Concrete exchanges must implement this method in order to create the managed representation. This is + * called during initialisation (template method pattern). + * @return the MBean + */ + protected abstract ExchangeMBean createMBean() throws AMQException; + + public void initialise(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) throws AMQException + { + _virtualHost = host; + _name = name; + _durable = durable; + _autoDelete = autoDelete; + _ticket = ticket; + _exchangeMbean = createMBean(); + _exchangeMbean.register(); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getTicket() + { + return _ticket; + } + + public void close() throws AMQException + { + if (_exchangeMbean != null) + { + _exchangeMbean.unregister(); + } + } + + public String toString() + { + return getClass().getSimpleName() + "[" + getName() +"]"; + } + + public ManagedObject getManagedObject() + { + return _exchangeMbean; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public QueueRegistry getQueueRegistry() + { + return getVirtualHost().getQueueRegistry(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java new file mode 100644 index 0000000000..c04b6c559c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -0,0 +1,114 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.exchange; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DefaultExchangeFactory implements ExchangeFactory +{ + private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class); + + private Map<AMQShortString, ExchangeType<? extends Exchange>> _exchangeClassMap = new HashMap<AMQShortString, ExchangeType<? extends Exchange>>(); + private final VirtualHost _host; + + public DefaultExchangeFactory(VirtualHost host) + { + _host = host; + registerExchangeType(DirectExchange.TYPE); + registerExchangeType(TopicExchange.TYPE); + registerExchangeType(HeadersExchange.TYPE); + registerExchangeType(FanoutExchange.TYPE); + } + + public void registerExchangeType(ExchangeType<? extends Exchange> type) + { + _exchangeClassMap.put(type.getName(), type); + } + + public Collection<ExchangeType<? extends Exchange>> getRegisteredTypes() + { + return _exchangeClassMap.values(); + } + + public Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException + { + ExchangeType<? extends Exchange> exchType = _exchangeClassMap.get(type); + if (exchType == null) + { + + throw new AMQUnknownExchangeType("Unknown exchange type: " + type,null); + } + Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete); + return e; + } + + @Override + public void initialise(VirtualHostConfiguration hostConfig) + { + + if (hostConfig == null) + { + return; + } + + for(Object className : hostConfig.getCustomExchanges()) + { + try + { + ExchangeType<?> exchangeType = ApplicationRegistry.getInstance().getPluginManager().getExchanges().get(String.valueOf(className)); + if (exchangeType == null) + { + _logger.error("No such custom exchange class found: \""+String.valueOf(className)+"\""); + return; + } + Class<? extends ExchangeType> exchangeTypeClass = exchangeType.getClass(); + ExchangeType type = exchangeTypeClass.newInstance(); + registerExchangeType(type); + } + catch (ClassCastException classCastEx) + { + _logger.error("No custom exchange class: \""+String.valueOf(className)+"\" cannot be registered as it does not extend class \""+ExchangeType.class+"\""); + } + catch (IllegalAccessException e) + { + _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e); + } + catch (InstantiationException e) + { + _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e); + } + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java new file mode 100644 index 0000000000..0ab8208d88 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java @@ -0,0 +1,139 @@ +/* + * + * 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.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.protocol.ExchangeInitialiser; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultExchangeRegistry implements ExchangeRegistry +{ + private static final Logger _log = Logger.getLogger(DefaultExchangeRegistry.class); + + /** + * Maps from exchange name to exchange instance + */ + private ConcurrentMap<AMQShortString, Exchange> _exchangeMap = new ConcurrentHashMap<AMQShortString, Exchange>(); + + private Exchange _defaultExchange; + private VirtualHost _host; + + public DefaultExchangeRegistry(VirtualHost host) + { + //create 'standard' exchanges: + _host = host; + + } + + public void initialise() throws AMQException + { + new ExchangeInitialiser().initialise(_host.getExchangeFactory(), this); + } + + public MessageStore getMessageStore() + { + return _host.getMessageStore(); + } + + public void registerExchange(Exchange exchange) throws AMQException + { + _exchangeMap.put(exchange.getName(), exchange); + if (exchange.isDurable()) + { + getMessageStore().createExchange(exchange); + } + } + + public void setDefaultExchange(Exchange exchange) + { + _defaultExchange = exchange; + } + + public Exchange getDefaultExchange() + { + return _defaultExchange; + } + + public Collection<AMQShortString> getExchangeNames() + { + return _exchangeMap.keySet(); + } + + public void unregisterExchange(AMQShortString name, boolean inUse) throws AMQException + { + // TODO: check inUse argument + Exchange e = _exchangeMap.remove(name); + if (e != null) + { + if (e.isDurable()) + { + getMessageStore().removeExchange(e); + } + e.close(); + } + else + { + throw new AMQException("Unknown exchange " + name); + } + } + + public Exchange getExchange(AMQShortString name) + { + if ((name == null) || name.length() == 0) + { + return getDefaultExchange(); + } + else + { + return _exchangeMap.get(name); + } + + } + + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param payload + * @throws AMQException if something goes wrong delivering data + */ + public void routeContent(IncomingMessage payload) throws AMQException + { + final AMQShortString exchange = payload.getExchange(); + final Exchange exch = getExchange(exchange); + // there is a small window of opportunity for the exchange to be deleted in between + // the BasicPublish being received (where the exchange is validated) and the final + // content body being received (which triggers this method) + // TODO: check where the exchange is validated + if (exch == null) + { + throw new AMQException("Exchange '" + exchange + "' does not exist"); + } + exch.route(payload); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java new file mode 100644 index 0000000000..e9af92bad8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java @@ -0,0 +1,251 @@ +/* + * + * 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.exchange; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DirectExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DirectExchange.class); + + /** + * Maps from queue name to queue instances + */ + private final Index _index = new Index(); + + public static final ExchangeType<DirectExchange> TYPE = new ExchangeType<DirectExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.DIRECT_EXCHANGE_CLASS; + } + + public Class<DirectExchange> getExchangeClass() + { + return DirectExchange.class; + } + + public DirectExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + DirectExchange exch = new DirectExchange(); + exch.initialise(host,name,durable,ticket,autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.DIRECT_EXCHANGE_NAME; + } + }; + + /** + * MBean class implementing the management interfaces. + */ + @MBeanDescription("Management Bean for Direct Exchange") + private final class DirectExchangeMBean extends ExchangeMBean + { + @MBeanConstructor("Creates an MBean for AMQ direct exchange") + public DirectExchangeMBean() throws JMException + { + super(); + _exchangeType = "direct"; + init(); + } + + public TabularData bindings() throws OpenDataException + { + Map<AMQShortString, List<AMQQueue>> bindings = _index.getBindingsMap(); + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (Map.Entry<AMQShortString, List<AMQQueue>> entry : bindings.entrySet()) + { + AMQShortString key = entry.getKey(); + List<String> queueList = new ArrayList<String>(); + + List<AMQQueue> queues = entry.getValue(); + for (AMQQueue q : queues) + { + queueList.add(q.getName().toString()); + } + + Object[] bindingItemValues = {key.toString(), queueList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createNewBinding(String queueName, String binding) throws JMException + { + AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + } + + try + { + queue.bind(DirectExchange.this, new AMQShortString(binding), null); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + }// End of MBean class + + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new DirectExchangeMBean(); + } + catch (JMException ex) + { + _logger.error("Exception occured in creating the direct exchange mbean", ex); + throw new AMQException("Exception occured in creating the direct exchange mbean", ex); + } + } + + public AMQShortString getType() + { + return ExchangeDefaults.DIRECT_EXCHANGE_CLASS; + } + + public void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + if (!_index.add(routingKey, queue)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Queue (" + queue + ") is already registered with routing key " + routingKey); + } + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Binding queue:" + queue + " with routing key '" + routingKey +"' to exchange:" + this); + } + } + } + + public void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + + if (!_index.remove(routingKey, queue)) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey + ". No queue was registered with that _routing key"); + } + } + + public void route(IncomingMessage payload) throws AMQException + { + + final AMQShortString routingKey = payload.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : payload.getRoutingKey(); + + final ArrayList<AMQQueue> queues = (routingKey == null) ? null : _index.get(routingKey); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + queues); + } + + payload.enqueue(queues); + + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return isBound(routingKey,queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + final List<AMQQueue> queues = _index.get(routingKey); + return queues != null && queues.contains(queue); + } + + public boolean isBound(AMQShortString routingKey) + { + final List<AMQQueue> queues = _index.get(routingKey); + return queues != null && !queues.isEmpty(); + } + + public boolean isBound(AMQQueue queue) + { + Map<AMQShortString, List<AMQQueue>> bindings = _index.getBindingsMap(); + for (List<AMQQueue> queues : bindings.values()) + { + if (queues.contains(queue)) + { + return true; + } + } + return false; + } + + public boolean hasBindings() + { + return !_index.getBindingsMap().isEmpty(); + } + + public Map<AMQShortString, List<AMQQueue>> getBindings() + { + return _index.getBindingsMap(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java new file mode 100644 index 0000000000..06209c5458 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java @@ -0,0 +1,98 @@ +/* + * + * 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.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.List; +import java.util.Map; + +public interface Exchange +{ + AMQShortString getName(); + + AMQShortString getType(); + + void initialise(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) throws AMQException; + + boolean isDurable(); + + /** + * @return true if the exchange will be deleted after all queues have been detached + */ + boolean isAutoDelete(); + + int getTicket(); + + void close() throws AMQException; + + void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + void route(IncomingMessage message) throws AMQException; + + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key and arguments + * @param routingKey + * @param arguments + * @param queue + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue); + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key + * @param routingKey + * @param queue + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey, AMQQueue queue); + + /** + * Determines whether a message is routing to any queue using a specific _routing key + * @param routingKey + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey); + + boolean isBound(AMQQueue queue); + + /** + * Returns true if this exchange has at least one binding associated with it. + * @return + * @throws AMQException + */ + boolean hasBindings(); + + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java new file mode 100644 index 0000000000..2f76d41228 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java @@ -0,0 +1,41 @@ +/* + * + * 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.exchange; + +import java.util.Collection; + +import org.apache.commons.configuration.Configuration; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; + + +public interface ExchangeFactory +{ + Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException; + + void initialise(VirtualHostConfiguration hostConfig); + + Collection<ExchangeType<? extends Exchange>> getRegisteredTypes(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java new file mode 100644 index 0000000000..c77f114428 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.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.exchange; + +import org.apache.qpid.AMQException; + +/** + * ExchangeInUseRegistry indicates that an exchange cannot be unregistered because it is currently being used. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failure to unregister exchange that is in use. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo This exception is not used. However, it is part of the ExchangeRegistry interface, and looks like code is + * going to need to be added to throw/deal with this. Alternatively ExchangeResitries may be able to handle the + * issue internally. + */ +public class ExchangeInUseException extends AMQException +{ + public ExchangeInUseException(String exchangeName) + { + super("Exchange " + exchangeName + " is currently in use"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java new file mode 100644 index 0000000000..fe3b19e74e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java @@ -0,0 +1,51 @@ +/* + * + * 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.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +import java.util.Collection; + + +public interface ExchangeRegistry extends MessageRouter +{ + void registerExchange(Exchange exchange) throws AMQException; + + /** + * Unregister an exchange + * @param name name of the exchange to delete + * @param inUse if true, do NOT delete the exchange if it is in use (has queues bound to it) + * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use + * @throws AMQException + */ + void unregisterExchange(AMQShortString name, boolean inUse) throws ExchangeInUseException, AMQException; + + Exchange getExchange(AMQShortString name); + + void setDefaultExchange(Exchange exchange); + + Exchange getDefaultExchange(); + + Collection<AMQShortString> getExchangeNames(); + + void initialise() throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java new file mode 100644 index 0000000000..0b55caa2f1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.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.exchange; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +public interface ExchangeType<T extends Exchange> +{ + public AMQShortString getName(); + public Class<T> getExchangeClass(); + public T newInstance(VirtualHost host, AMQShortString name, + boolean durable, int ticket, boolean autoDelete) throws AMQException; + public AMQShortString getDefaultExchangeName(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java new file mode 100644 index 0000000000..e9fd4d548b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java @@ -0,0 +1,224 @@ +/* + * + * 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.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +public class FanoutExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(FanoutExchange.class); + + /** + * Maps from queue name to queue instances + */ + private final CopyOnWriteArraySet<AMQQueue> _queues = new CopyOnWriteArraySet<AMQQueue>(); + + /** + * MBean class implementing the management interfaces. + */ + @MBeanDescription("Management Bean for Fanout Exchange") + private final class FanoutExchangeMBean extends ExchangeMBean + { + @MBeanConstructor("Creates an MBean for AMQ fanout exchange") + public FanoutExchangeMBean() throws JMException + { + super(); + _exchangeType = "fanout"; + init(); + } + + public TabularData bindings() throws OpenDataException + { + + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (AMQQueue queue : _queues) + { + String queueName = queue.getName().toString(); + + Object[] bindingItemValues = {queueName, new String[]{queueName}}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createNewBinding(String queueName, String binding) throws JMException + { + AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + } + + try + { + queue.bind(FanoutExchange.this, new AMQShortString(binding), null); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + } // End of MBean class + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new FanoutExchange.FanoutExchangeMBean(); + } + catch (JMException ex) + { + _logger.error("Exception occured in creating the direct exchange mbean", ex); + throw new AMQException("Exception occured in creating the direct exchange mbean", ex); + } + } + + public static final ExchangeType<FanoutExchange> TYPE = new ExchangeType<FanoutExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.FANOUT_EXCHANGE_CLASS; + } + + public Class<FanoutExchange> getExchangeClass() + { + return FanoutExchange.class; + } + + public FanoutExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + FanoutExchange exch = new FanoutExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.FANOUT_EXCHANGE_NAME; + } + }; + + public Map<AMQShortString, List<AMQQueue>> getBindings() + { + return null; + } + + public AMQShortString getType() + { + return ExchangeDefaults.FANOUT_EXCHANGE_CLASS; + } + + public void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + + if (_queues.contains(queue)) + { + _logger.debug("Queue " + queue + " is already registered"); + } + else + { + _queues.add(queue); + _logger.debug("Binding queue " + queue + " with routing key " + routingKey + " to exchange " + this); + } + } + + public void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + + if (!_queues.remove(queue)) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName() + ". "); + } + } + + public void route(IncomingMessage payload) throws AMQException + { + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + _queues); + } + + payload.enqueue(new ArrayList(_queues)); + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return isBound(routingKey, queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return _queues.contains(queue); + } + + public boolean isBound(AMQShortString routingKey) + { + + return (_queues != null) && !_queues.isEmpty(); + } + + public boolean isBound(AMQQueue queue) + { + + return _queues.contains(queue); + } + + public boolean hasBindings() + { + return !_queues.isEmpty(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java new file mode 100644 index 0000000000..2b7df4361a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java @@ -0,0 +1,219 @@ +/* + * + * 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.exchange; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.FieldTable; + +/** + * Defines binding and matching based on a set of headers. + */ +class HeadersBinding +{ + private static final Logger _logger = Logger.getLogger(HeadersBinding.class); + + private final FieldTable _mappings; + private final Set<String> required = new HashSet<String>(); + private final Map<String,Object> matches = new HashMap<String,Object>(); + private boolean matchAny; + + private final class MatchesOrProcessor implements FieldTable.FieldTableElementProcessor + { + private Boolean _result = Boolean.FALSE; + + public boolean processElement(String propertyName, AMQTypedValue value) + { + if((value != null) && (value.getValue() != null) && value.getValue().equals(matches.get(propertyName))) + { + _result = Boolean.TRUE; + return false; + } + return true; + } + + public Object getResult() + { + return _result; + } + } + + private final class RequiredOrProcessor implements FieldTable.FieldTableElementProcessor + { + Boolean _result = Boolean.FALSE; + + public boolean processElement(String propertyName, AMQTypedValue value) + { + if(required.contains(propertyName)) + { + _result = Boolean.TRUE; + return false; + } + return true; + } + + public Object getResult() + { + return _result; + } + } + + + + /** + * Creates a binding for a set of mappings. Those mappings whose value is + * null or the empty string are assumed only to be required headers, with + * no constraint on the value. Those with a non-null value are assumed to + * define a required match of value. + * @param mappings the defined mappings this binding should use + */ + + HeadersBinding(FieldTable mappings) + { + _mappings = mappings; + initMappings(); + } + + private void initMappings() + { + + _mappings.processOverElements(new FieldTable.FieldTableElementProcessor() + { + + public boolean processElement(String propertyName, AMQTypedValue value) + { + if (isSpecial(propertyName)) + { + processSpecial(propertyName, value.getValue()); + } + else if (value.getValue() == null || value.getValue().equals("")) + { + required.add(propertyName); + } + else + { + matches.put(propertyName,value.getValue()); + } + + return true; + } + + public Object getResult() + { + return null; + } + }); + } + + protected FieldTable getMappings() + { + return _mappings; + } + + /** + * Checks whether the supplied headers match the requirements of this binding + * @param headers the headers to check + * @return true if the headers define any required keys and match any required + * values + */ + public boolean matches(FieldTable headers) + { + if(headers == null) + { + return required.isEmpty() && matches.isEmpty(); + } + else + { + return matchAny ? or(headers) : and(headers); + } + } + + private boolean and(FieldTable headers) + { + if(headers.keys().containsAll(required)) + { + for(Map.Entry<String, Object> e : matches.entrySet()) + { + if(!e.getValue().equals(headers.getObject(e.getKey()))) + { + return false; + } + } + return true; + } + else + { + return false; + } + } + + + private boolean or(final FieldTable headers) + { + if(required.isEmpty() || !(Boolean) headers.processOverElements(new RequiredOrProcessor())) + { + return ((!matches.isEmpty()) && (Boolean) headers.processOverElements(new MatchesOrProcessor())) + || (required.isEmpty() && matches.isEmpty()); + } + else + { + return true; + } + } + + private void processSpecial(String key, Object value) + { + if("X-match".equalsIgnoreCase(key)) + { + matchAny = isAny(value); + } + else + { + _logger.warn("Ignoring special header: " + key); + } + } + + private boolean isAny(Object value) + { + if(value instanceof String) + { + if("any".equalsIgnoreCase((String) value)) return true; + if("all".equalsIgnoreCase((String) value)) return false; + } + _logger.warn("Ignoring unrecognised match type: " + value); + return false;//default to all + } + + static boolean isSpecial(Object key) + { + return key instanceof String && isSpecial((String) key); + } + + static boolean isSpecial(String key) + { + return key.startsWith("X-") || key.startsWith("x-"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java new file mode 100644 index 0000000000..1ee1f35de6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java @@ -0,0 +1,350 @@ +/* + * + * 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.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * An exchange that binds queues based on a set of required headers and header values + * and routes messages to these queues by matching the headers of the message against + * those with which the queues were bound. + * <p/> + * <pre> + * The Headers Exchange + * + * Routes messages according to the value/presence of fields in the message header table. + * (Basic and JMS content has a content header field called "headers" that is a table of + * message header fields). + * + * class = "headers" + * routing key is not used + * + * Has the following binding arguments: + * + * the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match. + * other fields prefixed with "X-" are ignored (and generate a console warning message). + * a field with no value or empty value indicates a match on presence only. + * a field with a value indicates match on field presence and specific value. + * + * Standard instances: + * + * amq.match - pub/sub on field content/value + * </pre> + */ +public class HeadersExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(HeadersExchange.class); + + + + public static final ExchangeType<HeadersExchange> TYPE = new ExchangeType<HeadersExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.HEADERS_EXCHANGE_CLASS; + } + + public Class<HeadersExchange> getExchangeClass() + { + return HeadersExchange.class; + } + + public HeadersExchange newInstance(VirtualHost host, AMQShortString name, boolean durable, int ticket, + boolean autoDelete) throws AMQException + { + HeadersExchange exch = new HeadersExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + + return ExchangeDefaults.HEADERS_EXCHANGE_NAME; + } + }; + + + private final List<Registration> _bindings = new CopyOnWriteArrayList<Registration>(); + + /** + * HeadersExchangeMBean class implements the management interface for the + * Header Exchanges. + */ + @MBeanDescription("Management Bean for Headers Exchange") + private final class HeadersExchangeMBean extends ExchangeMBean + { + @MBeanConstructor("Creates an MBean for AMQ Headers exchange") + public HeadersExchangeMBean() throws JMException + { + super(); + _exchangeType = "headers"; + init(); + } + + /** + * initialises the OpenType objects. + */ + protected void init() throws OpenDataException + { + _bindingItemNames = new String[]{"Binding No", "Queue Name", "Queue Bindings"}; + _bindingItemIndexNames = new String[]{_bindingItemNames[0]}; + + _bindingItemTypes = new OpenType[3]; + _bindingItemTypes[0] = SimpleType.INTEGER; + _bindingItemTypes[1] = SimpleType.STRING; + _bindingItemTypes[2] = new ArrayType(1, SimpleType.STRING); + _bindingDataType = new CompositeType("Exchange Binding", "Queue name and header bindings", + _bindingItemNames, _bindingItemNames, _bindingItemTypes); + _bindinglistDataType = new TabularType("Exchange Bindings", "List of exchange bindings for " + getName(), + _bindingDataType, _bindingItemIndexNames); + } + + public TabularData bindings() throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + int count = 1; + for (Iterator<Registration> itr = _bindings.iterator(); itr.hasNext();) + { + Registration registration = itr.next(); + String queueName = registration.queue.getName().toString(); + + HeadersBinding headers = registration.binding; + FieldTable headerMappings = headers.getMappings(); + final List<String> mappingList = new ArrayList<String>(); + + headerMappings.processOverElements(new FieldTable.FieldTableElementProcessor() + { + + public boolean processElement(String propertyName, AMQTypedValue value) + { + mappingList.add(propertyName + "=" + value.getValue()); + return true; + } + + public Object getResult() + { + return mappingList; + } + }); + + + Object[] bindingItemValues = {count++, queueName, mappingList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + /** + * Creates bindings. Binding pattern is as follows- + * <attributename>=<value>,<attributename>=<value>,... + * @param queueName + * @param binding + * @throws javax.management.JMException + */ + public void createNewBinding(String queueName, String binding) throws JMException + { + AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName)); + + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + } + + String[] bindings = binding.split(","); + FieldTable bindingMap = new FieldTable(); + for (int i = 0; i < bindings.length; i++) + { + String[] keyAndValue = bindings[i].split("="); + if (keyAndValue == null || keyAndValue.length < 2) + { + throw new JMException("Format for headers binding should be \"<attribute1>=<value1>,<attribute2>=<value2>\" "); + } + bindingMap.setString(keyAndValue[0], keyAndValue[1]); + } + + _bindings.add(new Registration(new HeadersBinding(bindingMap), queue)); + } + + } // End of MBean class + + public AMQShortString getType() + { + return ExchangeDefaults.HEADERS_EXCHANGE_CLASS; + } + + public void registerQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Binding " + queue.getName() + " with " + args); + _bindings.add(new Registration(new HeadersBinding(args), queue)); + } + + public void deregisterQueue(AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Unbinding " + queue.getName()); + if(!_bindings.remove(new Registration(new HeadersBinding(args), queue))) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue + " was not registered with exchange " + this.getName() + + " with headers args " + args); + } + } + + public void route(IncomingMessage payload) throws AMQException + { + FieldTable headers = getHeaders(payload.getContentHeaderBody()); + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": routing message with headers " + headers); + } + boolean routed = false; + ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>(); + for (Registration e : _bindings) + { + + if (e.binding.matches(headers)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": delivering message with headers " + + headers + " to " + e.queue.getName()); + } + queues.add(e.queue); + + routed = true; + } + } + payload.enqueue(queues); + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + //fixme isBound here should take the arguements in to consideration. + return isBound(routingKey, queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return isBound(queue); + } + + public boolean isBound(AMQShortString routingKey) + { + return hasBindings(); + } + + public boolean isBound(AMQQueue queue) + { + for (Registration r : _bindings) + { + if (r.queue.equals(queue)) + { + return true; + } + } + return false; + } + + public boolean hasBindings() + { + return !_bindings.isEmpty(); + } + + protected FieldTable getHeaders(ContentHeaderBody contentHeaderFrame) + { + //what if the content type is not 'basic'? 'file' and 'stream' content classes also define headers, + //but these are not yet implemented. + return ((BasicContentHeaderProperties) contentHeaderFrame.properties).getHeaders(); + } + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new HeadersExchangeMBean(); + } + catch (JMException ex) + { + _logger.error("Exception occured in creating the HeadersExchangeMBean", ex); + throw new AMQException("Exception occured in creating the HeadersExchangeMBean", ex); + } + } + + public Map<AMQShortString, List<AMQQueue>> getBindings() + { + return null; + } + + private static class Registration + { + private final HeadersBinding binding; + private final AMQQueue queue; + + Registration(HeadersBinding binding, AMQQueue queue) + { + this.binding = binding; + this.queue = queue; + } + + public int hashCode() + { + return queue.hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof Registration && ((Registration) o).queue.equals(queue); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java new file mode 100644 index 0000000000..ec83161029 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Index.java @@ -0,0 +1,99 @@ +/* + * + * 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.exchange; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * An index of queues against routing key. Allows multiple queues to be stored + * against the same key. Used in the DirectExchange. + */ +class Index +{ + private ConcurrentMap<AMQShortString, ArrayList<AMQQueue>> _index + = new ConcurrentHashMap<AMQShortString, ArrayList<AMQQueue>>(); + + synchronized boolean add(AMQShortString key, AMQQueue queue) + { + ArrayList<AMQQueue> queues = _index.get(key); + if(queues == null) + { + queues = new ArrayList<AMQQueue>(); + } + else + { + queues = new ArrayList<AMQQueue>(queues); + } + //next call is atomic, so there is no race to create the list + _index.put(key, queues); + + if(queues.contains(queue)) + { + return false; + } + else + { + return queues.add(queue); + } + } + + synchronized boolean remove(AMQShortString key, AMQQueue queue) + { + ArrayList<AMQQueue> queues = _index.get(key); + if (queues != null) + { + queues = new ArrayList<AMQQueue>(queues); + boolean removed = queues.remove(queue); + if(removed) + { + if (queues.size() == 0) + { + _index.remove(key); + } + else + { + _index.put(key, queues); + } + } + return removed; + } + return false; + } + + ArrayList<AMQQueue> get(AMQShortString key) + { + return _index.get(key); + } + + Map<AMQShortString, List<AMQQueue>> getBindingsMap() + { + return new HashMap<AMQShortString, List<AMQQueue>>(_index); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java new file mode 100644 index 0000000000..317ff385ab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ManagedExchange.java @@ -0,0 +1,99 @@ +/* + * + * 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.exchange; + +import java.io.IOException; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.TabularData; + +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; +import org.apache.qpid.server.queue.ManagedQueue; + +/** + * The management interface exposed to allow management of an Exchange. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedExchange +{ + static final String TYPE = "Exchange"; + static final int VERSION = 1; + + /** + * Returns the name of the managed exchange. + * @return the name of the exchange. + * @throws IOException + */ + @MBeanAttribute(name="Name", description=TYPE + " Name") + String getName() throws IOException; + + @MBeanAttribute(name="ExchangeType", description="Exchange Type") + String getExchangeType() throws IOException; + + @MBeanAttribute(name="TicketNo", description="Exchange Ticket No") + Integer getTicketNo() throws IOException; + + /** + * Tells if the exchange is durable or not. + * @return true if the exchange is durable. + * @throws IOException + */ + @MBeanAttribute(name="Durable", description="true if Exchange is durable") + boolean isDurable() throws IOException; + + /** + * Tells if the exchange is set for autodelete or not. + * @return true if the exchange is set as autodelete. + * @throws IOException + */ + @MBeanAttribute(name="AutoDelete", description="true if Exchange is AutoDelete") + boolean isAutoDelete() throws IOException; + + // Operations + + /** + * Returns all the bindings this exchange has with the queues. + * @return the bindings with the exchange. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="bindings", description="view the queue bindings for this exchange") + TabularData bindings() throws IOException, JMException; + + /** + * Creates new binding with the given queue and binding. + * @param queueName + * @param binding + * @throws JMException + */ + @MBeanOperation(name="createNewBinding", + description="create a new binding with this exchange", + impact= MBeanOperationInfo.ACTION) + void createNewBinding(@MBeanOperationParameter(name= ManagedQueue.TYPE, description="Queue name") String queueName, + @MBeanOperationParameter(name="Binding", description="New binding")String binding) + throws JMException; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java new file mode 100644 index 0000000000..db9beb6da7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java @@ -0,0 +1,41 @@ +/* + * + * 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.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.IncomingMessage; + +/** + * Separated out from the ExchangeRegistry interface to allow components + * that use only this part to have a dependency with a reduced footprint. + * + */ +public interface MessageRouter +{ + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param message the message to be routed + * + * @throws org.apache.qpid.AMQException if something goes wrong delivering data + */ + void routeContent(IncomingMessage message) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.java new file mode 100644 index 0000000000..d18ad7ab14 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/NoRouteException.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.exchange; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.IncomingMessage; + +/** + * NoRouteException is a {@link RequiredDeliveryException} that represents the failure case where a manadatory message + * cannot be delivered because there is no route for the message. The AMQP status code, 312, is always used to report + * this condition. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to deliver a message that must be delivered. + * </table> + */ +public class NoRouteException extends RequiredDeliveryException +{ + public NoRouteException(String msg, AMQMessage amqMessage) + { + super(msg, amqMessage); + } + + public AMQConstant getReplyCode() + { + return AMQConstant.NO_ROUTE; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java new file mode 100644 index 0000000000..bc303a219d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java @@ -0,0 +1,670 @@ +/* + * + * 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.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.AMQShortStringTokenizer; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.exchange.topic.TopicParser; +import org.apache.qpid.server.exchange.topic.TopicMatcherResult; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.filter.JMSSelectorFilter; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.lang.ref.WeakReference; + +public class TopicExchange extends AbstractExchange +{ + + public static final ExchangeType<TopicExchange> TYPE = new ExchangeType<TopicExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.TOPIC_EXCHANGE_CLASS; + } + + public Class<TopicExchange> getExchangeClass() + { + return TopicExchange.class; + } + + public TopicExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + TopicExchange exch = new TopicExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.TOPIC_EXCHANGE_NAME; + } + }; + + + private static final Logger _logger = Logger.getLogger(TopicExchange.class); + +/* + private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _bindingKey2queues = + new ConcurrentHashMap<AMQShortString, List<AMQQueue>>(); + private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _simpleBindingKey2queues = + new ConcurrentHashMap<AMQShortString, List<AMQQueue>>(); + private final ConcurrentHashMap<AMQShortString, List<AMQQueue>> _wildCardBindingKey2queues = + new ConcurrentHashMap<AMQShortString, List<AMQQueue>>(); +*/ + // private ConcurrentHashMap<AMQShortString, AMQQueue> _routingKey2queue = new ConcurrentHashMap<AMQShortString, AMQQueue>(); + private static final byte TOPIC_SEPARATOR = (byte)'.'; + private static final AMQShortString TOPIC_SEPARATOR_AS_SHORTSTRING = new AMQShortString("."); + private static final AMQShortString AMQP_STAR_TOKEN = new AMQShortString("*"); + private static final AMQShortString AMQP_HASH_TOKEN = new AMQShortString("#"); + + private static final byte HASH_BYTE = (byte)'#'; + private static final byte STAR_BYTE = (byte)'*'; + + private final TopicParser _parser = new TopicParser(); + + private final Map<AMQShortString, TopicExchangeResult> _topicExchangeResults = + new ConcurrentHashMap<AMQShortString, TopicExchangeResult>(); + + private final Map<Binding, FieldTable> _bindings = new HashMap<Binding, FieldTable>(); + + private final Map<String, WeakReference<JMSSelectorFilter<RuntimeException>>> _selectorCache = new WeakHashMap<String, WeakReference<JMSSelectorFilter<RuntimeException>>>(); + + public static class Binding + { + private final AMQShortString _bindingKey; + private final AMQQueue _queue; + private final FieldTable _args; + + public Binding(AMQShortString bindingKey, AMQQueue queue, FieldTable args) + { + _bindingKey = bindingKey; + _queue = queue; + _args = args; + } + + public AMQShortString getBindingKey() + { + return _bindingKey; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public int hashCode() + { + return (_bindingKey == null ? 1 : _bindingKey.hashCode())*31 +_queue.hashCode(); + } + + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o instanceof Binding) + { + Binding other = (Binding) o; + return (_queue == other._queue) + && ((_bindingKey == null) ? other._bindingKey == null : _bindingKey.equals(other._bindingKey)); + } + return false; + } + } + + + + private final class TopicExchangeResult implements TopicMatcherResult + { + private final Map<AMQQueue, Integer> _unfilteredQueues = new ConcurrentHashMap<AMQQueue, Integer>(); + private final ConcurrentHashMap<AMQQueue, Map<MessageFilter<RuntimeException>,Integer>> _filteredQueues = new ConcurrentHashMap<AMQQueue, Map<MessageFilter<RuntimeException>, Integer>>(); + + public void addUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == null) + { + _unfilteredQueues.put(queue, 1); + } + else + { + _unfilteredQueues.put(queue, instances + 1); + } + } + + public void removeUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == 1) + { + _unfilteredQueues.remove(queue); + } + else + { + _unfilteredQueues.put(queue,instances - 1); + } + + } + + + public void addFilteredQueue(AMQQueue queue, MessageFilter<RuntimeException> filter) + { + Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue); + if(filters == null) + { + filters = new ConcurrentHashMap<MessageFilter<RuntimeException>,Integer>(); + _filteredQueues.put(queue, filters); + } + Integer instances = filters.get(filter); + if(instances == null) + { + filters.put(filter,1); + } + else + { + filters.put(filter, instances + 1); + } + + } + + public void removeFilteredQueue(AMQQueue queue, MessageFilter<RuntimeException> filter) + { + Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue); + if(filters != null) + { + Integer instances = filters.get(filter); + if(instances != null) + { + if(instances == 1) + { + filters.remove(filter); + if(filters.isEmpty()) + { + _filteredQueues.remove(queue); + } + } + else + { + filters.put(filter, instances - 1); + } + } + + } + + } + + public void replaceQueueFilter(AMQQueue queue, + MessageFilter<RuntimeException> oldFilter, + MessageFilter<RuntimeException> newFilter) + { + Map<MessageFilter<RuntimeException>,Integer> filters = _filteredQueues.get(queue); + Map<MessageFilter<RuntimeException>,Integer> newFilters = new ConcurrentHashMap<MessageFilter<RuntimeException>,Integer>(filters); + Integer oldFilterInstances = filters.get(oldFilter); + if(oldFilterInstances == 1) + { + newFilters.remove(oldFilter); + } + else + { + newFilters.put(oldFilter, oldFilterInstances-1); + } + Integer newFilterInstances = filters.get(newFilter); + if(newFilterInstances == null) + { + newFilters.put(newFilter, 1); + } + else + { + newFilters.put(newFilter, newFilterInstances+1); + } + _filteredQueues.put(queue,newFilters); + } + + public Collection<AMQQueue> processMessage(IncomingMessage msg, Collection<AMQQueue> queues) + { + if(queues == null) + { + if(_filteredQueues.isEmpty()) + { + return new ArrayList<AMQQueue>(_unfilteredQueues.keySet()); + } + else + { + queues = new HashSet<AMQQueue>(); + } + } + else if(!(queues instanceof Set)) + { + queues = new HashSet<AMQQueue>(queues); + } + + queues.addAll(_unfilteredQueues.keySet()); + if(!_filteredQueues.isEmpty()) + { + for(Map.Entry<AMQQueue, Map<MessageFilter<RuntimeException>, Integer>> entry : _filteredQueues.entrySet()) + { + if(!queues.contains(entry.getKey())) + { + for(MessageFilter<RuntimeException> filter : entry.getValue().keySet()) + { + if(filter.matches(msg)) + { + queues.add(entry.getKey()); + } + } + } + } + } + return queues; + } + + } + + + /** TopicExchangeMBean class implements the management interface for the Topic exchanges. */ + @MBeanDescription("Management Bean for Topic Exchange") + private final class TopicExchangeMBean extends ExchangeMBean + { + @MBeanConstructor("Creates an MBean for AMQ topic exchange") + public TopicExchangeMBean() throws JMException + { + super(); + _exchangeType = "topic"; + init(); + } + + /** returns exchange bindings in tabular form */ + public TabularData bindings() throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + Map<String, List<String>> bindingData = new HashMap<String, List<String>>(); + for (Binding binding : _bindings.keySet()) + { + String key = binding.getBindingKey().toString(); + List<String> queueNames = bindingData.get(key); + if(queueNames == null) + { + queueNames = new ArrayList<String>(); + bindingData.put(key, queueNames); + } + queueNames.add(binding.getQueue().getName().toString()); + + } + for(Map.Entry<String, List<String>> entry : bindingData.entrySet()) + { + Object[] bindingItemValues = {entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]) }; + CompositeData bindingCompositeData = new CompositeDataSupport(_bindingDataType, _bindingItemNames, bindingItemValues); + _bindingList.put(bindingCompositeData); + } + + return _bindingList; + } + + public void createNewBinding(String queueName, String binding) throws JMException + { + AMQQueue queue = getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + } + + try + { + queue.bind(TopicExchange.this, new AMQShortString(binding), null); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + } // End of MBean class + + public AMQShortString getType() + { + return ExchangeDefaults.TOPIC_EXCHANGE_CLASS; + } + + public synchronized void registerQueue(AMQShortString rKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert rKey != null; + + _logger.debug("Registering queue " + queue.getName() + " with routing key " + rKey); + + + AMQShortString routingKey; + + if(rKey.contains(HASH_BYTE) || rKey.contains(STAR_BYTE)) + { + routingKey = normalize(rKey); + } + else + { + routingKey = rKey; + } + + Binding binding = new Binding(rKey, queue, args); + + if(_bindings.containsKey(binding)) + { + FieldTable oldArgs = _bindings.get(binding); + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + + if(argumentsContainSelector(args)) + { + if(argumentsContainSelector(oldArgs)) + { + result.replaceQueueFilter(queue,createSelectorFilter(oldArgs), createSelectorFilter(args)); + } + else + { + result.addFilteredQueue(queue,createSelectorFilter(args)); + result.removeUnfilteredQueue(queue); + } + } + else + { + if(argumentsContainSelector(oldArgs)) + { + result.addUnfilteredQueue(queue); + result.removeFilteredQueue(queue, createSelectorFilter(oldArgs)); + } + else + { + // TODO - fix control flow + return; + } + } + + } + else + { + + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + if(result == null) + { + result = new TopicExchangeResult(); + if(argumentsContainSelector(args)) + { + result.addFilteredQueue(queue, createSelectorFilter(args)); + } + else + { + result.addUnfilteredQueue(queue); + } + _parser.addBinding(routingKey, result); + _topicExchangeResults.put(routingKey,result); + } + else + { + if(argumentsContainSelector(args)) + { + result.addFilteredQueue(queue, createSelectorFilter(args)); + } + else + { + result.addUnfilteredQueue(queue); + } + } + _bindings.put(binding, args); + } + + + } + + private JMSSelectorFilter<RuntimeException> createSelectorFilter(final FieldTable args) + throws AMQException + { + + final String selectorString = args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()); + WeakReference<JMSSelectorFilter<RuntimeException>> selectorRef = _selectorCache.get(selectorString); + JMSSelectorFilter selector = null; + + if(selectorRef == null || (selector = selectorRef.get())==null) + { + selector = new JMSSelectorFilter<RuntimeException>(selectorString); + _selectorCache.put(selectorString, new WeakReference<JMSSelectorFilter<RuntimeException>>(selector)); + } + return selector; + } + + private static boolean argumentsContainSelector(final FieldTable args) + { + return args != null && args.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()) && args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()).trim().length() != 0; + } + + private AMQShortString normalize(AMQShortString routingKey) + { + if(routingKey == null) + { + routingKey = AMQShortString.EMPTY_STRING; + } + + AMQShortStringTokenizer routingTokens = routingKey.tokenize(TOPIC_SEPARATOR); + + List<AMQShortString> subscriptionList = new ArrayList<AMQShortString>(); + + while (routingTokens.hasMoreTokens()) + { + subscriptionList.add(routingTokens.nextToken()); + } + + int size = subscriptionList.size(); + + for (int index = 0; index < size; index++) + { + // if there are more levels + if ((index + 1) < size) + { + if (subscriptionList.get(index).equals(AMQP_HASH_TOKEN)) + { + if (subscriptionList.get(index + 1).equals(AMQP_HASH_TOKEN)) + { + // we don't need #.# delete this one + subscriptionList.remove(index); + size--; + // redo this normalisation + index--; + } + + if (subscriptionList.get(index + 1).equals(AMQP_STAR_TOKEN)) + { + // we don't want #.* swap to *.# + // remove it and put it in at index + 1 + subscriptionList.add(index + 1, subscriptionList.remove(index)); + } + } + } // if we have more levels + } + + + + AMQShortString normalizedString = AMQShortString.join(subscriptionList, TOPIC_SEPARATOR_AS_SHORTSTRING); + + return normalizedString; + } + + public void route(IncomingMessage payload) throws AMQException + { + + final AMQShortString routingKey = payload.getRoutingKey(); + + // The copy here is unfortunate, but not too bad relevant to the amount of + // things created and copied in getMatchedQueues + ArrayList<AMQQueue> queues = new ArrayList<AMQQueue>(); + queues.addAll(getMatchedQueues(payload, routingKey)); + + if(queues == null || queues.isEmpty()) + { + _logger.info("Message routing key: " + payload.getRoutingKey() + " No routes."); + } + + payload.enqueue(queues); + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + Binding binding = new Binding(routingKey, queue, arguments); + if (arguments == null) + { + return _bindings.containsKey(binding); + } + else + { + FieldTable o = _bindings.get(binding); + if (o != null) + { + return o.equals(arguments); + } + else + { + return false; + } + + } + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return isBound(routingKey, null, queue); + } + + public boolean isBound(AMQShortString routingKey) + { + for(Binding b : _bindings.keySet()) + { + if(b.getBindingKey().equals(routingKey)) + { + return true; + } + } + + return false; + } + + public boolean isBound(AMQQueue queue) + { + for(Binding b : _bindings.keySet()) + { + if(b.getQueue().equals(queue)) + { + return true; + } + } + + return false; + } + + public boolean hasBindings() + { + return !_bindings.isEmpty(); + } + + public synchronized void deregisterQueue(AMQShortString rKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert rKey != null; + + Binding binding = new Binding(rKey, queue, args); + + + if (!_bindings.containsKey(binding)) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Queue " + queue.getName() + " was not registered with exchange " + this.getName() + + " with routing key " + rKey + "."); + } + + FieldTable bindingArgs = _bindings.remove(binding); + AMQShortString bindingKey = normalize(rKey); + TopicExchangeResult result = _topicExchangeResults.get(bindingKey); + if(argumentsContainSelector(bindingArgs)) + { + result.removeFilteredQueue(queue, createSelectorFilter(bindingArgs)); + } + else + { + result.removeUnfilteredQueue(queue); + } + + } + + protected ExchangeMBean createMBean() throws AMQException + { + try + { + return new TopicExchangeMBean(); + } + catch (JMException ex) + { + _logger.error("Exception occured in creating the topic exchenge mbean", ex); + throw new AMQException("Exception occured in creating the topic exchenge mbean", ex); + } + } + + private Collection<AMQQueue> getMatchedQueues(IncomingMessage message, AMQShortString routingKey) + { + + Collection<TopicMatcherResult> results = _parser.parse(routingKey); + if(results.isEmpty()) + { + return Collections.EMPTY_SET; + } + else + { + Collection<AMQQueue> queues = results.size() == 1 ? null : new HashSet<AMQQueue>(); + for(TopicMatcherResult result : results) + { + + queues = ((TopicExchangeResult)result).processMessage(message, queues); + } + return queues; + } + + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java new file mode 100644 index 0000000000..8fdb91cbef --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java @@ -0,0 +1,40 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQShortString; + +/* +* +* 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. +* +*/ +public class HeaderKey +{ + public static final HeaderKey UNKNOWN = new HeaderKey(new AMQShortString("<< UNKNOWN >>")); + private AMQShortString _key; + + public HeaderKey(final AMQShortString key) + { + _key = key; + } + + public String toString() + { + return _key.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java new file mode 100644 index 0000000000..7be99a88c9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java @@ -0,0 +1,50 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.Map; +import java.util.HashMap; + +/* +* +* 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. +* +*/ +public class HeaderKeyDictionary +{ + + private final Map<AMQShortString, HeaderKey> _dictionary = new HashMap<AMQShortString, HeaderKey>(); + + + public HeaderKey get(final AMQShortString key) + { + HeaderKey headerKey = _dictionary.get(key); + return headerKey == null ? HeaderKey.UNKNOWN : headerKey; + } + + public HeaderKey getOrCreate(final AMQShortString key) + { + HeaderKey headerKey = _dictionary.get(key); + if(headerKey == null) + { + headerKey = new HeaderKey(key); + _dictionary.put(key, headerKey); + } + return headerKey; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java new file mode 100644 index 0000000000..518064bb29 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java @@ -0,0 +1,25 @@ +package org.apache.qpid.server.exchange.headers; + +/* +* +* 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. +* +*/ +public class HeaderMatcherResult +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java new file mode 100644 index 0000000000..9da93d483a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java @@ -0,0 +1,339 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.topic.TopicMatcherDFAState; +import org.apache.qpid.server.exchange.topic.TopicWord; +import org.apache.qpid.server.exchange.topic.TopicMatcherResult; + +import java.util.*; + +/* +* +* 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. +* +*/ +public class HeadersMatcherDFAState +{ + + + private final Collection<HeaderMatcherResult> _results; + private final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> _nextStateMap; + private final HeaderKeyDictionary _dictionary; + + public HeadersMatcherDFAState(Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap, + Collection<HeaderMatcherResult> results, + HeaderKeyDictionary dictionary) + { + _nextStateMap = nextStateMap; + _results = results; + _dictionary = dictionary; + } + + + public Collection<HeaderMatcherResult> match(final FieldTable table) + { + return match(table.iterator()); + } + + + + public Collection<HeaderMatcherResult> match(Iterator<Map.Entry<AMQShortString,AMQTypedValue>> fieldTableIterator) + { + + if(_nextStateMap.isEmpty()) + { + return _results; + } + + while(fieldTableIterator.hasNext()) + { + + Map.Entry<AMQShortString, AMQTypedValue> fieldTableEntry = fieldTableIterator.next(); + HeaderKey key = _dictionary.get(fieldTableEntry.getKey()); + if(key != HeaderKey.UNKNOWN) + { + Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap = _nextStateMap.get(key); + + if(valueToStateMap != null) + { + HeadersMatcherDFAState nextState = valueToStateMap.get(fieldTableEntry.getValue()); + + if(nextState == null) + { + nextState = valueToStateMap.get(null); + } + if(nextState != null && nextState != this) + { + return nextState.match(fieldTableIterator); + } + } + + } + } + + return _results; + + } + + + HeadersMatcherDFAState mergeStateMachines(HeadersMatcherDFAState otherStateMachine) + { + + assert(otherStateMachine._dictionary == _dictionary); + + Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap= new HashMap<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState>(); + + Collection<HeaderMatcherResult> results; + + if(_results.isEmpty()) + { + results = otherStateMachine._results; + } + else if(otherStateMachine._results.isEmpty()) + { + results = _results; + } + else + { + results = new HashSet<HeaderMatcherResult>(_results); + results.addAll(otherStateMachine._results); + } + + + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + HeadersMatcherDFAState newState = new HeadersMatcherDFAState(newNextStateMap, results, _dictionary); + + + Set<HeadersMatcherDFAState> oldStates = new HashSet<HeadersMatcherDFAState>(); + oldStates.add(this); + oldStates.add(otherStateMachine); + + newStateMap.put(oldStates, newState); + + mergeStateMachines(oldStates, newNextStateMap, newStateMap); + + return newState; + + + } + + private void mergeStateMachines(final Set<HeadersMatcherDFAState> oldStates, + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap, + final Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap) + { + Map<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>> nfaMap = new HashMap<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>>(); + + Set<HeaderKey> distinctKeys = new HashSet<HeaderKey>(); + + for(HeadersMatcherDFAState state : oldStates) + { + Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> map = state._nextStateMap; + + for(Map.Entry<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> entry : map.entrySet()) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(entry.getKey()); + + if(valueToStatesMap == null) + { + valueToStatesMap = new HashMap<AMQTypedValue, Set<HeadersMatcherDFAState>>(); + nfaMap.put(entry.getKey(), valueToStatesMap); + } + + for(Map.Entry<AMQTypedValue, HeadersMatcherDFAState> valueToStateEntry : entry.getValue().entrySet()) + { + Set<HeadersMatcherDFAState> states = valueToStatesMap.get(valueToStateEntry.getKey()); + if(states == null) + { + states = new HashSet<HeadersMatcherDFAState>(); + valueToStatesMap.put(valueToStateEntry.getKey(),states); + } + states.add(valueToStateEntry.getValue()); + } + + distinctKeys.add(entry.getKey()); + } + } + + Map<HeaderKey, Set<HeadersMatcherDFAState>> anyValueStates = new HashMap<HeaderKey, Set<HeadersMatcherDFAState>>(); + + for(HeaderKey distinctKey : distinctKeys) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStateMap = nfaMap.get(distinctKey); + if(valueToStateMap != null) + { + Set<HeadersMatcherDFAState> statesForKeyDefault = valueToStateMap.get(null); + if(statesForKeyDefault != null) + { + anyValueStates.put(distinctKey, statesForKeyDefault); + } + } + } + + // add the defaults for "null" to all other specified values of a given header key + + for( Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> entry : nfaMap.entrySet()) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = entry.getValue(); + for(Map.Entry<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStates : valueToStatesMap.entrySet()) + { + if(valueToStates.getKey() != null) + { + + + Set<HeadersMatcherDFAState> defaults = anyValueStates.get(entry.getKey()); + if(defaults != null) + { + valueToStates.getValue().addAll(defaults); + } + } + } + } + + // if a given header key is not mentioned in the map of a machine; then that machine would stay at the same state + // for that key. + for(HeaderKey distinctKey : distinctKeys) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(distinctKey); + for(HeadersMatcherDFAState oldState : oldStates) + { + if(!oldState._nextStateMap.containsKey(distinctKey)) + { + for(Set<HeadersMatcherDFAState> endStates : valueToStatesMap.values()) + { + endStates.add(oldState); + } + } + } + } + + + + + for(Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> transitionClass : nfaMap.entrySet()) + { + Map<AMQTypedValue, HeadersMatcherDFAState> valueToDFAState = newNextStateMap.get(transitionClass.getKey()); + if(valueToDFAState == null) + { + valueToDFAState = new HashMap<AMQTypedValue, HeadersMatcherDFAState>(); + newNextStateMap.put(transitionClass.getKey(), valueToDFAState); + } + + for(Map.Entry<AMQTypedValue,Set<HeadersMatcherDFAState>> transition : transitionClass.getValue().entrySet()) + { + Set<HeadersMatcherDFAState> destinations = transition.getValue(); + + + HeadersMatcherDFAState nextState = newStateMap.get(destinations); + + if(nextState == null) + { + + if(destinations.size() == 1) + { + nextState = destinations.iterator().next(); + newStateMap.put(destinations, nextState); + } + else + { + Collection<HeaderMatcherResult> results; + + Set<Collection<HeaderMatcherResult>> resultSets = new HashSet<Collection<HeaderMatcherResult>>(); + for(HeadersMatcherDFAState destination : destinations) + { + resultSets.add(destination._results); + } + resultSets.remove(Collections.EMPTY_SET); + if(resultSets.size() == 0) + { + results = Collections.EMPTY_SET; + } + else if(resultSets.size() == 1) + { + results = resultSets.iterator().next(); + } + else + { + results = new HashSet<HeaderMatcherResult>(); + for(Collection<HeaderMatcherResult> oldResult : resultSets) + { + results.addAll(oldResult); + } + } + + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + nextState = new HeadersMatcherDFAState(nextStateMap, results, _dictionary); + newStateMap.put(destinations, nextState); + + mergeStateMachines( + destinations, + nextStateMap, + newStateMap); + + + } + + + } + valueToDFAState.put(transition.getKey(),nextState); + } + } + + + + final ArrayList<HeaderKey> removeKeyList = new ArrayList<HeaderKey>(); + + for(Map.Entry<HeaderKey,Map<AMQTypedValue,HeadersMatcherDFAState>> entry : _nextStateMap.entrySet()) + { + final ArrayList<AMQTypedValue> removeValueList = new ArrayList<AMQTypedValue>(); + + for(Map.Entry<AMQTypedValue,HeadersMatcherDFAState> valueToDFAState : entry.getValue().entrySet()) + { + if(valueToDFAState.getValue() == this) + { + HeadersMatcherDFAState defaultState = entry.getValue().get(null); + if(defaultState == null || defaultState == this) + { + removeValueList.add(valueToDFAState.getKey()); + } + } + } + + for(AMQTypedValue removeValue : removeValueList) + { + entry.getValue().remove(removeValue); + } + + if(entry.getValue().isEmpty()) + { + removeKeyList.add(entry.getKey()); + } + + } + + for(HeaderKey removeKey : removeKeyList) + { + _nextStateMap.remove(removeKey); + } + + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java new file mode 100644 index 0000000000..85e74122c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java @@ -0,0 +1,439 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.*; + +import java.util.*; + +/* +* +* 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. +* +*/ +public class HeadersParser +{ + + private final HeaderKeyDictionary _dictionary = new HeaderKeyDictionary(); + private static final AMQShortString MATCHING_TYPE_KEY = new AMQShortString("x-match"); + private static final String ANY_MATCHING = "any"; + private static final AMQShortString RESERVED_KEY_PREFIX = new AMQShortString("x-"); + + + HeadersMatcherDFAState createStateMachine(FieldTable bindingArguments, HeaderMatcherResult result) + { + String matchingType = bindingArguments.getString(MATCHING_TYPE_KEY); + boolean matchAny = matchingType.equalsIgnoreCase(ANY_MATCHING); + if(matchAny) + { + return createStateMachineForAnyMatch(bindingArguments, result); + } + else + { + return createStateMachineForAllMatch(bindingArguments, result); + } + + + } + + + private HeadersMatcherDFAState createStateMachineForAnyMatch(final FieldTable bindingArguments, + final HeaderMatcherResult result) + { + + // DFAs for "any" matches have only two states, "not-matched" and "matched"... they start in the former + // and upon meeting any of the criteria they move to the latter + + //noinspection unchecked + final HeadersMatcherDFAState successState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary); + + Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap = + new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + Set<AMQShortString> seenKeys = new HashSet<AMQShortString>(); + + Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator(); + + while(tableIterator.hasNext()) + { + final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next(); + final AMQShortString key = entry.getKey(); + final AMQTypedValue value = entry.getValue(); + + + if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX)) + { + final AMQType type = value.getType(); + + final HeaderKey headerKey = _dictionary.getOrCreate(key); + final Map<AMQTypedValue, HeadersMatcherDFAState> valueMap; + + if(type == AMQType.VOID || + ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0)) + { + valueMap = Collections.singletonMap(null,successState); + + } + else + { + valueMap = Collections.singletonMap(value,successState); + } + nextStateMap.put(headerKey,valueMap); + + } + + } + + if(seenKeys.size() == 0) + { + return successState; + } + else + { + return new HeadersMatcherDFAState(nextStateMap,Collections.EMPTY_SET,_dictionary); + } + + + } + + + private HeadersMatcherDFAState createStateMachineForAllMatch(final FieldTable bindingArguments, + final HeaderMatcherResult result) + { + // DFAs for "all" matches have a "success" state, a "fail" state, and states for every subset of + // matches which are possible, starting with the empty subset. For example if we have a binding + // { x-match="all" + // a=1 + // b=1 + // c=1 + // d=1 } + // Then we would have the following states + // (1) Seen none of a, b, c, or d + // (2) Seen a=1 ; none of b,c, or d + // (3) Seen b=1 ; none of a,c, or d + // (4) Seen c=1 ; none of a,b, or d + // (5) Seen d=1 ; none of a,b, or c + // (6) Seen a=1,b=1 ; none of c,d + // (7) Seen a=1,c=1 ; none of b,d + // (8) Seen a=1,d=1 ; none of b,c + // (9) Seen b=1,c=1 ; none of a,d + //(10) Seen b=1,d=1 ; none of c,d + //(11) Seen c=1,d=1 ; none of a,b + //(12) Seen a=1,b=1,c=1 ; not d + //(13) Seen a=1,b=1,d=1 ; not c + //(14) Seen a=1,c=1,d=1 ; not b + //(15) Seen b=1,c=1,d=1 ; not a + //(16) success + //(17) fail + // + // All states but (16) can transition to (17); additionally: + // (1) can transition to (2),(3),(4),(5) + // (2) can transition to (6),(7),(8) + // (3) can transition to (6),(9),(10) + // (4) can transition to (7),(9),(11) + // (5) can transition to (8),(10),(11) + // (6) can transition to (12),(13) + // (7) can transition to (12),(14) + // (8) can transition to (13),(14) + // (9) can transition to (12),(15) + //(10) can transition to (13),(15) + //(11) can transition to (14),(15) + //(12)-(15) can transition to (16) + + Set<AMQShortString> seenKeys = new HashSet<AMQShortString>(); + List<KeyValuePair> requiredTerms = new ArrayList<KeyValuePair>(bindingArguments.size()); + + Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator(); + + + + while(tableIterator.hasNext()) + { + final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next(); + final AMQShortString key = entry.getKey(); + final AMQTypedValue value = entry.getValue(); + + + if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX)) + { + final AMQType type = value.getType(); + + if(type == AMQType.VOID || + ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0)) + { + requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),null)); + } + else + { + requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),value)); + } + } + + } + + final HeadersMatcherDFAState successState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary); + + final HeadersMatcherDFAState failState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.EMPTY_SET,_dictionary); + + Map<Set<KeyValuePair>, HeadersMatcherDFAState> notSeenTermsToStateMap = + new HashMap<Set<KeyValuePair>, HeadersMatcherDFAState>(); + + notSeenTermsToStateMap.put(Collections.EMPTY_SET, successState); + + + final int numberOfTerms = requiredTerms.size(); + + for(int numMissingTerms = 1; numMissingTerms <= numberOfTerms; numMissingTerms++) + { + int[] pos = new int[numMissingTerms]; + for(int i = 0; i < numMissingTerms; i++) + { + pos[i] = i; + } + + final int maxTermValue = (numberOfTerms - (numMissingTerms - 1)); + + while(pos[0] < maxTermValue) + { + + Set<KeyValuePair> stateSet = new HashSet<KeyValuePair>(); + for(int posIndex = 0; posIndex < pos.length; posIndex++) + { + stateSet.add(requiredTerms.get(pos[posIndex])); + } + + final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap = + new HashMap<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>>(); + + + for(int posIndex = 0; posIndex < pos.length; posIndex++) + { + KeyValuePair nextTerm = requiredTerms.get(pos[posIndex]); + HashSet<KeyValuePair> nextStateSet = + new HashSet<KeyValuePair>(stateSet); + nextStateSet.remove(nextTerm); + + Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap = + new HashMap<AMQTypedValue, HeadersMatcherDFAState>(); + nextStateMap.put(nextTerm._key, valueToStateMap); + + valueToStateMap.put( nextTerm._value,notSeenTermsToStateMap.get(nextStateSet)); + if(nextTerm._value != null) + { + valueToStateMap.put(null, failState); + } + + + } + + + HeadersMatcherDFAState newState = new HeadersMatcherDFAState(nextStateMap, Collections.EMPTY_SET, _dictionary); + + notSeenTermsToStateMap.put(stateSet, newState); + + int i = numMissingTerms; + while(i-- != 0) + { + if(++pos[i] <= numberOfTerms -(numMissingTerms-i)) + { + int k = pos[i]; + for(int j = i+1; j < numMissingTerms; j++) + { + pos[j] = ++k; + } + break; + } + } + } + + + + + } + + + return notSeenTermsToStateMap.get(new HashSet<KeyValuePair>(requiredTerms)); + + + + } + + public static void main(String[] args) throws AMQFrameDecodingException + { + + FieldTable bindingTable = new FieldTable(); + + bindingTable.setString(new AMQShortString("x-match"),"all"); + bindingTable.setInteger("a",1); + bindingTable.setVoid(new AMQShortString("b")); + bindingTable.setString("c",""); + bindingTable.setInteger("d",4); + bindingTable.setInteger("e",1); + + + + FieldTable bindingTable2 = new FieldTable(); + bindingTable2.setString(new AMQShortString("x-match"),"all"); + bindingTable2.setInteger("a",1); + bindingTable2.setVoid(new AMQShortString("b")); + bindingTable2.setString("c",""); + bindingTable2.setInteger("d",4); + bindingTable2.setInteger("e",1); + bindingTable2.setInteger("f",1); + + + FieldTable table = new FieldTable(); + table.setInteger("a",1); + table.setInteger("b",2); + table.setString("c",""); + table.setInteger("d",4); + table.setInteger("e",1); + table.setInteger("f",1); + table.setInteger("h",1); + table.setInteger("i",1); + table.setInteger("j",1); + table.setInteger("k",1); + table.setInteger("l",1); + + org.apache.mina.common.ByteBuffer buffer = org.apache.mina.common.ByteBuffer.allocate( (int) table.getEncodedSize()); + EncodingUtils.writeFieldTableBytes(buffer, table); + buffer.flip(); + + FieldTable table2 = EncodingUtils.readFieldTable(buffer); + + + + FieldTable bindingTable3 = new FieldTable(); + bindingTable3.setString(new AMQShortString("x-match"),"any"); + bindingTable3.setInteger("a",1); + bindingTable3.setInteger("b",3); + + + FieldTable bindingTable4 = new FieldTable(); + bindingTable4.setString(new AMQShortString("x-match"),"any"); + bindingTable4.setVoid(new AMQShortString("a")); + + + FieldTable bindingTable5 = new FieldTable(); + bindingTable5.setString(new AMQShortString("x-match"),"all"); + bindingTable5.setString(new AMQShortString("h"),"hello"); + + for(int i = 0; i < 100; i++) + { + printMatches(new FieldTable[] {bindingTable5} , table2); + } + + + + } + + + + private static void printMatches(final FieldTable[] bindingKeys, final FieldTable routingKey) + { + HeadersMatcherDFAState sm = null; + Map<HeaderMatcherResult, String> resultMap = new HashMap<HeaderMatcherResult, String>(); + + HeadersParser parser = new HeadersParser(); + + for(int i = 0; i < bindingKeys.length; i++) + { + HeaderMatcherResult r = new HeaderMatcherResult(); + resultMap.put(r, bindingKeys[i].toString()); + + + if(i==0) + { + sm = parser.createStateMachine(bindingKeys[i], r); + } + else + { + sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeys[i], r)); + } + } + + Collection<HeaderMatcherResult> results = null; + long beforeTime = System.currentTimeMillis(); + for(int i = 0; i < 1000000; i++) + { + routingKey.size(); + + assert sm != null; + results = sm.match(routingKey); + + } + long elapsed = System.currentTimeMillis() - beforeTime; + System.out.println("1000000 Iterations took: " + elapsed); + Collection<String> resultStrings = new ArrayList<String>(); + + assert results != null; + for(HeaderMatcherResult result : results) + { + resultStrings.add(resultMap.get(result)); + } + + final ArrayList<String> nonMatches = new ArrayList<String>(); + for(FieldTable key : bindingKeys) + { + nonMatches.add(key.toString()); + } + nonMatches.removeAll(resultStrings); + System.out.println("\""+routingKey+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches); + + + } + + + public final static class KeyValuePair + { + public final HeaderKey _key; + public final AMQTypedValue _value; + private final int _hashCode; + + public KeyValuePair(final HeaderKey key, final AMQTypedValue value) + { + _key = key; + _value = value; + int hash = (1 + 31 * _key.hashCode()); + if(_value != null) + { + hash+=_value.hashCode(); + } + _hashCode = hash; + } + + public int hashCode() + { + return _hashCode; + } + + public boolean equals(Object o) + { + KeyValuePair other = (KeyValuePair)o; + return (_key == other._key) && (_value == null ? other._value == null : _value.equals(other._value)); + } + + + public String toString() + { + return "{" + _key + " -> " + _value + "}"; + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java new file mode 100644 index 0000000000..36076cf75b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java @@ -0,0 +1,295 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQShortStringTokenizer; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/* +* +* 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. +* +*/ +public class TopicMatcherDFAState +{ + private static final AtomicInteger stateId = new AtomicInteger(); + + private final int _id = stateId.incrementAndGet(); + + private final Collection<TopicMatcherResult> _results; + private final Map<TopicWord, TopicMatcherDFAState> _nextStateMap; + private static final byte TOPIC_DELIMITTER = (byte)'.'; + + + public TopicMatcherDFAState(Map<TopicWord, TopicMatcherDFAState> nextStateMap, + Collection<TopicMatcherResult> results ) + { + _nextStateMap = nextStateMap; + _results = results; + } + + + public TopicMatcherDFAState nextState(TopicWord word) + { + final TopicMatcherDFAState nextState = _nextStateMap.get(word); + return nextState == null ? _nextStateMap.get(TopicWord.ANY_WORD) : nextState; + } + + public Collection<TopicMatcherResult> terminate() + { + return _results; + } + + + public Collection<TopicMatcherResult> parse(TopicWordDictionary dictionary, AMQShortString routingKey) + { + return parse(dictionary, routingKey.tokenize(TOPIC_DELIMITTER)); + } + + private Collection<TopicMatcherResult> parse(final TopicWordDictionary dictionary, + final AMQShortStringTokenizer tokens) + { + if(!tokens.hasMoreTokens()) + { + return _results; + } + TopicWord word = dictionary.getWord(tokens.nextToken()); + TopicMatcherDFAState nextState = _nextStateMap.get(word); + if(nextState == null && word != TopicWord.ANY_WORD) + { + nextState = _nextStateMap.get(TopicWord.ANY_WORD); + } + if(nextState == null) + { + return Collections.EMPTY_SET; + } + // Shortcut if we are at a looping terminal state + if((nextState == this) && (_nextStateMap.size() == 1) && _nextStateMap.containsKey(TopicWord.ANY_WORD)) + { + return _results; + } + + return nextState.parse(dictionary, tokens); + + } + + + public TopicMatcherDFAState mergeStateMachines(TopicMatcherDFAState otherStateMachine) + { + Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap= new HashMap<Set<TopicMatcherDFAState>, TopicMatcherDFAState>(); + + Collection<TopicMatcherResult> results; + + if(_results.isEmpty()) + { + results = otherStateMachine._results; + } + else if(otherStateMachine._results.isEmpty()) + { + results = _results; + } + else + { + results = new HashSet<TopicMatcherResult>(_results); + results.addAll(otherStateMachine._results); + } + + + final Map<TopicWord, TopicMatcherDFAState> newNextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>(); + + TopicMatcherDFAState newState = new TopicMatcherDFAState(newNextStateMap, results); + + + Set<TopicMatcherDFAState> oldStates = new HashSet<TopicMatcherDFAState>(); + oldStates.add(this); + oldStates.add(otherStateMachine); + + newStateMap.put(oldStates, newState); + + mergeStateMachines(oldStates, newNextStateMap, newStateMap); + + return newState; + + } + + private static void mergeStateMachines( + final Set<TopicMatcherDFAState> oldStates, + final Map<TopicWord, TopicMatcherDFAState> newNextStateMap, + final Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap) + { + Map<TopicWord, Set<TopicMatcherDFAState>> nfaMap = new HashMap<TopicWord, Set<TopicMatcherDFAState>>(); + + for(TopicMatcherDFAState state : oldStates) + { + Map<TopicWord, TopicMatcherDFAState> map = state._nextStateMap; + for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : map.entrySet()) + { + Set<TopicMatcherDFAState> states = nfaMap.get(entry.getKey()); + if(states == null) + { + states = new HashSet<TopicMatcherDFAState>(); + nfaMap.put(entry.getKey(), states); + } + states.add(entry.getValue()); + } + } + + Set<TopicMatcherDFAState> anyWordStates = nfaMap.get(TopicWord.ANY_WORD); + + for(Map.Entry<TopicWord, Set<TopicMatcherDFAState>> transition : nfaMap.entrySet()) + { + Set<TopicMatcherDFAState> destinations = transition.getValue(); + + if(anyWordStates != null) + { + destinations.addAll(anyWordStates); + } + + TopicMatcherDFAState nextState = newStateMap.get(destinations); + if(nextState == null) + { + + if(destinations.size() == 1) + { + nextState = destinations.iterator().next(); + newStateMap.put(destinations, nextState); + } + else + { + Collection<TopicMatcherResult> results; + + Set<Collection<TopicMatcherResult>> resultSets = new HashSet<Collection<TopicMatcherResult>>(); + for(TopicMatcherDFAState destination : destinations) + { + resultSets.add(destination._results); + } + resultSets.remove(Collections.EMPTY_SET); + if(resultSets.size() == 0) + { + results = Collections.EMPTY_SET; + } + else if(resultSets.size() == 1) + { + results = resultSets.iterator().next(); + } + else + { + results = new HashSet<TopicMatcherResult>(); + for(Collection<TopicMatcherResult> oldResult : resultSets) + { + results.addAll(oldResult); + } + } + + final Map<TopicWord, TopicMatcherDFAState> nextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>(); + + nextState = new TopicMatcherDFAState(nextStateMap, results); + newStateMap.put(destinations, nextState); + + mergeStateMachines( + destinations, + nextStateMap, + newStateMap); + + + } + + + } + newNextStateMap.put(transition.getKey(),nextState); + } + + // Remove redundant transitions where defined tokenWord has same action as ANY_WORD + TopicMatcherDFAState anyWordState = newNextStateMap.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List<TopicWord> removeList = new ArrayList<TopicWord>(); + for(Map.Entry<TopicWord,TopicMatcherDFAState> entry : newNextStateMap.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + newNextStateMap.remove(removeKey); + } + } + + + + } + + + public String toString() + { + StringBuilder transitions = new StringBuilder(); + for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : _nextStateMap.entrySet()) + { + transitions.append("[ "); + transitions.append(entry.getKey()); + transitions.append("\t ->\t "); + transitions.append(entry.getValue()._id); + transitions.append(" ]\n"); + } + + + return "[ State " + _id + " ]\n" + transitions + "\n"; + + } + + public String reachableStates() + { + StringBuilder result = new StringBuilder("Start state: " + _id + "\n"); + + SortedSet<TopicMatcherDFAState> reachableStates = + new TreeSet<TopicMatcherDFAState>(new Comparator<TopicMatcherDFAState>() + { + public int compare(final TopicMatcherDFAState o1, final TopicMatcherDFAState o2) + { + return o1._id - o2._id; + } + }); + reachableStates.add(this); + + int count; + + do + { + count = reachableStates.size(); + Collection<TopicMatcherDFAState> originalStates = new ArrayList<TopicMatcherDFAState>(reachableStates); + for(TopicMatcherDFAState state : originalStates) + { + reachableStates.addAll(state._nextStateMap.values()); + } + } + while(reachableStates.size() != count); + + + + for(TopicMatcherDFAState state : reachableStates) + { + result.append(state.toString()); + } + + return result.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java new file mode 100644 index 0000000000..71d30adfac --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java @@ -0,0 +1,25 @@ +package org.apache.qpid.server.exchange.topic; + +/* +* +* 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. +* +*/ +public interface TopicMatcherResult +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java new file mode 100644 index 0000000000..3e9facf412 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java @@ -0,0 +1,613 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQShortStringTokenizer; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.io.IOException; + +/* +* +* 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. +* +*/ +public class TopicParser +{ + private static final byte TOPIC_DELIMITER = (byte)'.'; + + private final TopicWordDictionary _dictionary = new TopicWordDictionary(); + private final AtomicReference<TopicMatcherDFAState> _stateMachine = new AtomicReference<TopicMatcherDFAState>(); + + private static class Position + { + private final TopicWord _word; + private final boolean _selfTransition; + private final int _position; + private final boolean _endState; + private boolean _followedByAnyLoop; + + + public Position(final int position, final TopicWord word, final boolean selfTransition, final boolean endState) + { + _position = position; + _word = word; + _selfTransition = selfTransition; + _endState = endState; + } + + + } + + private static final Position ERROR_POSITION = new Position(Integer.MAX_VALUE,null, true, false); + + private static class SimpleState + { + Set<Position> _positions; + Map<TopicWord, SimpleState> _nextState; + } + + + public void addBinding(AMQShortString bindingKey, TopicMatcherResult result) + { + + TopicMatcherDFAState startingStateMachine; + TopicMatcherDFAState newStateMachine; + + do + { + startingStateMachine = _stateMachine.get(); + if(startingStateMachine == null) + { + newStateMachine = createStateMachine(bindingKey, result); + } + else + { + newStateMachine = startingStateMachine.mergeStateMachines(createStateMachine(bindingKey, result)); + } + + } + while(!_stateMachine.compareAndSet(startingStateMachine,newStateMachine)); + + } + + public Collection<TopicMatcherResult> parse(AMQShortString routingKey) + { + TopicMatcherDFAState stateMachine = _stateMachine.get(); + if(stateMachine == null) + { + return Collections.EMPTY_SET; + } + else + { + return stateMachine.parse(_dictionary,routingKey); + } + } + + + TopicMatcherDFAState createStateMachine(AMQShortString bindingKey, TopicMatcherResult result) + { + List<TopicWord> wordList = createTopicWordList(bindingKey); + int wildCards = 0; + for(TopicWord word : wordList) + { + if(word == TopicWord.WILDCARD_WORD) + { + wildCards++; + } + } + if(wildCards == 0) + { + TopicMatcherDFAState[] states = new TopicMatcherDFAState[wordList.size()+1]; + states[states.length-1] = new TopicMatcherDFAState(Collections.EMPTY_MAP, Collections.singleton(result)); + for(int i = states.length-2; i >= 0; i--) + { + states[i] = new TopicMatcherDFAState(Collections.singletonMap(wordList.get(i),states[i+1]),Collections.EMPTY_SET); + + } + return states[0]; + } + else if(wildCards == wordList.size()) + { + Map<TopicWord,TopicMatcherDFAState> stateMap = new HashMap<TopicWord,TopicMatcherDFAState>(); + TopicMatcherDFAState state = new TopicMatcherDFAState(stateMap, Collections.singleton(result)); + stateMap.put(TopicWord.ANY_WORD, state); + return state; + } + + + int positionCount = wordList.size() - wildCards; + + Position[] positions = new Position[positionCount+1]; + + int lastWord; + + if(wordList.get(wordList.size()-1)== TopicWord.WILDCARD_WORD) + { + lastWord = wordList.size()-1; + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, true, true); + } + else + { + lastWord = wordList.size(); + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, false, true); + } + + + int pos = 0; + int wordPos = 0; + + + while(wordPos < lastWord) + { + TopicWord word = wordList.get(wordPos++); + + if(word == TopicWord.WILDCARD_WORD) + { + int nextWordPos = wordPos++; + word = wordList.get(nextWordPos); + + positions[pos] = new Position(pos++,word,true,false); + } + else + { + positions[pos] = new Position(pos++,word,false,false); + } + + } + + + for(int p = 0; p<positionCount; p++) + { + boolean followedByWildcards = true; + + int n = p; + while(followedByWildcards && n<(positionCount+1)) + { + + if(positions[n]._selfTransition) + { + break; + } + else if(positions[n]._word!=TopicWord.ANY_WORD) + { + followedByWildcards = false; + } + n++; + } + + + positions[p]._followedByAnyLoop = followedByWildcards && (n!= positionCount+1); + } + + + // from each position you transition to a set of other positions. + // we approach this by examining steps of increasing length - so we + // look how far we can go from the start position in 1 word, 2 words, etc... + + Map<Set<Position>,SimpleState> stateMap = new HashMap<Set<Position>,SimpleState>(); + + + SimpleState state = new SimpleState(); + state._positions = Collections.singleton( positions[0] ); + stateMap.put(state._positions, state); + + calculateNextStates(state, stateMap, positions); + + SimpleState[] simpleStates = stateMap.values().toArray(new SimpleState[stateMap.size()]); + HashMap<TopicWord, TopicMatcherDFAState>[] dfaStateMaps = new HashMap[simpleStates.length]; + Map<SimpleState, TopicMatcherDFAState> simple2DFAMap = new HashMap<SimpleState, TopicMatcherDFAState>(); + + for(int i = 0; i < simpleStates.length; i++) + { + + Collection<TopicMatcherResult> results; + boolean endState = false; + + for(Position p : simpleStates[i]._positions) + { + if(p._endState) + { + endState = true; + break; + } + } + + if(endState) + { + results = Collections.singleton(result); + } + else + { + results = Collections.EMPTY_SET; + } + + dfaStateMaps[i] = new HashMap<TopicWord, TopicMatcherDFAState>(); + simple2DFAMap.put(simpleStates[i], new TopicMatcherDFAState(dfaStateMaps[i],results)); + + } + for(int i = 0; i < simpleStates.length; i++) + { + SimpleState simpleState = simpleStates[i]; + + Map<TopicWord, SimpleState> nextSimpleStateMap = simpleState._nextState; + for(Map.Entry<TopicWord, SimpleState> stateMapEntry : nextSimpleStateMap.entrySet()) + { + dfaStateMaps[i].put(stateMapEntry.getKey(), simple2DFAMap.get(stateMapEntry.getValue())); + } + + } + + return simple2DFAMap.get(state); + + } + + + + private void calculateNextStates(final SimpleState state, + final Map<Set<Position>, SimpleState> stateMap, + final Position[] positions) + { + Map<TopicWord, Set<Position>> transitions = new HashMap<TopicWord,Set<Position>>(); + + for(Position pos : state._positions) + { + if(pos._selfTransition) + { + Set<Position> dest = transitions.get(TopicWord.ANY_WORD); + if(dest == null) + { + dest = new HashSet<Position>(); + transitions.put(TopicWord.ANY_WORD,dest); + } + dest.add(pos); + } + + final int nextPos = pos._position + 1; + Position nextPosition = nextPos == positions.length ? ERROR_POSITION : positions[nextPos]; + + Set<Position> dest = transitions.get(pos._word); + if(dest == null) + { + dest = new HashSet<Position>(); + transitions.put(pos._word,dest); + } + dest.add(nextPosition); + + } + + Set<Position> anyWordTransitions = transitions.get(TopicWord.ANY_WORD); + if(anyWordTransitions != null) + { + for(Set<Position> dest : transitions.values()) + { + dest.addAll(anyWordTransitions); + } + } + + state._nextState = new HashMap<TopicWord, SimpleState>(); + + for(Map.Entry<TopicWord,Set<Position>> dest : transitions.entrySet()) + { + + if(dest.getValue().size()>1) + { + dest.getValue().remove(ERROR_POSITION); + } + Position loopingTerminal = null; + for(Position destPos : dest.getValue()) + { + if(destPos._selfTransition && destPos._endState) + { + loopingTerminal = destPos; + break; + } + } + + if(loopingTerminal!=null) + { + dest.setValue(Collections.singleton(loopingTerminal)); + } + else + { + Position anyLoop = null; + for(Position destPos : dest.getValue()) + { + if(destPos._followedByAnyLoop) + { + if(anyLoop == null || anyLoop._position<destPos._position) + { + anyLoop = destPos; + } + } + } + if(anyLoop != null) + { + Collection<Position> removals = new ArrayList<Position>(); + for(Position destPos : dest.getValue()) + { + if(destPos._position < anyLoop._position) + { + removals.add(destPos); + } + } + dest.getValue().removeAll(removals); + } + } + + SimpleState stateForEntry = stateMap.get(dest.getValue()); + if(stateForEntry == null) + { + stateForEntry = new SimpleState(); + stateForEntry._positions = dest.getValue(); + stateMap.put(dest.getValue(),stateForEntry); + calculateNextStates(stateForEntry, + stateMap, + positions); + } + state._nextState.put(dest.getKey(),stateForEntry); + + + + } + + // remove redundant transitions + SimpleState anyWordState = state._nextState.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List<TopicWord> removeList = new ArrayList<TopicWord>(); + for(Map.Entry<TopicWord,SimpleState> entry : state._nextState.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + state._nextState.remove(removeKey); + } + } + + + } + + private List<TopicWord> createTopicWordList(final AMQShortString bindingKey) + { + AMQShortStringTokenizer tokens = bindingKey.tokenize(TOPIC_DELIMITER); + TopicWord previousWord = null; + + List<TopicWord> wordList = new ArrayList<TopicWord>(); + + while(tokens.hasMoreTokens()) + { + TopicWord nextWord = _dictionary.getOrCreateWord(tokens.nextToken()); + if(previousWord == TopicWord.WILDCARD_WORD) + { + + if(nextWord == TopicWord.WILDCARD_WORD) + { + // consecutive wildcards can be merged + // i.e. subsequent wildcards can be discarded + continue; + } + else if(nextWord == TopicWord.ANY_WORD) + { + // wildcard and anyword can be reordered to always put anyword first + wordList.set(wordList.size()-1,TopicWord.ANY_WORD); + nextWord = TopicWord.WILDCARD_WORD; + } + } + wordList.add(nextWord); + previousWord = nextWord; + + } + return wordList; + } + + + public static void main(String[] args) + { + + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.*.q.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches(new String[]{ + "#.a.#", + "#.b.#", + "#.c.#", + "#.d.#", + "#.e.#", + "#.f.#", + "#.g.#", + "#.h.#", + "#.i.#", + "#.j.#", + "#.k.#", + "#.l.#", + "#.m.#", + "#.n.#", + "#.o.#", + "#.p.#", + "#.q.#" + + }, "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); +/* + printMatches(new String[]{ + "#.a.#", + "#.b.#", + "#.c.#", + "#.d.#", + "#.e.#", + "#.f.#", + "#.g.#", + "#.h.#", + "#.i.#", + "#.j.#", + "#.k.#", + "#.l.#", + "#.m.#", + "#.n.#", + "#.o.#", + "#.p.#", + "#.q.#", + "#.r.#", + "#.s.#", + "#.t.#", + "#.u.#", + "#.v.#", + "#.w.#", + "#.x.#", + "#.y.#", + "#.z.#" + + + },"a.b"); + + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches("a.#.b.#","a.b.b.b.b.b.b.b.c"); + +*/ + + printMatches("",""); + printMatches("a","a"); + printMatches("a",""); + printMatches("","a"); + printMatches("a.b","a.b"); + printMatches("a","a.b"); + printMatches("a.b","a"); + printMatches("*","a"); + printMatches("*.b","a.b"); + printMatches("*.*","a.b"); + printMatches("a.*","a.b"); + printMatches("a.*.#","a.b"); + printMatches("a.#.b","a.b"); + + printMatches("#.b","a"); + printMatches("#.b","a.b"); + printMatches("#.a.b","a.b"); + + + printMatches("#",""); + printMatches("#","a"); + printMatches("#","a.b"); + printMatches("#.#","a.b"); + printMatches("#.*","a.b"); + + printMatches("#.a.b","a.b"); + printMatches("a.b.#","a.b"); + printMatches("a.#","a.b"); + printMatches("#.*.#","a.b"); + printMatches("#.*.b.#","a.b"); + printMatches("#.a.*.#","a.b"); + printMatches("#.a.#.b.#","a.b"); + printMatches("#.*.#.*.#","a.b"); + printMatches("*.#.*.#","a.b"); + printMatches("#.*.#.*","a.b"); + + + printMatches(new String[]{"a.#.b.#","a.*.#.b.#"},"a.b.b.b.b.b.b.b.c"); + + + printMatches(new String[]{"a.b", "a.c"},"a.b"); + printMatches(new String[]{"a.#", "a.c", "#.b"},"a.b"); + printMatches(new String[]{"a.#", "a.c", "#.b", "#", "*.*"},"a.b"); + + printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.e"); + printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.f.g"); + + + + + } + + private static void printMatches(final String[] bindingKeys, final String routingKey) + { + TopicMatcherDFAState sm = null; + Map<TopicMatcherResult, String> resultMap = new HashMap<TopicMatcherResult, String>(); + + TopicParser parser = new TopicParser(); + + long start = System.currentTimeMillis(); + for(int i = 0; i < bindingKeys.length; i++) + { + System.out.println((System.currentTimeMillis() - start) + ":\t" + bindingKeys[i]); + TopicMatcherResult r = new TopicMatcherResult(){}; + resultMap.put(r, bindingKeys[i]); + AMQShortString bindingKeyShortString = new AMQShortString(bindingKeys[i]); + + System.err.println("====================================================="); + System.err.println("Adding binding key: " + bindingKeyShortString); + System.err.println("-----------------------------------------------------"); + + + if(i==0) + { + sm = parser.createStateMachine(bindingKeyShortString, r); + } + else + { + sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeyShortString, r)); + } + System.err.println(sm.reachableStates()); + System.err.println("====================================================="); + try + { + System.in.read(); + } + catch (IOException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + AMQShortString routingKeyShortString = new AMQShortString(routingKey); + + Collection<TopicMatcherResult> results = sm.parse(parser._dictionary, routingKeyShortString); + Collection<String> resultStrings = new ArrayList<String>(); + + for(TopicMatcherResult result : results) + { + resultStrings.add(resultMap.get(result)); + } + + final ArrayList<String> nonMatches = new ArrayList<String>(Arrays.asList(bindingKeys)); + nonMatches.removeAll(resultStrings); + System.out.println("\""+routingKeyShortString+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches); + + + } + + private static void printMatches(String bindingKey, String routingKey) + { + printMatches(new String[] { bindingKey }, routingKey); + } + + + private static boolean matches(String bindingKey, String routingKey) + { + AMQShortString bindingKeyShortString = new AMQShortString(bindingKey); + AMQShortString routingKeyShortString = new AMQShortString(routingKey); + TopicParser parser = new TopicParser(); + + final TopicMatcherResult result = new TopicMatcherResult(){}; + + TopicMatcherDFAState sm = parser.createStateMachine(bindingKeyShortString, result); + return !sm.parse(parser._dictionary,routingKeyShortString).isEmpty(); + + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java new file mode 100644 index 0000000000..f14d70f8a1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java @@ -0,0 +1,54 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +/* +* +* 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. +* +*/ +public final class TopicWord +{ + public static final TopicWord ANY_WORD = new TopicWord("*"); + public static final TopicWord WILDCARD_WORD = new TopicWord("#"); + private String _word; + + public TopicWord() + { + + } + + public TopicWord(String s) + { + _word = s; + } + + public TopicWord(final AMQShortString name) + { + _word = name.toString(); + } + + public String toString() + { + return _word; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java new file mode 100644 index 0000000000..65a0cd3107 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java @@ -0,0 +1,63 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.concurrent.ConcurrentHashMap; + +/* +* +* 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. +* +*/ +public class TopicWordDictionary +{ + private final ConcurrentHashMap<AMQShortString,TopicWord> _dictionary = + new ConcurrentHashMap<AMQShortString,TopicWord>(); + + + + public TopicWordDictionary() + { + _dictionary.put(new AMQShortString("*"), TopicWord.ANY_WORD); + _dictionary.put(new AMQShortString("#"), TopicWord.WILDCARD_WORD); + } + + + + + public TopicWord getOrCreateWord(AMQShortString name) + { + TopicWord word = _dictionary.putIfAbsent(name, new TopicWord(name)); + if(word == null) + { + word = _dictionary.get(name); + } + return word; + } + + + public TopicWord getWord(AMQShortString name) + { + TopicWord word = _dictionary.get(name); + if(word == null) + { + word = TopicWord.ANY_WORD; + } + return word; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java new file mode 100644 index 0000000000..a964bce306 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java @@ -0,0 +1,275 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +/** + * An expression which performs an operation on two expression values + */ +public abstract class ArithmeticExpression<E extends Exception> extends BinaryExpression<E> +{ + + protected static final int INTEGER = 1; + protected static final int LONG = 2; + protected static final int DOUBLE = 3; + + /** + * @param left + * @param right + */ + public ArithmeticExpression(Expression left, Expression right) + { + super(left, right); + } + + public static Expression createPlus(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof String) + { + String text = (String) lvalue; + String answer = text + rvalue; + + return answer; + } + else if (lvalue instanceof Number) + { + return plus((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call plus operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "+"; + } + }; + } + + public static Expression createMinus(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return minus((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call minus operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "-"; + } + }; + } + + public static Expression createMultiply(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return multiply((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call multiply operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "*"; + } + }; + } + + public static Expression createDivide(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return divide((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call divide operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "/"; + } + }; + } + + public static Expression createMod(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return mod((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call mod operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "%"; + } + }; + } + + protected Number plus(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return new Integer(left.intValue() + right.intValue()); + + case LONG: + return new Long(left.longValue() + right.longValue()); + + default: + return new Double(left.doubleValue() + right.doubleValue()); + } + } + + protected Number minus(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return new Integer(left.intValue() - right.intValue()); + + case LONG: + return new Long(left.longValue() - right.longValue()); + + default: + return new Double(left.doubleValue() - right.doubleValue()); + } + } + + protected Number multiply(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return new Integer(left.intValue() * right.intValue()); + + case LONG: + return new Long(left.longValue() * right.longValue()); + + default: + return new Double(left.doubleValue() * right.doubleValue()); + } + } + + protected Number divide(Number left, Number right) + { + return new Double(left.doubleValue() / right.doubleValue()); + } + + protected Number mod(Number left, Number right) + { + return new Double(left.doubleValue() % right.doubleValue()); + } + + private int numberType(Number left, Number right) + { + if (isDouble(left) || isDouble(right)) + { + return DOUBLE; + } + else if ((left instanceof Long) || (right instanceof Long)) + { + return LONG; + } + else + { + return INTEGER; + } + } + + private boolean isDouble(Number n) + { + return (n instanceof Float) || (n instanceof Double); + } + + protected Number asNumber(Object value) + { + if (value instanceof Number) + { + return (Number) value; + } + else + { + throw new RuntimeException("Cannot convert value: " + value + " into a number"); + } + } + + public Object evaluate(Filterable<E> message) throws E + { + Object lvalue = left.evaluate(message); + if (lvalue == null) + { + return null; + } + + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + return evaluate(lvalue, rvalue); + } + + /** + * @param lvalue + * @param rvalue + * @return + */ + protected abstract Object evaluate(Object lvalue, Object rvalue); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java new file mode 100644 index 0000000000..7308de80d6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java @@ -0,0 +1,106 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +/** + * An expression which performs an operation on two expression values. + */ +public abstract class BinaryExpression<E extends Exception> implements Expression<E> +{ + protected Expression<E> left; + protected Expression<E> right; + + public BinaryExpression(Expression<E> left, Expression<E> right) + { + this.left = left; + this.right = right; + } + + public Expression<E> getLeft() + { + return left; + } + + public Expression<E> getRight() + { + return right; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")"; + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return toString().equals(o.toString()); + + } + + /** + * Returns the symbol that represents this binary expression. For example, addition is + * represented by "+" + * + * @return + */ + public abstract String getExpressionSymbol(); + + /** + * @param expression + */ + public void setRight(Expression<E> expression) + { + right = expression; + } + + /** + * @param expression + */ + public void setLeft(Expression<E> expression) + { + left = expression; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java new file mode 100644 index 0000000000..9beb9798d0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java @@ -0,0 +1,41 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * A BooleanExpression is an expression that always + * produces a Boolean result. + */ +public interface BooleanExpression<E extends Exception> extends Expression<E> +{ + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + * @throws E + */ + public boolean matches(Filterable<E> message) throws E; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java new file mode 100644 index 0000000000..921005c462 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java @@ -0,0 +1,601 @@ +/* + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * A filter performing a comparison of two objects + */ +public abstract class ComparisonExpression<E extends Exception> extends BinaryExpression<E> implements BooleanExpression<E> +{ + + public static<E extends Exception> BooleanExpression<E> createBetween(Expression<E> value, Expression left, Expression<E> right) + { + return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); + } + + public static<E extends Exception> BooleanExpression<E> createNotBetween(Expression<E> value, Expression<E> left, Expression<E> right) + { + return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); + } + + private static final HashSet REGEXP_CONTROL_CHARS = new HashSet(); + + static + { + REGEXP_CONTROL_CHARS.add(new Character('.')); + REGEXP_CONTROL_CHARS.add(new Character('\\')); + REGEXP_CONTROL_CHARS.add(new Character('[')); + REGEXP_CONTROL_CHARS.add(new Character(']')); + REGEXP_CONTROL_CHARS.add(new Character('^')); + REGEXP_CONTROL_CHARS.add(new Character('$')); + REGEXP_CONTROL_CHARS.add(new Character('?')); + REGEXP_CONTROL_CHARS.add(new Character('*')); + REGEXP_CONTROL_CHARS.add(new Character('+')); + REGEXP_CONTROL_CHARS.add(new Character('{')); + REGEXP_CONTROL_CHARS.add(new Character('}')); + REGEXP_CONTROL_CHARS.add(new Character('|')); + REGEXP_CONTROL_CHARS.add(new Character('(')); + REGEXP_CONTROL_CHARS.add(new Character(')')); + REGEXP_CONTROL_CHARS.add(new Character(':')); + REGEXP_CONTROL_CHARS.add(new Character('&')); + REGEXP_CONTROL_CHARS.add(new Character('<')); + REGEXP_CONTROL_CHARS.add(new Character('>')); + REGEXP_CONTROL_CHARS.add(new Character('=')); + REGEXP_CONTROL_CHARS.add(new Character('!')); + } + + static class LikeExpression<E extends Exception> extends UnaryExpression<E> implements BooleanExpression<E> + { + + Pattern likePattern; + + /** + * @param right + */ + public LikeExpression(Expression<E> right, String like, int escape) + { + super(right); + + StringBuffer regexp = new StringBuffer(like.length() * 2); + regexp.append("\\A"); // The beginning of the input + for (int i = 0; i < like.length(); i++) + { + char c = like.charAt(i); + if (escape == (0xFFFF & c)) + { + i++; + if (i >= like.length()) + { + // nothing left to escape... + break; + } + + char t = like.charAt(i); + regexp.append("\\x"); + regexp.append(Integer.toHexString(0xFFFF & t)); + } + else if (c == '%') + { + regexp.append(".*?"); // Do a non-greedy match + } + else if (c == '_') + { + regexp.append("."); // match one + } + else if (REGEXP_CONTROL_CHARS.contains(new Character(c))) + { + regexp.append("\\x"); + regexp.append(Integer.toHexString(0xFFFF & c)); + } + else + { + regexp.append(c); + } + } + + regexp.append("\\z"); // The end of the input + + likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL); + } + + /** + * org.apache.activemq.filter.UnaryExpression#getExpressionSymbol() + */ + public String getExpressionSymbol() + { + return "LIKE"; + } + + /** + * org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) + */ + public Object evaluate(Filterable<E> message) throws E + { + + Object rv = this.getRight().evaluate(message); + + if (rv == null) + { + return null; + } + + if (!(rv instanceof String)) + { + return + Boolean.FALSE; + // throw new RuntimeException("LIKE can only operate on String identifiers. LIKE attemped on: '" + rv.getClass()); + } + + return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE; + } + + public boolean matches(Filterable<E> message) throws E + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + + public static BooleanExpression createLike(Expression left, String right, String escape) + { + if ((escape != null) && (escape.length() != 1)) + { + throw new RuntimeException( + "The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape); + } + + int c = -1; + if (escape != null) + { + c = 0xFFFF & escape.charAt(0); + } + + return new LikeExpression(left, right, c); + } + + public static BooleanExpression createNotLike(Expression left, String right, String escape) + { + return UnaryExpression.createNOT(createLike(left, right, escape)); + } + + public static BooleanExpression createInFilter(Expression left, List elements) + { + + if (!(left instanceof PropertyExpression)) + { + throw new RuntimeException("Expected a property for In expression, got: " + left); + } + + return UnaryExpression.createInExpression((PropertyExpression) left, elements, false); + + } + + public static BooleanExpression createNotInFilter(Expression left, List elements) + { + + if (!(left instanceof PropertyExpression)) + { + throw new RuntimeException("Expected a property for In expression, got: " + left); + } + + return UnaryExpression.createInExpression((PropertyExpression) left, elements, true); + + } + + public static BooleanExpression createIsNull(Expression left) + { + return doCreateEqual(left, ConstantExpression.NULL); + } + + public static BooleanExpression createIsNotNull(Expression left) + { + return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); + } + + public static BooleanExpression createNotEqual(Expression left, Expression right) + { + return UnaryExpression.createNOT(createEqual(left, right)); + } + + public static BooleanExpression createEqual(Expression left, Expression right) + { + checkEqualOperand(left); + checkEqualOperand(right); + checkEqualOperandCompatability(left, right); + + return doCreateEqual(left, right); + } + + private static<E extends Exception> BooleanExpression<E> doCreateEqual(Expression<E> left, Expression<E> right) + { + return new EqualExpression(left, right); + } + + public static BooleanExpression createGreaterThan(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + protected boolean asBoolean(int answer) + { + return answer > 0; + } + + public String getExpressionSymbol() + { + return ">"; + } + }; + } + + public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + protected boolean asBoolean(int answer) + { + return answer >= 0; + } + + public String getExpressionSymbol() + { + return ">="; + } + }; + } + + public static BooleanExpression createLessThan(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + + protected boolean asBoolean(int answer) + { + return answer < 0; + } + + public String getExpressionSymbol() + { + return "<"; + } + + }; + } + + public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + + protected boolean asBoolean(int answer) + { + return answer <= 0; + } + + public String getExpressionSymbol() + { + return "<="; + } + }; + } + + /** + * Only Numeric expressions can be used in >, >=, < or <= expressions.s + * + * @param expr + */ + public static void checkLessThanOperand(Expression expr) + { + if (expr instanceof ConstantExpression) + { + Object value = ((ConstantExpression) expr).getValue(); + if (value instanceof Number) + { + return; + } + + // Else it's boolean or a String.. + throw new RuntimeException("Value '" + expr + "' cannot be compared."); + } + + if (expr instanceof BooleanExpression) + { + throw new RuntimeException("Value '" + expr + "' cannot be compared."); + } + } + + /** + * Validates that the expression can be used in == or <> expression. + * Cannot not be NULL TRUE or FALSE litterals. + * + * @param expr + */ + public static void checkEqualOperand(Expression expr) + { + if (expr instanceof ConstantExpression) + { + Object value = ((ConstantExpression) expr).getValue(); + if (value == null) + { + throw new RuntimeException("'" + expr + "' cannot be compared."); + } + } + } + + /** + * + * @param left + * @param right + */ + private static void checkEqualOperandCompatability(Expression left, Expression right) + { + if ((left instanceof ConstantExpression) && (right instanceof ConstantExpression)) + { + if ((left instanceof BooleanExpression) && !(right instanceof BooleanExpression)) + { + throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); + } + } + } + + /** + * @param left + * @param right + */ + public ComparisonExpression(Expression left, Expression right) + { + super(left, right); + } + + public Object evaluate(Filterable<E> message) throws E + { + Comparable lv = (Comparable) left.evaluate(message); + if (lv == null) + { + return null; + } + + Comparable rv = (Comparable) right.evaluate(message); + if (rv == null) + { + return null; + } + + return compare(lv, rv); + } + + protected Boolean compare(Comparable lv, Comparable rv) + { + Class lc = lv.getClass(); + Class rc = rv.getClass(); + // If the the objects are not of the same type, + // try to convert up to allow the comparison. + if (lc != rc) + { + if (lc == Byte.class) + { + if (rc == Short.class) + { + lv = new Short(((Number) lv).shortValue()); + } + else if (rc == Integer.class) + { + lv = new Integer(((Number) lv).intValue()); + } + else if (rc == Long.class) + { + lv = new Long(((Number) lv).longValue()); + } + else if (rc == Float.class) + { + lv = new Float(((Number) lv).floatValue()); + } + else if (rc == Double.class) + { + lv = new Double(((Number) lv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Short.class) + { + if (rc == Integer.class) + { + lv = new Integer(((Number) lv).intValue()); + } + else if (rc == Long.class) + { + lv = new Long(((Number) lv).longValue()); + } + else if (rc == Float.class) + { + lv = new Float(((Number) lv).floatValue()); + } + else if (rc == Double.class) + { + lv = new Double(((Number) lv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Integer.class) + { + if (rc == Long.class) + { + lv = new Long(((Number) lv).longValue()); + } + else if (rc == Float.class) + { + lv = new Float(((Number) lv).floatValue()); + } + else if (rc == Double.class) + { + lv = new Double(((Number) lv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Long.class) + { + if (rc == Integer.class) + { + rv = new Long(((Number) rv).longValue()); + } + else if (rc == Float.class) + { + lv = new Float(((Number) lv).floatValue()); + } + else if (rc == Double.class) + { + lv = new Double(((Number) lv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Float.class) + { + if (rc == Integer.class) + { + rv = new Float(((Number) rv).floatValue()); + } + else if (rc == Long.class) + { + rv = new Float(((Number) rv).floatValue()); + } + else if (rc == Double.class) + { + lv = new Double(((Number) lv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Double.class) + { + if (rc == Integer.class) + { + rv = new Double(((Number) rv).doubleValue()); + } + else if (rc == Long.class) + { + rv = new Double(((Number) rv).doubleValue()); + } + else if (rc == Float.class) + { + rv = new Float(((Number) rv).doubleValue()); + } + else + { + return Boolean.FALSE; + } + } + else + { + return Boolean.FALSE; + } + } + + return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; + } + + protected abstract boolean asBoolean(int answer); + + public boolean matches(Filterable<E> message) throws E + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + + private static class EqualExpression<E extends Exception> extends ComparisonExpression<E> + { + public EqualExpression(final Expression<E> left, final Expression<E> right) + { + super(left, right); + } + + public Object evaluate(Filterable<E> message) throws E + { + Object lv = left.evaluate(message); + Object rv = right.evaluate(message); + + // Iff one of the values is null + if ((lv == null) ^ (rv == null)) + { + return Boolean.FALSE; + } + + if ((lv == rv) || lv.equals(rv)) + { + return Boolean.TRUE; + } + + if ((lv instanceof Comparable) && (rv instanceof Comparable)) + { + return compare((Comparable) lv, (Comparable) rv); + } + + return Boolean.FALSE; + } + + protected boolean asBoolean(int answer) + { + return answer == 0; + } + + public String getExpressionSymbol() + { + return "="; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java new file mode 100644 index 0000000000..3ed2286f2e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java @@ -0,0 +1,211 @@ +/* + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.math.BigDecimal; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents a constant expression + */ +public class ConstantExpression<E extends Exception> implements Expression<E> +{ + + static class BooleanConstantExpression<E extends Exception> extends ConstantExpression<E> implements BooleanExpression<E> + { + public BooleanConstantExpression(Object value) + { + super(value); + } + + public boolean matches(Filterable<E> message) throws E + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + + public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null); + public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE); + public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE); + + private Object value; + + public static ConstantExpression createFromDecimal(String text) + { + + // Strip off the 'l' or 'L' if needed. + if (text.endsWith("l") || text.endsWith("L")) + { + text = text.substring(0, text.length() - 1); + } + + Number value; + try + { + value = new Long(text); + } + catch (NumberFormatException e) + { + // The number may be too big to fit in a long. + value = new BigDecimal(text); + } + + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = new Integer(value.intValue()); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFromHex(String text) + { + Number value = new Long(Long.parseLong(text.substring(2), 16)); + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = new Integer(value.intValue()); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFromOctal(String text) + { + Number value = new Long(Long.parseLong(text, 8)); + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = new Integer(value.intValue()); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFloat(String text) + { + Number value = new Double(text); + + return new ConstantExpression(value); + } + + public ConstantExpression(Object value) + { + this.value = value; + } + + public Object evaluate(Filterable<E> message) throws E + { + return value; + } + + public Object getValue() + { + return value; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + if (value == null) + { + return "NULL"; + } + + if (value instanceof Boolean) + { + return ((Boolean) value).booleanValue() ? "TRUE" : "FALSE"; + } + + if (value instanceof String) + { + return encodeString((String) value); + } + + return value.toString(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return toString().equals(o.toString()); + + } + + /** + * Encodes the value of string so that it looks like it would look like + * when it was provided in a selector. + * + * @param s + * @return + */ + public static String encodeString(String s) + { + StringBuffer b = new StringBuffer(); + b.append('\''); + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if (c == '\'') + { + b.append(c); + } + + b.append(c); + } + + b.append('\''); + + return b.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java new file mode 100644 index 0000000000..f2ebe41d26 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java @@ -0,0 +1,38 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents an expression + */ +public interface Expression<E extends Exception> +{ + + /** + * @return the value of this expression + */ + public Object evaluate(Filterable<E> message) throws E; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java new file mode 100644 index 0000000000..dd3c126ee5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java @@ -0,0 +1,39 @@ +/* + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; +import org.apache.qpid.AMQException; + +public interface FilterManager<E extends Exception> +{ + void add(MessageFilter<E> filter); + + void remove(MessageFilter<E> filter); + + boolean allAllow(Filterable<E> msg); + + boolean hasFilters(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java new file mode 100644 index 0000000000..a7f49d0566 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java @@ -0,0 +1,67 @@ +/* + * 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.filter; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.FieldTable; + + +public class FilterManagerFactory +{ + //private final static Logger _logger = LoggerFactory.getLogger(FilterManagerFactory.class); + private final static org.apache.log4j.Logger _logger = org.apache.log4j.Logger.getLogger(FilterManagerFactory.class); + + //fixme move to a common class so it can be refered to from client code. + + public static FilterManager createManager(FieldTable filters) throws AMQException + { + FilterManager manager = null; + + if (filters != null) + { + + + + if(filters.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue())) + { + String selector = filters.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()); + + if (selector != null && !selector.equals("")) + { + manager = new SimpleFilterManager(); + manager.add(new JMSSelectorFilter(selector)); + } + + } + + + } + else + { + _logger.debug("No Filters found."); + } + + + return manager; + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java new file mode 100644 index 0000000000..96c9353872 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java @@ -0,0 +1,56 @@ +/* + * 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.filter; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.filter.jms.selector.SelectorParser; +import org.apache.qpid.server.queue.Filterable; + + +public class JMSSelectorFilter<E extends Exception> implements MessageFilter<E> +{ + private final static Logger _logger = org.apache.log4j.Logger.getLogger(JMSSelectorFilter.class); + + private String _selector; + private BooleanExpression<E> _matcher; + + public JMSSelectorFilter(String selector) throws AMQException + { + _selector = selector; + _matcher = new SelectorParser().parse(selector); + } + + public boolean matches(Filterable<E> message) throws E + { + boolean match = _matcher.matches(message); + if(_logger.isDebugEnabled()) + { + _logger.debug(message + " match(" + match + ") selector(" + System.identityHashCode(_selector) + "):" + _selector); + } + return match; + } + + public String getSelector() + { + return _selector; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java new file mode 100644 index 0000000000..094363ed9a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java @@ -0,0 +1,122 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * A filter performing a comparison of two objects + */ +public abstract class LogicExpression<E extends Exception> extends BinaryExpression<E> implements BooleanExpression<E> +{ + + public static<E extends Exception> BooleanExpression createOR(BooleanExpression<E> lvalue, BooleanExpression<E> rvalue) + { + return new OrExpression(lvalue, rvalue); + } + + public static<E extends Exception> BooleanExpression createAND(BooleanExpression<E> lvalue, BooleanExpression<E> rvalue) + { + return new AndExpression(lvalue, rvalue); + } + + /** + * @param left + * @param right + */ + public LogicExpression(BooleanExpression left, BooleanExpression right) + { + super(left, right); + } + + public abstract Object evaluate(Filterable<E> message) throws E; + + public boolean matches(Filterable<E> message) throws E + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + + private static class OrExpression<E extends Exception> extends LogicExpression<E> + { + public OrExpression(final BooleanExpression<E> lvalue, final BooleanExpression<E> rvalue) + { + super(lvalue, rvalue); + } + + public Object evaluate(Filterable<E> message) throws E + { + + Boolean lv = (Boolean) left.evaluate(message); + // Can we do an OR shortcut?? + if ((lv != null) && lv.booleanValue()) + { + return Boolean.TRUE; + } + + Boolean rv = (Boolean) right.evaluate(message); + + return (rv == null) ? null : rv; + } + + public String getExpressionSymbol() + { + return "OR"; + } + } + + private static class AndExpression<E extends Exception> extends LogicExpression<E> + { + public AndExpression(final BooleanExpression<E> lvalue, final BooleanExpression<E> rvalue) + { + super(lvalue, rvalue); + } + + public Object evaluate(Filterable<E> message) throws E + { + + Boolean lv = (Boolean) left.evaluate(message); + + // Can we do an AND shortcut?? + if (lv == null) + { + return null; + } + + if (!lv.booleanValue()) + { + return Boolean.FALSE; + } + + Boolean rv = (Boolean) right.evaluate(message); + + return (rv == null) ? null : rv; + } + + public String getExpressionSymbol() + { + return "AND"; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java new file mode 100644 index 0000000000..58fc55f8e6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.filter; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +public interface MessageFilter<E extends Exception> +{ + boolean matches(Filterable<E> message) throws E; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java new file mode 100644 index 0000000000..f1b3b2511d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.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.filter; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +public class NoConsumerFilter implements MessageFilter +{ + private final static Logger _logger = org.apache.log4j.Logger.getLogger(NoConsumerFilter.class); + + + public NoConsumerFilter() throws AMQException + { + _logger.info("Created NoConsumerFilter"); + } + + public boolean matches(Filterable message) + { + return true; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java new file mode 100644 index 0000000000..b30c70dac3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java @@ -0,0 +1,268 @@ +/* + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.util.HashMap; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.CommonContentHeaderProperties; +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents a property expression + */ +public class PropertyExpression<E extends Exception> implements Expression<E> +{ + // Constants - defined the same as JMS + private static final int NON_PERSISTENT = 1; + private static final int PERSISTENT = 2; + private static final int DEFAULT_PRIORITY = 4; + + private static final Logger _logger = org.apache.log4j.Logger.getLogger(PropertyExpression.class); + + private static final HashMap<String, Expression<? extends Exception>> JMS_PROPERTY_EXPRESSIONS = new HashMap<String, Expression<? extends Exception>>(); + + { + JMS_PROPERTY_EXPRESSIONS.put("JMSDestination", new Expression<E>() + { + public Object evaluate(Filterable<E> message) + { + //TODO + return null; + } + }); + JMS_PROPERTY_EXPRESSIONS.put("JMSReplyTo", new ReplyToExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSType", new TypeExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSDeliveryMode", new DeliveryModeExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSPriority", new PriorityExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("AMQMessageID", new MessageIDExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSTimestamp", new TimestampExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSCorrelationID", new CorrelationIdExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSExpiration", new ExpirationExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSRedelivered", new Expression<E>() + { + public Object evaluate(Filterable message) throws E + { + return message.isRedelivered(); + } + }); + } + + private final String name; + private final Expression<E> jmsPropertyExpression; + + public boolean outerTest() + { + return false; + } + + public PropertyExpression(String name) + { + this.name = name; + + + + jmsPropertyExpression = (Expression<E>) JMS_PROPERTY_EXPRESSIONS.get(name); + } + + public Object evaluate(Filterable<E> message) throws E + { + + if (jmsPropertyExpression != null) + { + return jmsPropertyExpression.evaluate(message); + } + else + { + + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) message.getContentHeaderBody().properties; + + if (_logger.isDebugEnabled()) + { + _logger.debug("Looking up property:" + name); + _logger.debug("Properties are:" + _properties.getHeaders().keySet()); + } + + return _properties.getHeaders().getObject(name); + } + } + + public String getName() + { + return name; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return name; + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return name.hashCode(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return name.equals(((PropertyExpression) o).name); + + } + + private static class ReplyToExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + AMQShortString replyTo = _properties.getReplyTo(); + + return (replyTo == null) ? null : replyTo.toString(); + + } + + } + + private static class TypeExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + AMQShortString type = _properties.getType(); + + return (type == null) ? null : type.toString(); + + } + } + + private static class DeliveryModeExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + int mode = message.isPersistent() ? PERSISTENT : NON_PERSISTENT; + if (_logger.isDebugEnabled()) + { + _logger.debug("JMSDeliveryMode is :" + mode); + } + + return mode; + } + } + + private static class PriorityExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + + return (int) _properties.getPriority(); + } + } + + private static class MessageIDExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + AMQShortString messageId = _properties.getMessageId(); + + return (messageId == null) ? null : messageId; + + } + } + + private static class TimestampExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + + return _properties.getTimestamp(); + } + } + + private static class CorrelationIdExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + AMQShortString correlationId = _properties.getCorrelationId(); + + return (correlationId == null) ? null : correlationId.toString(); + } + } + + private static class ExpirationExpression<E extends Exception> implements Expression<E> + { + public Object evaluate(Filterable<E> message) throws E + { + + CommonContentHeaderProperties _properties = + (CommonContentHeaderProperties) + message.getContentHeaderBody().properties; + + return _properties.getExpiration(); + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java new file mode 100644 index 0000000000..cb738e1489 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.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.filter; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +public class SimpleFilterManager implements FilterManager<AMQException> +{ + private final Logger _logger = Logger.getLogger(SimpleFilterManager.class); + + private final ConcurrentLinkedQueue<MessageFilter<AMQException>> _filters; + + public SimpleFilterManager() + { + _logger.debug("Creating SimpleFilterManager"); + _filters = new ConcurrentLinkedQueue<MessageFilter<AMQException>>(); + } + + public void add(MessageFilter<AMQException> filter) + { + _filters.add(filter); + } + + public void remove(MessageFilter<AMQException> filter) + { + _filters.remove(filter); + } + + public boolean allAllow(Filterable<AMQException> msg) + { + for (MessageFilter<AMQException> filter : _filters) + { + try + { + if (!filter.matches(msg)) + { + return false; + } + } + catch (AMQException e) + { + //fixme + e.printStackTrace(); + return false; + } + } + return true; + } + + public boolean hasFilters() + { + return !_filters.isEmpty(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java new file mode 100644 index 0000000000..799a38af5a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java @@ -0,0 +1,369 @@ +/* + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +/** + * An expression which performs an operation on two expression values + */ +public abstract class UnaryExpression<E extends Exception> implements Expression<E> +{ + + private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE); + protected Expression<E> right; + + public static<E extends Exception> Expression<E> createNegate(Expression<E> left) + { + return new NegativeExpression(left); + } + + public static<E extends Exception> BooleanExpression createInExpression(PropertyExpression<E> right, List elements, final boolean not) + { + + // Use a HashSet if there are many elements. + Collection t; + if (elements.size() == 0) + { + t = null; + } + else if (elements.size() < 5) + { + t = elements; + } + else + { + t = new HashSet(elements); + } + + final Collection inList = t; + + return new InExpression(right, inList, not); + } + + abstract static class BooleanUnaryExpression<E extends Exception> extends UnaryExpression<E> implements BooleanExpression<E> + { + public BooleanUnaryExpression(Expression<E> left) + { + super(left); + } + + public boolean matches(Filterable<E> message) throws E + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + ; + + public static<E extends Exception> BooleanExpression<E> createNOT(BooleanExpression<E> left) + { + return new NotExpression(left); + } + + public static BooleanExpression createXPath(final String xpath) + { + return new XPathExpression(xpath); + } + + public static BooleanExpression createXQuery(final String xpath) + { + return new XQueryExpression(xpath); + } + + public static<E extends Exception> BooleanExpression createBooleanCast(Expression<E> left) + { + return new BooleanCastExpression(left); + } + + private static Number negate(Number left) + { + Class clazz = left.getClass(); + if (clazz == Integer.class) + { + return new Integer(-left.intValue()); + } + else if (clazz == Long.class) + { + return new Long(-left.longValue()); + } + else if (clazz == Float.class) + { + return new Float(-left.floatValue()); + } + else if (clazz == Double.class) + { + return new Double(-left.doubleValue()); + } + else if (clazz == BigDecimal.class) + { + // We ussually get a big deciamal when we have Long.MIN_VALUE constant in the + // Selector. Long.MIN_VALUE is too big to store in a Long as a positive so we store it + // as a Big decimal. But it gets Negated right away.. to here we try to covert it back + // to a Long. + BigDecimal bd = (BigDecimal) left; + bd = bd.negate(); + + if (BD_LONG_MIN_VALUE.compareTo(bd) == 0) + { + return new Long(Long.MIN_VALUE); + } + + return bd; + } + else + { + throw new RuntimeException("Don't know how to negate: " + left); + } + } + + public UnaryExpression(Expression left) + { + this.right = left; + } + + public Expression<E> getRight() + { + return right; + } + + public void setRight(Expression expression) + { + right = expression; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + getExpressionSymbol() + " " + right.toString() + ")"; + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return toString().equals(o.toString()); + + } + + /** + * Returns the symbol that represents this binary expression. For example, addition is + * represented by "+" + * + * @return + */ + public abstract String getExpressionSymbol(); + + private static class NegativeExpression<E extends Exception> extends UnaryExpression<E> + { + public NegativeExpression(final Expression<E> left) + { + super(left); + } + + public Object evaluate(Filterable<E> message) throws E + { + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (rvalue instanceof Number) + { + return negate((Number) rvalue); + } + + return null; + } + + public String getExpressionSymbol() + { + return "-"; + } + } + + private static class InExpression<E extends Exception> extends BooleanUnaryExpression<E> + { + private final Collection _inList; + private final boolean _not; + + public InExpression(final PropertyExpression<E> right, final Collection inList, final boolean not) + { + super(right); + _inList = inList; + _not = not; + } + + public Object evaluate(Filterable<E> message) throws E + { + + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (rvalue.getClass() != String.class) + { + return null; + } + + if (((_inList != null) && _inList.contains(rvalue)) ^ _not) + { + return Boolean.TRUE; + } + else + { + return Boolean.FALSE; + } + + } + + public String toString() + { + StringBuffer answer = new StringBuffer(); + answer.append(right); + answer.append(" "); + answer.append(getExpressionSymbol()); + answer.append(" ( "); + + int count = 0; + for (Iterator i = _inList.iterator(); i.hasNext();) + { + Object o = (Object) i.next(); + if (count != 0) + { + answer.append(", "); + } + + answer.append(o); + count++; + } + + answer.append(" )"); + + return answer.toString(); + } + + public String getExpressionSymbol() + { + if (_not) + { + return "NOT IN"; + } + else + { + return "IN"; + } + } + } + + private static class NotExpression<E extends Exception> extends BooleanUnaryExpression<E> + { + public NotExpression(final BooleanExpression<E> left) + { + super(left); + } + + public Object evaluate(Filterable<E> message) throws E + { + Boolean lvalue = (Boolean) right.evaluate(message); + if (lvalue == null) + { + return null; + } + + return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE; + } + + public String getExpressionSymbol() + { + return "NOT"; + } + } + + private static class BooleanCastExpression<E extends Exception> extends BooleanUnaryExpression<E> + { + public BooleanCastExpression(final Expression<E> left) + { + super(left); + } + + public Object evaluate(Filterable<E> message) throws E + { + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (!rvalue.getClass().equals(Boolean.class)) + { + return Boolean.FALSE; + } + + return ((Boolean) rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE; + } + + public String toString() + { + return right.toString(); + } + + public String getExpressionSymbol() + { + return ""; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java new file mode 100644 index 0000000000..1311178fb1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java @@ -0,0 +1,127 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Used to evaluate an XPath Expression in a JMS selector. + */ +public final class XPathExpression implements BooleanExpression { + + private static final Logger log = Logger.getLogger(XPathExpression.class); + private static final String EVALUATOR_SYSTEM_PROPERTY = "org.apache.qpid.server.filter.XPathEvaluatorClassName"; + private static final String DEFAULT_EVALUATOR_CLASS_NAME=XalanXPathEvaluator.class.getName(); + + private static final Constructor EVALUATOR_CONSTRUCTOR; + + static { + String cn = System.getProperty(EVALUATOR_SYSTEM_PROPERTY, DEFAULT_EVALUATOR_CLASS_NAME); + Constructor m = null; + try { + try { + m = getXPathEvaluatorConstructor(cn); + } catch (Throwable e) { + log.warn("Invalid "+XPathEvaluator.class.getName()+" implementation: "+cn+", reason: "+e,e); + cn = DEFAULT_EVALUATOR_CLASS_NAME; + try { + m = getXPathEvaluatorConstructor(cn); + } catch (Throwable e2) { + log.error("Default XPath evaluator could not be loaded",e); + } + } + } finally { + EVALUATOR_CONSTRUCTOR = m; + } + } + + private static Constructor getXPathEvaluatorConstructor(String cn) throws ClassNotFoundException, SecurityException, NoSuchMethodException { + Class c = XPathExpression.class.getClassLoader().loadClass(cn); + if( !XPathEvaluator.class.isAssignableFrom(c) ) { + throw new ClassCastException(""+c+" is not an instance of "+XPathEvaluator.class); + } + return c.getConstructor(new Class[]{String.class}); + } + + private final String xpath; + private final XPathEvaluator evaluator; + + static public interface XPathEvaluator { + public boolean evaluate(Filterable message) throws AMQException; + } + + XPathExpression(String xpath) { + this.xpath = xpath; + this.evaluator = createEvaluator(xpath); + } + + private XPathEvaluator createEvaluator(String xpath2) { + try { + return (XPathEvaluator)EVALUATOR_CONSTRUCTOR.newInstance(new Object[]{xpath}); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if( cause instanceof RuntimeException ) { + throw (RuntimeException)cause; + } + throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e); + } catch (Throwable e) { + throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e); + } + } + + public Object evaluate(Filterable message) throws AMQException { +// try { +//FIXME this is flow to disk work +// if( message.isDropped() ) +// return null; + return evaluator.evaluate(message) ? Boolean.TRUE : Boolean.FALSE; +// } catch (IOException e) { +// +// JMSException exception = new JMSException(e.getMessage()); +// exception.initCause(e); +// throw exception; +// +// } + + } + + public String toString() { + return "XPATH "+ConstantExpression.encodeString(xpath); + } + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + * @throws AMQException + */ + public boolean matches(Filterable message) throws AMQException + { + Object object = evaluate(message); + return object!=null && object==Boolean.TRUE; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java new file mode 100644 index 0000000000..c13f81cd08 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.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.filter; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; + +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +/** + * Used to evaluate an XQuery Expression in a JMS selector. + */ +public final class XQueryExpression implements BooleanExpression { + private final String xpath; + + XQueryExpression(String xpath) { + super(); + this.xpath = xpath; + } + + public Object evaluate(Filterable message) throws AMQException { + return Boolean.FALSE; + } + + public String toString() { + return "XQUERY "+ConstantExpression.encodeString(xpath); + } + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + * @throws AMQException + */ + public boolean matches(Filterable message) throws AMQException + { + Object object = evaluate(message); + return object!=null && object==Boolean.TRUE; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java new file mode 100644 index 0000000000..cc67776682 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java @@ -0,0 +1,103 @@ +/** + * + * 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.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.io.ByteArrayInputStream; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.Filterable; +import org.apache.xpath.CachedXPathAPI; +import org.w3c.dom.Document; +import org.w3c.dom.traversal.NodeIterator; +import org.xml.sax.InputSource; + +public class XalanXPathEvaluator implements XPathExpression.XPathEvaluator { + + private final String xpath; + + public XalanXPathEvaluator(String xpath) { + this.xpath = xpath; + } + + public boolean evaluate(Filterable m) throws AMQException + { + // TODO - we would have to check the content type and then evaluate the content + // here... is this really a feature we wish to implement? - RobG + /* + + if( m instanceof TextMessage ) { + String text = ((TextMessage)m).getText(); + return evaluate(text); + } else if ( m instanceof BytesMessage ) { + BytesMessage bm = (BytesMessage) m; + byte data[] = new byte[(int) bm.getBodyLength()]; + bm.readBytes(data); + return evaluate(data); + } + */ + return false; + + } + + private boolean evaluate(byte[] data) { + try { + + InputSource inputSource = new InputSource(new ByteArrayInputStream(data)); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder dbuilder = factory.newDocumentBuilder(); + Document doc = dbuilder.parse(inputSource); + + CachedXPathAPI cachedXPathAPI = new CachedXPathAPI(); + NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath); + return iterator.nextNode()!=null; + + } catch (Throwable e) { + return false; + } + } + + private boolean evaluate(String text) { + try { + InputSource inputSource = new InputSource(new StringReader(text)); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder dbuilder = factory.newDocumentBuilder(); + Document doc = dbuilder.parse(inputSource); + + // We should associated the cachedXPathAPI object with the message being evaluated + // since that should speedup subsequent xpath expressions. + CachedXPathAPI cachedXPathAPI = new CachedXPathAPI(); + NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath); + return iterator.nextNode()!=null; + } catch (Throwable e) { + return false; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java new file mode 100644 index 0000000000..895db7b15b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.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.flow; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Set; +import java.util.HashSet; + +public abstract class AbstractFlowCreditManager implements FlowCreditManager +{ + protected final AtomicBoolean _suspended = new AtomicBoolean(false); + private final Set<FlowCreditManagerListener> _listeners = new HashSet<FlowCreditManagerListener>(); + + public final void addStateListener(FlowCreditManagerListener listener) + { + _listeners.add(listener); + } + + public final boolean removeListener(FlowCreditManagerListener listener) + { + return _listeners.remove(listener); + } + + private void notifyListeners(final boolean suspended) + { + for(FlowCreditManagerListener listener : _listeners) + { + listener.creditStateChanged(!suspended); + } + } + + protected final void setSuspended(final boolean suspended) + { + if(_suspended.compareAndSet(!suspended, suspended)) + { + notifyListeners(suspended); + } + } + + protected final void notifyIncreaseBytesCredit() + { + notifyListeners(false); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java new file mode 100644 index 0000000000..96a1071135 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java @@ -0,0 +1,77 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Set; +import java.util.HashSet; + +/* +* +* 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. +* +*/ +public class BytesOnlyCreditManager extends AbstractFlowCreditManager +{ + private final AtomicLong _bytesCredit; + + public BytesOnlyCreditManager(long initialCredit) + { + _bytesCredit = new AtomicLong(initialCredit); + } + + public void addCredit(long messageCredit, long bytesCredit) + { + _bytesCredit.addAndGet(bytesCredit); + setSuspended(false); + } + + public void removeAllCredit() + { + _bytesCredit.set(0L); + } + + public boolean hasCredit() + { + return _bytesCredit.get() > 0L; + } + + public boolean useCreditForMessage(AMQMessage msg) + { + final long msgSize = msg.getSize(); + if(hasCredit()) + { + if(_bytesCredit.addAndGet(-msgSize) >= 0) + { + return true; + } + else + { + _bytesCredit.addAndGet(msgSize); + setSuspended(true); + return false; + } + } + else + { + return false; + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java new file mode 100644 index 0000000000..a249a6e63a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java @@ -0,0 +1,44 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +/* +* +* 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. +* +*/ +public interface FlowCreditManager +{ + + public static interface FlowCreditManagerListener + { + void creditStateChanged(boolean hasCredit); + } + + void addStateListener(FlowCreditManagerListener listener); + + boolean removeListener(FlowCreditManagerListener listener); + + public void addCredit(long messageCredit, long bytesCredit); + + public void removeAllCredit(); + + public boolean hasCredit(); + + public boolean useCreditForMessage(AMQMessage msg); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java new file mode 100644 index 0000000000..d63431c3eb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java @@ -0,0 +1,44 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +/* +* +* 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. +* +*/ +public class LimitlessCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + public void addCredit(long messageCredit, long bytesCredit) + { + } + + public void removeAllCredit() + { + } + + public boolean hasCredit() + { + return true; + } + + public boolean useCreditForMessage(AMQMessage msg) + { + return true; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java new file mode 100644 index 0000000000..9c377481de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java @@ -0,0 +1,79 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.concurrent.atomic.AtomicLong; + +/* +* +* 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. +* +*/ +public class MessageAndBytesCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private long _messageCredit; + private long _bytesCredit; + + MessageAndBytesCreditManager(final long messageCredit, final long bytesCredit) + { + _messageCredit = messageCredit; + _bytesCredit = bytesCredit; + } + + public synchronized void addCredit(long messageCredit, long bytesCredit) + { + _messageCredit += messageCredit; + _bytesCredit += bytesCredit; + setSuspended(hasCredit()); + } + + public synchronized void removeAllCredit() + { + _messageCredit = 0L; + _bytesCredit = 0L; + setSuspended(true); + } + + public synchronized boolean hasCredit() + { + return (_messageCredit > 0L) && ( _bytesCredit > 0L ); + } + + public synchronized boolean useCreditForMessage(AMQMessage msg) + { + if(_messageCredit == 0L) + { + setSuspended(true); + return false; + } + else + { + final long msgSize = msg.getSize(); + if(msgSize > _bytesCredit) + { + setSuspended(true); + return false; + } + _messageCredit--; + _bytesCredit -= msgSize; + setSuspended(false); + return true; + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java new file mode 100644 index 0000000000..c1b3a09006 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java @@ -0,0 +1,76 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.concurrent.atomic.AtomicLong; + +/* +* +* 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. +* +*/ +public class MessageOnlyCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private final AtomicLong _messageCredit; + + public MessageOnlyCreditManager(final long initialCredit) + { + _messageCredit = new AtomicLong(initialCredit); + } + + public void addCredit(long messageCredit, long bytesCredit) + { + setSuspended(false); + _messageCredit.addAndGet(messageCredit); + } + + public void removeAllCredit() + { + setSuspended(true); + _messageCredit.set(0L); + } + + public boolean hasCredit() + { + return _messageCredit.get() > 0L; + } + + public boolean useCreditForMessage(AMQMessage msg) + { + if(hasCredit()) + { + if(_messageCredit.addAndGet(-1L) >= 0) + { + setSuspended(false); + return true; + } + else + { + _messageCredit.addAndGet(1L); + setSuspended(true); + return false; + } + } + else + { + setSuspended(true); + return false; + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java new file mode 100644 index 0000000000..be0300f2c1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java @@ -0,0 +1,185 @@ +/* +* +* 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.flow; + +import org.apache.qpid.server.queue.AMQMessage; + +public class Pre0_10CreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + + private volatile long _bytesCreditLimit; + private volatile long _messageCreditLimit; + + private volatile long _bytesCredit; + private volatile long _messageCredit; + + public Pre0_10CreditManager(long bytesCreditLimit, long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + _bytesCredit = bytesCreditLimit; + _messageCredit = messageCreditLimit; + } + + + public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit) + { + long bytesCreditChange = bytesCreditLimit - _bytesCreditLimit; + long messageCreditChange = messageCreditLimit - _messageCreditLimit; + + + + if(bytesCreditChange != 0L) + { + if(bytesCreditLimit == 0L) + { + _bytesCredit = 0; + } + else + { + _bytesCredit += bytesCreditChange; + } + } + + + if(messageCreditChange != 0L) + { + if(messageCreditLimit == 0L) + { + _messageCredit = 0; + } + else + { + _messageCredit += messageCreditChange; + } + } + + + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + + setSuspended(!hasCredit()); + + } + + + public synchronized void addCredit(final long messageCredit, final long bytesCredit) + { + final long messageCreditLimit = _messageCreditLimit; + boolean notifyIncrease = true; + if(messageCreditLimit != 0L) + { + notifyIncrease = (_messageCredit != 0); + long newCredit = _messageCredit + messageCredit; + _messageCredit = newCredit > messageCreditLimit ? messageCreditLimit : newCredit; + } + + + final long bytesCreditLimit = _bytesCreditLimit; + if(bytesCreditLimit != 0L) + { + long newCredit = _bytesCredit + bytesCredit; + _bytesCredit = newCredit > bytesCreditLimit ? bytesCreditLimit : newCredit; + if(notifyIncrease && bytesCredit>0) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + public synchronized void removeAllCredit() + { + _bytesCredit = 0L; + _messageCredit = 0L; + setSuspended(!hasCredit()); + } + + public synchronized boolean hasCredit() + { + return (_bytesCreditLimit == 0L || _bytesCredit > 0) + && (_messageCreditLimit == 0L || _messageCredit > 0); + } + + public synchronized boolean useCreditForMessage(final AMQMessage msg) + { + if(_messageCreditLimit != 0L) + { + if(_messageCredit != 0L) + { + if(_bytesCreditLimit == 0L) + { + _messageCredit--; + + return true; + } + else + { + if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit)) + { + _messageCredit--; + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + } + else + { + setSuspended(true); + return false; + } + } + else + { + if(_bytesCreditLimit == 0L) + { + + return true; + } + else + { + if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit)) + { + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java new file mode 100644 index 0000000000..e64eaeae76 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java @@ -0,0 +1,61 @@ +package org.apache.qpid.server.handler;
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+/**
+ * @author Apache Software Foundation
+ *
+ *
+ */
+public class AccessRequestHandler implements StateAwareMethodListener<AccessRequestBody>
+{
+ private static final AccessRequestHandler _instance = new AccessRequestHandler();
+
+
+ public static AccessRequestHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private AccessRequestHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AccessRequestBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+ // We don't implement access control class, but to keep clients happy that expect it
+ // always use the "0" ticket.
+ AccessRequestOkBody response = methodRegistry.createAccessRequestOkBody(0);
+
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java new file mode 100644 index 0000000000..f90e7c3dff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java @@ -0,0 +1,67 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicAckBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody> +{ + private static final Logger _log = Logger.getLogger(BasicAckMethodHandler.class); + + private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler(); + + public static BasicAckMethodHandler getInstance() + { + return _instance; + } + + private BasicAckMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicAckBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolSession = stateManager.getProtocolSession(); + + + if (_log.isDebugEnabled()) + { + _log.debug("Ack(Tag:" + body.getDeliveryTag() + ":Mult:" + body.getMultiple() + ") received on channel " + channelId); + } + + final AMQChannel channel = protocolSession.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + // this method throws an AMQException if the delivery tag is not known + channel.acknowledgeMessage(body.getDeliveryTag(), body.getMultiple()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java new file mode 100644 index 0000000000..29054f55c1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody> +{ + private static final Logger _log = Logger.getLogger(BasicCancelMethodHandler.class); + + private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler(); + + public static BasicCancelMethodHandler getInstance() + { + return _instance; + } + + private BasicCancelMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicCancelBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + final AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_log.isDebugEnabled()) + { + _log.debug("BasicCancel: for:" + body.getConsumerTag() + + " nowait:" + body.getNowait()); + } + + channel.unsubscribeConsumer(body.getConsumerTag()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + BasicCancelOkBody cancelOkBody = methodRegistry.createBasicCancelOkBody(body.getConsumerTag()); + session.writeFrame(cancelOkBody.generateFrame(channelId)); + } + } +} 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 new file mode 100644 index 0000000000..08610f24cd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java @@ -0,0 +1,173 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ConsumerTagNotUniqueException; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody> +{ + private static final Logger _logger = Logger.getLogger(BasicConsumeMethodHandler.class); + + private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler(); + + public static BasicConsumeMethodHandler getInstance() + { + return _instance; + } + + private BasicConsumeMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicConsumeBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + + + + AMQChannel channel = session.getChannel(channelId); + + VirtualHost vHost = session.getVirtualHost(); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("BasicConsume: from '" + body.getQueue() + + "' for:" + body.getConsumerTag() + + " nowait:" + body.getNowait() + + " args:" + body.getArguments()); + } + + AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue().intern()); + + if (queue == null) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("No queue for '" + body.getQueue() + "'"); + } + if (body.getQueue() != null) + { + String msg = "No such queue, '" + body.getQueue() + "'"; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + String msg = "No queue name provided, no default queue defined."; + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, msg); + } + } + else + { + + final AMQShortString consumerTagName; + + // Check authz + if (!vHost.getAccessManager().authoriseConsume(session, + body.getExclusive(), body.getNoAck(), + body.getNoLocal(), body.getNowait(), queue)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + if (body.getConsumerTag() != null) + { + consumerTagName = body.getConsumerTag().intern(); + } + else + { + consumerTagName = null; + } + + try + { + AMQShortString consumerTag = channel.subscribeToQueue(consumerTagName, queue, !body.getNoAck(), + body.getArguments(), body.getNoLocal(), body.getExclusive()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicConsumeOkBody(consumerTag); + session.writeFrame(responseBody.generateFrame(channelId)); + + } + + + } + catch (org.apache.qpid.AMQInvalidArgumentException ise) + { + _logger.debug("Closing connection due to invalid selector"); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.INVALID_ARGUMENT.getCode(), + new AMQShortString(ise.getMessage()), + body.getClazz(), + body.getMethod()); + session.writeFrame(responseBody.generateFrame(channelId)); + + + } + catch (ConsumerTagNotUniqueException e) + { + AMQShortString msg = new AMQShortString("Non-unique consumer tag, '" + body.getConsumerTag() + "'"); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + msg, // replytext + body.getClazz(), + body.getMethod()); + session.writeFrame(responseBody.generateFrame(0)); + } + catch (AMQQueue.ExistingExclusiveSubscription e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getName() + + " as it already has an existing exclusive consumer"); + } + catch (AMQQueue.ExistingSubscriptionPreventsExclusive e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getName() + + " exclusively as it already has a consumer"); + } + + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java new file mode 100644 index 0000000000..001b7858ec --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java @@ -0,0 +1,190 @@ +/*
+ * 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.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicGetBody;
+import org.apache.qpid.framing.BasicGetEmptyBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.flow.FlowCreditManager;
+import org.apache.qpid.server.flow.MessageOnlyCreditManager;
+import org.apache.qpid.server.subscription.SubscriptionImpl;
+import org.apache.qpid.server.subscription.ClientDeliveryMethod;
+import org.apache.qpid.server.subscription.RecordDeliveryMethod;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.server.subscription.SubscriptionFactoryImpl;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.queue.SimpleAMQQueue;
+import org.apache.qpid.server.security.access.Permission;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+
+public class BasicGetMethodHandler implements StateAwareMethodListener<BasicGetBody>
+{
+ private static final Logger _log = Logger.getLogger(BasicGetMethodHandler.class);
+
+ private static final BasicGetMethodHandler _instance = new BasicGetMethodHandler();
+
+ public static BasicGetMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicGetMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicGetBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+
+ VirtualHost vHost = session.getVirtualHost();
+
+ AMQChannel channel = session.getChannel(channelId);
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+ else
+ {
+ AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue());
+ if (queue == null)
+ {
+ _log.info("No queue for '" + body.getQueue() + "'");
+ if(body.getQueue()!=null)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_FOUND,
+ "No such queue, '" + body.getQueue()+ "'");
+ }
+ else
+ {
+ throw body.getConnectionException(AMQConstant.NOT_ALLOWED,
+ "No queue name provided, no default queue defined.");
+ }
+ }
+ else
+ {
+
+ //Perform ACLs
+ if (!vHost.getAccessManager().authoriseConsume(session, body.getNoAck(), queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ if (!performGet(queue,session, channel, !body.getNoAck()))
+ {
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ // TODO - set clusterId
+ BasicGetEmptyBody responseBody = methodRegistry.createBasicGetEmptyBody(null);
+
+
+ session.writeFrame(responseBody.generateFrame(channelId));
+ }
+ }
+ }
+ }
+
+ public static boolean performGet(final AMQQueue queue,
+ final AMQProtocolSession session,
+ final AMQChannel channel,
+ final boolean acks)
+ throws AMQException
+ {
+
+ final FlowCreditManager singleMessageCredit = new MessageOnlyCreditManager(1L);
+
+ final ClientDeliveryMethod getDeliveryMethod = new ClientDeliveryMethod()
+ {
+
+ int _msg;
+
+ public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ throws AMQException
+ {
+ singleMessageCredit.useCreditForMessage(entry.getMessage());
+ session.getProtocolOutputConverter().writeGetOk(entry.getMessage(), channel.getChannelId(),
+ deliveryTag, queue.getMessageCount());
+
+ }
+ };
+ final RecordDeliveryMethod getRecordMethod = new RecordDeliveryMethod()
+ {
+
+ public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag)
+ {
+ channel.addUnacknowledgedMessage(entry, deliveryTag, null);
+ }
+ };
+
+ Subscription sub;
+ if(acks)
+ {
+ sub = SubscriptionFactoryImpl.INSTANCE.createSubscription(channel, session, null, acks, null, false, singleMessageCredit, getDeliveryMethod, getRecordMethod);
+ }
+ else
+ {
+ sub = new GetNoAckSubscription(channel,
+ session,
+ null,
+ null,
+ false,
+ singleMessageCredit,
+ getDeliveryMethod,
+ getRecordMethod);
+ }
+
+ queue.registerSubscription(sub,false);
+ queue.flushSubscription(sub);
+ queue.unregisterSubscription(sub);
+ return(!singleMessageCredit.hasCredit());
+
+
+ }
+
+ public static final class GetNoAckSubscription extends SubscriptionImpl.NoAckSubscription
+ {
+ public GetNoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession,
+ AMQShortString consumerTag, FieldTable filters,
+ boolean noLocal, FlowCreditManager creditManager,
+ ClientDeliveryMethod deliveryMethod,
+ RecordDeliveryMethod recordMethod)
+ throws AMQException
+ {
+ super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod);
+ }
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return !getCreditManager().useCreditForMessage(msg.getMessage());
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java new file mode 100644 index 0000000000..a7d3ad6217 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java @@ -0,0 +1,106 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody> +{ + private static final Logger _logger = Logger.getLogger(BasicPublishMethodHandler.class); + + private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler(); + + + public static BasicPublishMethodHandler getInstance() + { + return _instance; + } + + private BasicPublishMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicPublishBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Publish received on channel " + channelId); + } + + AMQShortString exchange = body.getExchange(); + // TODO: check the delivery tag field details - is it unique across the broker or per subscriber? + if (exchange == null) + { + exchange = ExchangeDefaults.DEFAULT_EXCHANGE_NAME; + + } + + VirtualHost vHost = session.getVirtualHost(); + Exchange e = vHost.getExchangeRegistry().getExchange(exchange); + // if the exchange does not exist we raise a channel exception + if (e == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange name"); + } + else + { + // The partially populated BasicDeliver frame plus the received route body + // is stored in the channel. Once the final body frame has been received + // it is routed to the exchange. + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //Access Control + if (!vHost.getAccessManager().authorisePublish(session, + body.getImmediate(), body.getMandatory(), + body.getRoutingKey(), e)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + MessagePublishInfo info = session.getMethodRegistry().getProtocolVersionMethodConverter().convertToInfo(body); + info.setExchange(exchange); + channel.setPublishFrame(info, e); + } + } + +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java new file mode 100644 index 0000000000..dd3281c65f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicQosBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody> +{ + private static final BasicQosHandler _instance = new BasicQosHandler(); + + public static BasicQosHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, BasicQosBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setCredit(body.getPrefetchSize(), body.getPrefetchCount()); + + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicQosOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java new file mode 100644 index 0000000000..c7842cd643 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java @@ -0,0 +1,73 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicRecoverBody; +import org.apache.qpid.framing.BasicRecoverOkBody; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicRecoverMethodHandler implements StateAwareMethodListener<BasicRecoverBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRecoverMethodHandler.class); + + private static final BasicRecoverMethodHandler _instance = new BasicRecoverMethodHandler(); + + public static BasicRecoverMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, BasicRecoverBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.debug("Recover received on protocol session " + session + " and channel " + channelId); + AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.resend(body.getRequeue()); + + // Qpid 0-8 hacks a synchronous -ok onto recover. + // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant + if(session.getProtocolVersion().equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) session.getMethodRegistry(); + AMQMethodBody recoverOk = methodRegistry.createBasicRecoverOkBody(); + session.writeFrame(recoverOk.generateFrame(channelId)); + + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java new file mode 100644 index 0000000000..2c264c3d45 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java @@ -0,0 +1,75 @@ +package org.apache.qpid.server.handler;
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.BasicRecoverSyncBody;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.AMQException;
+
+public class BasicRecoverSyncMethodHandler implements StateAwareMethodListener<BasicRecoverSyncBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRecoverSyncMethodHandler.class);
+
+ private static final BasicRecoverSyncMethodHandler _instance = new BasicRecoverSyncMethodHandler();
+
+ public static BasicRecoverSyncMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.debug("Recover received on protocol session " + session + " and channel " + channelId);
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.resend(body.getRequeue());
+
+ // Qpid 0-8 hacks a synchronous -ok onto recover.
+ // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant
+ if(session.getProtocolVersion().equals(ProtocolVersion.v0_9))
+ {
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java new file mode 100644 index 0000000000..f3cab10ed7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java @@ -0,0 +1,125 @@ +/* + * + * 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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicRejectBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicRejectMethodHandler implements StateAwareMethodListener<BasicRejectBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRejectMethodHandler.class); + + private static BasicRejectMethodHandler _instance = new BasicRejectMethodHandler(); + + public static BasicRejectMethodHandler getInstance() + { + return _instance; + } + + private BasicRejectMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicRejectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting:" + body.getDeliveryTag() + + ": Requeue:" + body.getRequeue() + + //": Resend:" + evt.getMethod().resend + + " on channel:" + channel.debugIdentity()); + } + + long deliveryTag = body.getDeliveryTag(); + + QueueEntry message = channel.getUnacknowledgedMessageMap().get(deliveryTag); + + if (message == null) + { + _logger.warn("Dropping reject request as message is null for tag:" + deliveryTag); +// throw evt.getMethod().getChannelException(AMQConstant.NOT_FOUND, "Delivery Tag(" + deliveryTag + ")not known"); + } + else + { + if (message.isQueueDeleted()) + { + _logger.warn("Message's Queue as already been purged, unable to Reject. " + + "Dropping message should use Dead Letter Queue"); + message = channel.getUnacknowledgedMessageMap().remove(deliveryTag); + if(message != null) + { + message.discard(channel.getStoreContext()); + } + //sendtoDeadLetterQueue(msg) + return; + } + + if (!message.getMessage().isReferenced()) + { + _logger.warn("Message as already been purged, unable to Reject."); + return; + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting: DT:" + deliveryTag + "-" + message.getMessage().debugIdentity() + + ": Requeue:" + body.getRequeue() + + //": Resend:" + evt.getMethod().resend + + " on channel:" + channel.debugIdentity()); + } + + // If we haven't requested message to be resent to this consumer then reject it from ever getting it. + //if (!evt.getMethod().resend) + { + message.reject(); + } + + if (body.getRequeue()) + { + channel.requeue(deliveryTag); + } + else + { + _logger.warn("Dropping message as requeue not required and there is no dead letter queue"); + message = channel.getUnacknowledgedMessageMap().remove(deliveryTag); + //sendtoDeadLetterQueue(AMQMessage message) +// message.queue = channel.getDefaultDeadLetterQueue(); +// channel.requeue(deliveryTag); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java new file mode 100644 index 0000000000..9133cce6b7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class ChannelCloseHandler implements StateAwareMethodListener<ChannelCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseHandler.class); + + private static ChannelCloseHandler _instance = new ChannelCloseHandler(); + + public static ChannelCloseHandler getInstance() + { + return _instance; + } + + private ChannelCloseHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) + { + _logger.info("Received channel close for id " + channelId + " citing class " + body.getClassId() + + " and method " + body.getMethodId()); + } + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "Trying to close unknown channel"); + } + + session.closeChannel(channelId); + // Client requested closure so we don't wait for ok we send it + stateManager.getProtocolSession().closeChannelOk(channelId); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ChannelCloseOkBody responseBody = methodRegistry.createChannelCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java new file mode 100644 index 0000000000..a857490e7e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelCloseOkHandler implements StateAwareMethodListener<ChannelCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseOkHandler.class); + + private static ChannelCloseOkHandler _instance = new ChannelCloseOkHandler(); + + public static ChannelCloseOkHandler getInstance() + { + return _instance; + } + + private ChannelCloseOkHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelCloseOkBody body, int channelId) throws AMQException + { + + _logger.info("Received channel-close-ok for channel-id " + channelId); + + // Let the Protocol Session know the channel is now closed. + stateManager.getProtocolSession().closeChannelOk(channelId); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java new file mode 100644 index 0000000000..696ca8a63b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelFlowHandler implements StateAwareMethodListener<ChannelFlowBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelFlowHandler.class); + + private static ChannelFlowHandler _instance = new ChannelFlowHandler(); + + public static ChannelFlowHandler getInstance() + { + return _instance; + } + + private ChannelFlowHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelFlowBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setSuspended(!body.getActive()); + _logger.debug("Channel.Flow for channel " + channelId + ", active=" + body.getActive()); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelFlowOkBody(body.getActive()); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java new file mode 100644 index 0000000000..054674aed4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java @@ -0,0 +1,103 @@ +/* + * + * 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.handler; + +import java.util.UUID; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ChannelOpenHandler implements StateAwareMethodListener<ChannelOpenBody> +{ + private static ChannelOpenHandler _instance = new ChannelOpenHandler(); + + public static ChannelOpenHandler getInstance() + { + return _instance; + } + + private ChannelOpenHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + + final AMQChannel channel = new AMQChannel(session,channelId, virtualHost.getMessageStore() + ); + session.addChannel(channel); + + ChannelOpenOkBody response; + + ProtocolVersion pv = session.getProtocolVersion(); + + if(pv.equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0); + response = methodRegistry.createChannelOpenOkBody(); + + } + else if(pv.equals(ProtocolVersion.v0_9)) + { + MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); + UUID uuid = UUID.randomUUID(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(output); + try + { + dataOut.writeLong(uuid.getMostSignificantBits()); + dataOut.writeLong(uuid.getLeastSignificantBits()); + dataOut.flush(); + dataOut.close(); + } + catch (IOException e) + { + // This *really* shouldn't happen as we're not doing any I/O + throw new RuntimeException("I/O exception when writing to byte array", e); + } + + // should really associate this channelId to the session + byte[] channelName = output.toByteArray(); + + response = methodRegistry.createChannelOpenOkBody(channelName); + } + else + { + throw new AMQException(AMQConstant.INTERNAL_ERROR, "Got channel open for protocol version not catered for: " + pv, null); + } + + + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..dade5d5f54 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class); + + private static ConnectionCloseMethodHandler _instance = new ConnectionCloseMethodHandler(); + + public static ConnectionCloseMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) + { + _logger.info("ConnectionClose received with reply code/reply text " + body.getReplyCode() + "/" + + body.getReplyText() + " for " + session); + } + try + { + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ConnectionCloseOkBody responseBody = methodRegistry.createConnectionCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java new file mode 100644 index 0000000000..bc6e5ab403 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java @@ -0,0 +1,63 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionCloseOkMethodHandler implements StateAwareMethodListener<ConnectionCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseOkMethodHandler.class); + + private static ConnectionCloseOkMethodHandler _instance = new ConnectionCloseOkMethodHandler(); + + public static ConnectionCloseOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionCloseOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + //todo should this not do more than just log the method? + _logger.info("Received Connection-close-ok"); + + try + { + stateManager.changeState(AMQState.CONNECTION_CLOSED); + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java new file mode 100644 index 0000000000..824f084f57 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java @@ -0,0 +1,102 @@ +/* + * + * 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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.log4j.Logger; + +public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionOpenMethodHandler.class); + + private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler(); + + public static ConnectionOpenMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenMethodHandler() + { + } + + private static AMQShortString generateClientID() + { + return new AMQShortString(Long.toString(System.currentTimeMillis())); + } + + public void methodReceived(AMQStateManager stateManager, ConnectionOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + + //ignore leading '/' + String virtualHostName; + if ((body.getVirtualHost() != null) && body.getVirtualHost().charAt(0) == '/') + { + virtualHostName = new StringBuilder(body.getVirtualHost().subSequence(1, body.getVirtualHost().length())).toString(); + } + else + { + virtualHostName = body.getVirtualHost() == null ? null : String.valueOf(body.getVirtualHost()); + } + + VirtualHost virtualHost = stateManager.getVirtualHostRegistry().getVirtualHost(virtualHostName); + + if (virtualHost == null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, "Unknown virtual host: '" + virtualHostName + "'"); + } + else + { + session.setVirtualHost(virtualHost); + + //Perform ACL + if (!virtualHost.getAccessManager().authoriseConnect(session, virtualHost)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + // See Spec (0.8.2). Section 3.1.2 Virtual Hosts + if (session.getContextKey() == null) + { + session.setContextKey(generateClientID()); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionOpenOkBody(body.getVirtualHost()); + + stateManager.changeState(AMQState.CONNECTION_OPEN); + + session.writeFrame(responseBody.generateFrame(channelId)); + + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java new file mode 100644 index 0000000000..a2a6faf21b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java @@ -0,0 +1,126 @@ +/* + * + * 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.handler; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionSecureOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener<ConnectionSecureOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionSecureOkMethodHandler.class); + + private static ConnectionSecureOkMethodHandler _instance = new ConnectionSecureOkMethodHandler(); + + public static ConnectionSecureOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionSecureOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionSecureOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + + SaslServer ss = session.getSaslServer(); + if (ss == null) + { + throw new AMQException("No SASL context set up in session"); + } + MethodRegistry methodRegistry = session.getMethodRegistry(); + AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + switch (authResult.status) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + // This should be abstracted + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody connectionCloseBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(connectionCloseBody.generateFrame(0)); + disposeSaslServer(session); + break; + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = + methodRegistry.createConnectionTuneBody(0xFFFF, + ConnectionStartOkMethodHandler.getConfiguredFrameSize(), + ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay()); + session.writeFrame(tuneBody.generateFrame(0)); + session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID())); + disposeSaslServer(session); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge); + session.writeFrame(secureBody.generateFrame(0)); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java new file mode 100644 index 0000000000..6698ae888a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java @@ -0,0 +1,163 @@ +/* + * + * 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.handler; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + + +public class ConnectionStartOkMethodHandler implements StateAwareMethodListener<ConnectionStartOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class); + + private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler(); + + public static ConnectionStartOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionStartOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionStartOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.info("SASL Mechanism selected: " + body.getMechanism()); + _logger.info("Locale selected: " + body.getLocale()); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager();//session.getVirtualHost().getAuthenticationManager(); + + SaslServer ss = null; + try + { + ss = authMgr.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN()); + + if (ss == null) + { + throw body.getConnectionException(AMQConstant.RESOURCE_ERROR, "Unable to create SASL Server:" + body.getMechanism() + ); + } + + session.setSaslServer(ss); + + AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + + //save clientProperties + if (session.getClientProperties() == null) + { + session.setClientProperties(body.getClientProperties()); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + + switch (authResult.status) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody closeBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(closeBody.generateFrame(0)); + disposeSaslServer(session); + break; + + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID())); + + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody(0xFFFF, + getConfiguredFrameSize(), + ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay()); + session.writeFrame(tuneBody.generateFrame(0)); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge); + session.writeFrame(secureBody.generateFrame(0)); + } + } + catch (SaslException e) + { + disposeSaslServer(session); + throw new AMQException("SASL error: " + e, e); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } + + static int getConfiguredFrameSize() + { + final ServerConfiguration config = ApplicationRegistry.getInstance().getConfiguration(); + final int framesize = config.getFrameSize(); + _logger.info("Framesize set to " + framesize); + return framesize; + } +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java new file mode 100644 index 0000000000..0fe8c5dc92 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java @@ -0,0 +1,54 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionTuneOkMethodHandler implements StateAwareMethodListener<ConnectionTuneOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionTuneOkMethodHandler.class); + + private static ConnectionTuneOkMethodHandler _instance = new ConnectionTuneOkMethodHandler(); + + public static ConnectionTuneOkMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, ConnectionTuneOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isDebugEnabled()) + { + _logger.debug(body); + } + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + session.initHeartbeats(body.getHeartbeat()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java new file mode 100644 index 0000000000..ccd42204d9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java @@ -0,0 +1,178 @@ +/* + * + * 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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * @author Apache Software Foundation + * + * + */ +public class ExchangeBoundHandler implements StateAwareMethodListener<ExchangeBoundBody> +{ + private static final ExchangeBoundHandler _instance = new ExchangeBoundHandler(); + + public static final int OK = 0; + + public static final int EXCHANGE_NOT_FOUND = 1; + + public static final int QUEUE_NOT_FOUND = 2; + + public static final int NO_BINDINGS = 3; + + public static final int QUEUE_NOT_BOUND = 4; + + public static final int NO_QUEUE_BOUND_WITH_RK = 5; + + public static final int SPECIFIC_QUEUE_NOT_BOUND_WITH_RK = 6; + + public static ExchangeBoundHandler getInstance() + { + return _instance; + } + + private ExchangeBoundHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeBoundBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + VirtualHost virtualHost = session.getVirtualHost(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + MethodRegistry methodRegistry = session.getMethodRegistry(); + + + + + AMQShortString exchangeName = body.getExchange(); + AMQShortString queueName = body.getQueue(); + AMQShortString routingKey = body.getRoutingKey(); + if (exchangeName == null) + { + throw new AMQException("Exchange exchange must not be null"); + } + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + ExchangeBoundOkBody response; + if (exchange == null) + { + + + response = methodRegistry.createExchangeBoundOkBody(EXCHANGE_NOT_FOUND, + new AMQShortString("Exchange " + exchangeName + " not found")); + } + else if (routingKey == null) + { + if (queueName == null) + { + if (exchange.hasBindings()) + { + response = methodRegistry.createExchangeBoundOkBody(OK, null); + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_BINDINGS, // replyCode + null); // replyText + } + } + else + { + + AMQQueue queue = queueRegistry.getQueue(queueName); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + if (exchange.isBound(queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_BOUND, // replyCode + new AMQShortString("Queue " + queueName + " not bound to exchange " + exchangeName)); // replyText + } + } + } + } + else if (queueName != null) + { + AMQQueue queue = queueRegistry.getQueue(queueName); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + if (exchange.isBound(body.getRoutingKey(), queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(SPECIFIC_QUEUE_NOT_BOUND_WITH_RK, // replyCode + new AMQShortString("Queue " + queueName + " not bound with routing key " + + body.getRoutingKey() + " to exchange " + exchangeName)); // replyText + } + } + } + else + { + if (exchange.isBound(body.getRoutingKey())) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_QUEUE_BOUND_WITH_RK, // replyCode + new AMQShortString("No queue bound with routing key " + body.getRoutingKey() + + " to exchange " + exchangeName)); // replyText + } + } + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java new file mode 100644 index 0000000000..ba60808492 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -0,0 +1,122 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ExchangeDeclareHandler implements StateAwareMethodListener<ExchangeDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(ExchangeDeclareHandler.class); + + private static final ExchangeDeclareHandler _instance = new ExchangeDeclareHandler(); + + public static ExchangeDeclareHandler getInstance() + { + return _instance; + } + + + + private ExchangeDeclareHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeDeclareBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + ExchangeFactory exchangeFactory = virtualHost.getExchangeFactory(); + + if (!body.getPassive()) + { + // Perform ACL if request is not passive + if (!virtualHost.getAccessManager().authoriseCreateExchange(session, body.getAutoDelete(), + body.getDurable(), body.getExchange(), body.getInternal(), body.getNowait(), body.getPassive(), + body.getType())) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Request to declare exchange of type " + body.getType() + " with name " + body.getExchange()); + } + synchronized(exchangeRegistry) + { + Exchange exchange = exchangeRegistry.getExchange(body.getExchange()); + + + + if (exchange == null) + { + if(body.getPassive() && ((body.getType() == null) || body.getType().length() ==0)) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange: " + body.getExchange()); + } + else + { + try + { + + exchange = exchangeFactory.createExchange(body.getExchange() == null ? null : body.getExchange().intern(), + body.getType() == null ? null : body.getType().intern(), + body.getDurable(), + body.getPassive(), body.getTicket()); + exchangeRegistry.registerExchange(exchange); + } + catch(AMQUnknownExchangeType e) + { + throw body.getConnectionException(AMQConstant.COMMAND_INVALID, "Unknown exchange: " + body.getExchange(),e); + } + } + } + else if (!exchange.getType().equals(body.getType())) + { + + throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + body.getExchange() + " of type " + exchange.getType() + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor(),null); + } + + } + if(!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createExchangeDeclareOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java new file mode 100644 index 0000000000..bd4b610933 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ExchangeDeleteBody; +import org.apache.qpid.framing.ExchangeDeleteOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ExchangeDeleteHandler implements StateAwareMethodListener<ExchangeDeleteBody> +{ + private static final ExchangeDeleteHandler _instance = new ExchangeDeleteHandler(); + + public static ExchangeDeleteHandler getInstance() + { + return _instance; + } + + private ExchangeDeleteHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + + //Perform ACLs + if (!virtualHost.getAccessManager().authoriseDelete(session, + exchangeRegistry.getExchange(body.getExchange()))) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + try + { + exchangeRegistry.unregisterExchange(body.getExchange(), body.getIfUnused()); + + ExchangeDeleteOkBody responseBody = session.getMethodRegistry().createExchangeDeleteOkBody(); + + session.writeFrame(responseBody.generateFrame(channelId)); + } + catch (ExchangeInUseException e) + { + // TODO: sort out consistent channel close mechanism that does all clean up etc. + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java new file mode 100644 index 0000000000..ac516b6133 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java @@ -0,0 +1,34 @@ +/* + * + * 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.handler; + +import java.util.concurrent.Executor; + +/** + * An executor that executes the task on the current thread. + */ +public class OnCurrentThreadExecutor implements Executor +{ + public void execute(Runnable command) + { + command.run(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java new file mode 100644 index 0000000000..84491c1d2e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java @@ -0,0 +1,143 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidRoutingKeyException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueBindHandler implements StateAwareMethodListener<QueueBindBody> +{ + private static final Logger _log = Logger.getLogger(QueueBindHandler.class); + + private static final QueueBindHandler _instance = new QueueBindHandler(); + + public static QueueBindHandler getInstance() + { + return _instance; + } + + private QueueBindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueBindBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + + final AMQQueue queue; + final AMQShortString routingKey; + + if (body.getQueue() == null) + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + if (body.getRoutingKey() == null) + { + routingKey = queue.getName(); + } + else + { + routingKey = body.getRoutingKey().intern(); + } + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + routingKey = body.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : body.getRoutingKey().intern(); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.getExchange()); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist."); + } + + + try + { + + //Perform ACLs + if (!virtualHost.getAccessManager().authoriseBind(session, exch, + queue, routingKey)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + if (!exch.isBound(routingKey, body.getArguments(), queue)) + { + queue.bind(exch, routingKey, body.getArguments()); + } + } + catch (AMQInvalidRoutingKeyException rke) + { + throw body.getChannelException(AMQConstant.INVALID_ROUTING_KEY, routingKey.toString()); + } + catch (AMQException e) + { + throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString()); + } + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java new file mode 100644 index 0000000000..b705ea7dba --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java @@ -0,0 +1,203 @@ +/* + * + * 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.handler; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.QueueDeclareOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(QueueDeclareHandler.class); + + private static final QueueDeclareHandler _instance = new QueueDeclareHandler(); + + public static QueueDeclareHandler getInstance() + { + return _instance; + } + + public boolean autoRegister = ApplicationRegistry.getInstance().getConfiguration().getQueueAutoRegister(); + + private final AtomicInteger _counter = new AtomicInteger(); + + public void methodReceived(AMQStateManager stateManager, QueueDeclareBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + MessageStore store = virtualHost.getMessageStore(); + + + if (!body.getPassive()) + { + // Perform ACL if request is not passive + if (!virtualHost.getAccessManager().authoriseCreateQueue(session, body.getAutoDelete(), body.getDurable(), + body.getExclusive(), body.getNowait(), body.getPassive(), body.getQueue())) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + } + + final AMQShortString queueName; + + // if we aren't given a queue name, we create one which we return to the client + + if ((body.getQueue() == null) || (body.getQueue().length() == 0)) + { + queueName = createName(); + } + else + { + queueName = body.getQueue().intern(); + } + + AMQQueue queue; + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + synchronized (queueRegistry) + { + + + + if (((queue = queueRegistry.getQueue(queueName)) == null)) + { + + if (body.getPassive()) + { + String msg = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ")."; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + queue = createQueue(queueName, body, virtualHost, session); + if (queue.isDurable() && !queue.isAutoDelete()) + { + store.createQueue(queue, body.getArguments()); + } + queueRegistry.registerQueue(queue); + if (autoRegister) + { + Exchange defaultExchange = exchangeRegistry.getDefaultExchange(); + + queue.bind(defaultExchange, queueName, null); + _logger.info("Queue " + queueName + " bound to default exchange(" + defaultExchange.getName() + ")"); + } + } + } + else if (queue.getOwner() != null && !session.getContextKey().equals(queue.getOwner())) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, "Cannot declare queue('" + queueName + "')," + + " as exclusive queue with same name " + + "declared on another client ID('" + + queue.getOwner() + "')"); + } + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //set this as the default queue on the channel: + channel.setDefaultQueue(queue); + } + + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + QueueDeclareOkBody responseBody = + methodRegistry.createQueueDeclareOkBody(queueName, + queue.getMessageCount(), + queue.getConsumerCount()); + session.writeFrame(responseBody.generateFrame(channelId)); + + _logger.info("Queue " + queueName + " declared successfully"); + } + } + + protected AMQShortString createName() + { + return new AMQShortString("tmp_" + UUID.randomUUID()); + } + + protected AMQQueue createQueue(final AMQShortString queueName, + QueueDeclareBody body, + VirtualHost virtualHost, + final AMQProtocolSession session) + throws AMQException + { + final QueueRegistry registry = virtualHost.getQueueRegistry(); + AMQShortString owner = body.getExclusive() ? session.getContextKey() : null; + + final AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueName, body.getDurable(), owner, body.getAutoDelete(), virtualHost, + body.getArguments()); + + + if (body.getExclusive() && !body.getDurable()) + { + final AMQProtocolSession.Task deleteQueueTask = + new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + if (registry.getQueue(queueName) == queue) + { + queue.delete(); + } + } + }; + + session.addSessionCloseTask(deleteQueueTask); + + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) + { + session.removeSessionCloseTask(deleteQueueTask); + } + }); + }// if exclusive and not durable + + return queue; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java new file mode 100644 index 0000000000..b397db9246 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.QueueDeleteBody; +import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.security.access.Permission; + +public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody> +{ + private static final QueueDeleteHandler _instance = new QueueDeleteHandler(); + + public static QueueDeleteHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + + public QueueDeleteHandler() + { + this(true); + } + + public QueueDeleteHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + + } + + public void methodReceived(AMQStateManager stateManager, QueueDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + MessageStore store = virtualHost.getMessageStore(); + + AMQQueue queue; + if (body.getQueue() == null) + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //get the default queue on the channel: + queue = channel.getDefaultQueue(); + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + } + + if (queue == null) + { + if (_failIfNotFound) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + } + else + { + if (body.getIfEmpty() && !queue.isEmpty()) + { + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is not empty."); + } + else if (body.getIfUnused() && !queue.isUnused()) + { + // TODO - Error code + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is still used."); + + } + else + { + + //Perform ACLs + if (!virtualHost.getAccessManager().authoriseDelete(session, queue)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + int purged = queue.delete(); + + + if (queue.isDurable()) + { + store.removeQueue(queue); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + QueueDeleteOkBody responseBody = methodRegistry.createQueueDeleteOkBody(purged); + session.writeFrame(responseBody.generateFrame(channelId)); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java new file mode 100644 index 0000000000..2768518f53 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java @@ -0,0 +1,122 @@ +/*
+ * 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.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.QueuePurgeBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.QueueRegistry;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.virtualhost.VirtualHost;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.security.access.Permission;
+
+public class QueuePurgeHandler implements StateAwareMethodListener<QueuePurgeBody>
+{
+ private static final QueuePurgeHandler _instance = new QueuePurgeHandler();
+
+ public static QueuePurgeHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private final boolean _failIfNotFound;
+
+ public QueuePurgeHandler()
+ {
+ this(true);
+ }
+
+ public QueuePurgeHandler(boolean failIfNotFound)
+ {
+ _failIfNotFound = failIfNotFound;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, QueuePurgeBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+ VirtualHost virtualHost = session.getVirtualHost();
+ QueueRegistry queueRegistry = virtualHost.getQueueRegistry();
+
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ AMQQueue queue;
+ if(body.getQueue() == null)
+ {
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ //get the default queue on the channel:
+ queue = channel.getDefaultQueue();
+
+ if(queue == null)
+ {
+ if(_failIfNotFound)
+ {
+ throw body.getConnectionException(AMQConstant.NOT_ALLOWED,"No queue specified.");
+ }
+ }
+ }
+ else
+ {
+ queue = queueRegistry.getQueue(body.getQueue());
+ }
+
+ if(queue == null)
+ {
+ if(_failIfNotFound)
+ {
+ throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist.");
+ }
+ }
+ else
+ {
+
+ //Perform ACLs
+ if (!virtualHost.getAccessManager().authorisePurge(session, queue))
+ {
+ throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied");
+ }
+
+ long purged = queue.clearQueue(channel.getStoreContext());
+
+
+ if(!body.getNowait())
+ {
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+ AMQMethodBody responseBody = methodRegistry.createQueuePurgeOkBody(purged);
+ session.writeFrame(responseBody.generateFrame(channelId));
+
+ }
+ }
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java new file mode 100644 index 0000000000..d4272239d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java @@ -0,0 +1,137 @@ +package org.apache.qpid.server.handler; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +import org.apache.log4j.Logger; + +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidRoutingKeyException; +import org.apache.qpid.protocol.AMQConstant; + +public class QueueUnbindHandler implements StateAwareMethodListener<QueueUnbindBody> +{ + private static final Logger _log = Logger.getLogger(QueueUnbindHandler.class); + + private static final QueueUnbindHandler _instance = new QueueUnbindHandler(); + + public static QueueUnbindHandler getInstance() + { + return _instance; + } + + private QueueUnbindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueUnbindBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + + final AMQQueue queue; + final AMQShortString routingKey; + + if (body.getQueue() == null) + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(); + + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(); + } + + if (queue == null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.getExchange()); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist."); + } + + //Perform ACLs + if (!virtualHost.getAccessManager().authoriseUnbind(session, exch, routingKey, queue)) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied"); + } + + try + { + queue.unBind(exch, routingKey, body.getArguments()); + } + catch (AMQInvalidRoutingKeyException rke) + { + throw body.getChannelException(AMQConstant.INVALID_ROUTING_KEY, routingKey.toString()); + } + catch (AMQException e) + { + if(e.getErrorCode() == AMQConstant.NOT_FOUND) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND,e.getMessage(),e); + } + throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString()); + } + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + + MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueUnbindOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java new file mode 100644 index 0000000000..9475b83c8f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java @@ -0,0 +1,566 @@ +/*
+ *
+ * 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.handler;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl implements MethodDispatcher
+{
+ private final AMQStateManager _stateManager;
+
+ private static interface DispatcherFactory
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_8_0(stateManager);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_9(stateManager);
+ }
+ });
+
+ }
+
+
+ private static final AccessRequestHandler _accessRequestHandler = AccessRequestHandler.getInstance();
+ private static final ChannelCloseHandler _channelCloseHandler = ChannelCloseHandler.getInstance();
+ private static final ChannelOpenHandler _channelOpenHandler = ChannelOpenHandler.getInstance();
+ private static final ChannelCloseOkHandler _channelCloseOkHandler = ChannelCloseOkHandler.getInstance();
+ private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance();
+ private static final ConnectionCloseOkMethodHandler _connectionCloseOkMethodHandler = ConnectionCloseOkMethodHandler.getInstance();
+ private static final ConnectionOpenMethodHandler _connectionOpenMethodHandler = ConnectionOpenMethodHandler.getInstance();
+ private static final ConnectionTuneOkMethodHandler _connectionTuneOkMethodHandler = ConnectionTuneOkMethodHandler.getInstance();
+ private static final ConnectionSecureOkMethodHandler _connectionSecureOkMethodHandler = ConnectionSecureOkMethodHandler.getInstance();
+ private static final ConnectionStartOkMethodHandler _connectionStartOkMethodHandler = ConnectionStartOkMethodHandler.getInstance();
+ private static final ExchangeDeclareHandler _exchangeDeclareHandler = ExchangeDeclareHandler.getInstance();
+ private static final ExchangeDeleteHandler _exchangeDeleteHandler = ExchangeDeleteHandler.getInstance();
+ private static final ExchangeBoundHandler _exchangeBoundHandler = ExchangeBoundHandler.getInstance();
+ private static final BasicAckMethodHandler _basicAckMethodHandler = BasicAckMethodHandler.getInstance();
+ private static final BasicRecoverMethodHandler _basicRecoverMethodHandler = BasicRecoverMethodHandler.getInstance();
+ private static final BasicConsumeMethodHandler _basicConsumeMethodHandler = BasicConsumeMethodHandler.getInstance();
+ private static final BasicGetMethodHandler _basicGetMethodHandler = BasicGetMethodHandler.getInstance();
+ private static final BasicCancelMethodHandler _basicCancelMethodHandler = BasicCancelMethodHandler.getInstance();
+ private static final BasicPublishMethodHandler _basicPublishMethodHandler = BasicPublishMethodHandler.getInstance();
+ private static final BasicQosHandler _basicQosHandler = BasicQosHandler.getInstance();
+ private static final QueueBindHandler _queueBindHandler = QueueBindHandler.getInstance();
+ private static final QueueDeclareHandler _queueDeclareHandler = QueueDeclareHandler.getInstance();
+ private static final QueueDeleteHandler _queueDeleteHandler = QueueDeleteHandler.getInstance();
+ private static final QueuePurgeHandler _queuePurgeHandler = QueuePurgeHandler.getInstance();
+ private static final ChannelFlowHandler _channelFlowHandler = ChannelFlowHandler.getInstance();
+ private static final TxSelectHandler _txSelectHandler = TxSelectHandler.getInstance();
+ private static final TxCommitHandler _txCommitHandler = TxCommitHandler.getInstance();
+ private static final TxRollbackHandler _txRollbackHandler = TxRollbackHandler.getInstance();
+ private static final BasicRejectMethodHandler _basicRejectMethodHandler = BasicRejectMethodHandler.getInstance();
+
+
+
+ public static MethodDispatcher createMethodDispatcher(AMQStateManager stateManager, ProtocolVersion protocolVersion)
+ {
+ return _dispatcherFactories.get(protocolVersion).createMethodDispatcher(stateManager);
+ }
+
+
+ public ServerMethodDispatcherImpl(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+
+ protected AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+
+
+ public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException
+ {
+ _accessRequestHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException
+ {
+ _basicAckMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException
+ {
+ _basicCancelMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException
+ {
+ _basicConsumeMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException
+ {
+ _basicGetMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException
+ {
+ _basicPublishMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException
+ {
+ _basicQosHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException
+ {
+ _basicRecoverMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException
+ {
+ _basicRejectMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException
+ {
+ _channelOpenHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ _channelCloseOkHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ _channelFlowHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ _connectionOpenMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ _connectionCloseMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ _connectionCloseOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ _connectionSecureOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ _connectionStartOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ _connectionTuneOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ _exchangeBoundHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ _exchangeDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ _exchangeDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException
+ {
+ _queueBindHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException
+ {
+ _queueDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException
+ {
+ _queueDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException
+ {
+ _queuePurgeHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException
+ {
+ _txCommitHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException
+ {
+ _txRollbackHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException
+ {
+ _txSelectHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java new file mode 100644 index 0000000000..8b1dca77ba --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java @@ -0,0 +1,164 @@ +/*
+ *
+ * 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.handler;
+
+
+import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+
+
+public class ServerMethodDispatcherImpl_0_9
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_9
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_9(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java new file mode 100644 index 0000000000..d599ca3d4e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java @@ -0,0 +1,86 @@ +/*
+ *
+ * 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.handler;
+
+import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl_8_0
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_8_0
+{
+ public ServerMethodDispatcherImpl_8_0(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java new file mode 100644 index 0000000000..9b23d88838 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java @@ -0,0 +1,78 @@ +/* + * + * 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.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxCommitBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody> +{ + private static final Logger _log = Logger.getLogger(TxCommitHandler.class); + + private static TxCommitHandler _instance = new TxCommitHandler(); + + public static TxCommitHandler getInstance() + { + return _instance; + } + + private TxCommitHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxCommitBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + if (_log.isDebugEnabled()) + { + _log.debug("Commit received on channel " + channelId); + } + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.commit(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createTxCommitOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + channel.processReturns(); + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java new file mode 100644 index 0000000000..5f402f3fda --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxRollbackBody; +import org.apache.qpid.framing.TxRollbackOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxRollbackHandler implements StateAwareMethodListener<TxRollbackBody> +{ + private static TxRollbackHandler _instance = new TxRollbackHandler(); + + public static TxRollbackHandler getInstance() + { + return _instance; + } + + private TxRollbackHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxRollbackBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.rollback(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createTxRollbackOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + + //Now resend all the unacknowledged messages back to the original subscribers. + //(Must be done after the TxnRollback-ok response). + // Why, are we not allowed to send messages back to client before the ok method? + channel.resend(false); + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java new file mode 100644 index 0000000000..308f5b73cf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java @@ -0,0 +1,63 @@ +/* + * + * 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.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxSelectBody; +import org.apache.qpid.framing.TxSelectOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class TxSelectHandler implements StateAwareMethodListener<TxSelectBody> +{ + private static TxSelectHandler _instance = new TxSelectHandler(); + + public static TxSelectHandler getInstance() + { + return _instance; + } + + private TxSelectHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxSelectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setLocalTransactional(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + TxSelectOkBody responseBody = methodRegistry.createTxSelectOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java new file mode 100644 index 0000000000..fb18519fe1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java @@ -0,0 +1,33 @@ +/*
+ *
+ * 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.handler;
+
+
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.AMQException;
+
+public class UnexpectedMethodException extends AMQException
+{
+ public UnexpectedMethodException(AMQMethodBody body)
+ {
+ super("Unexpected method recevied: " + body.getClass().getName());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java new file mode 100644 index 0000000000..f723ab206c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagement.java @@ -0,0 +1,136 @@ +/* + * 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.logging.management; + +import java.io.IOException; + +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; + +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.TabularData; + +public interface LoggingManagement +{ + String TYPE = "LoggingManagement"; + int VERSION = 1; + + //TabularType and contained CompositeType key/description information + String[] COMPOSITE_ITEM_NAMES = {"LoggerName", "Level"}; + String[] COMPOSITE_ITEM_DESCRIPTIONS = {"Name of the logger", "Level of the logger"}; + String[] TABULAR_UNIQUE_INDEX = {COMPOSITE_ITEM_NAMES[0]}; + + /** + * Attribute to represent the log4j xml configuration file's LogWatch interval. + * @return The logwatch interval in seconds. + */ + @MBeanAttribute(name="Log4jLogWatchInterval", + description = "The log4j xml configuration file LogWatch interval (in seconds). 0 indicates not being checked.") + Integer getLog4jLogWatchInterval(); + + /** + * Attribute to represent the available log4j logger output levels. + * @return The logging level names. + */ + @MBeanAttribute(name="AvailableLoggerLevels", description = "The values to which log output level can be set.") + String[] getAvailableLoggerLevels(); + + + //****** log4j runtime operations ****** // + + /** + * Sets the level of an active Log4J logger + * @param logger The name of the logger + * @param level The level to set the logger to + * @return True if successful, false if unsuccessful (eg if an invalid level is specified) + */ + @MBeanOperation(name = "setRuntimeLoggerLevel", description = "Set the runtime logging level for an active log4j logger.", + impact = MBeanOperationInfo.ACTION) + boolean setRuntimeLoggerLevel(@MBeanOperationParameter(name = "logger", description = "Logger name")String logger, + @MBeanOperationParameter(name = "level", description = "Logger level")String level); + + /** + * Retrieves a TabularData set of the active log4j loggers and their levels + * @return TabularData set of CompositeData rows with logger name and level, or null if there is a problem with the TabularData type + */ + @MBeanOperation(name = "viewEffectiveRuntimeLoggerLevels", description = "View the effective runtime logging level " + + "for active log4j logger's.", impact = MBeanOperationInfo.INFO) + TabularData viewEffectiveRuntimeLoggerLevels(); + + /** + * Sets the level of the active Log4J RootLogger + * @param level The level to set the RootLogger to + * @return True if successful, false if unsuccessful (eg if an invalid level is specified) + */ + @MBeanOperation(name = "setRuntimeRootLoggerLevel", description = "Set the runtime logging level for the active log4j Root Logger.", + impact = MBeanOperationInfo.ACTION) + boolean setRuntimeRootLoggerLevel(@MBeanOperationParameter(name = "level", description = "Logger level")String level); + + /** + * Attribute to represent the level of the active Log4J RootLogger + * @return The level of the RootLogger. + */ + @MBeanAttribute(name = "getRuntimeRootLoggerLevel", description = "Get the runtime logging level for the active log4j Root Logger.") + String getRuntimeRootLoggerLevel(); + + + //****** log4j XML configuration file operations ****** // + + /** + * Updates the level of an existing Log4J logger within the xml configuration file + * @param logger The name of the logger + * @param level The level to set the logger to + * @return True if successful, false if unsuccessful (eg if an invalid logger or level is specified) + * @throws IOException if there is an error parsing the configuration file. + */ + @MBeanOperation(name = "setConfigFileLoggerLevel", description = "Set the logging level for an existing logger " + + "in the log4j xml configuration file", impact = MBeanOperationInfo.ACTION) + boolean setConfigFileLoggerLevel(@MBeanOperationParameter(name = "logger", description = "logger name")String logger, + @MBeanOperationParameter(name = "level", description = "Logger level")String level) throws IOException; + + /** + * Retrieves a TabularData set of the existing Log4J loggers within the xml configuration file + * @return TabularData set of CompositeData rows with logger name and level, or null if there is a problem with the TabularData type + * @throws IOException if there is an error parsing the configuration file. + */ + @MBeanOperation(name = "viewConfigFileLoggerLevels", description = "Get the logging level defined for the logger's " + + "in the log4j xml configuration file.", impact = MBeanOperationInfo.INFO) + TabularData viewConfigFileLoggerLevels() throws IOException; + + /** + * Updates the level of the Log4J RootLogger within the xml configuration file if it is present + * @param level The level to set the logger to + * @return True if successful, false if not (eg an invalid level is specified, or root logger level isnt already defined) + * @throws IOException if there is an error parsing the configuration file. + */ + @MBeanOperation(name = "setConfigFileRootLoggerLevel", description = "Set the logging level for the Root Logger " + + "in the log4j xml configuration file.", impact = MBeanOperationInfo.ACTION) + boolean setConfigFileRootLoggerLevel(@MBeanOperationParameter(name = "level", description = "Logger level")String level) throws IOException; + + /** + * Attribute to represent the level of the Log4J RootLogger within the xml configuration file + * @return The level of the RootLogger, or null if it is not present + */ + @MBeanAttribute(name = "getConfigFileRootLoggerLevel", description = "Get the logging level for the Root Logger " + + "in the log4j xml configuration file.") + String getConfigFileRootLoggerLevel() throws IOException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java new file mode 100644 index 0000000000..cd3f85f8ca --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java @@ -0,0 +1,674 @@ +/* + * 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.logging.management; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.AMQManagedObject; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.Log4jEntityResolver; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + + +/** MBean class for BrokerLoggingManagerMBean. It implements all the management features exposed for managing logging. */ +@MBeanDescription("Logging Management Interface") +public class LoggingManagementMBean extends AMQManagedObject implements LoggingManagement +{ + + private static final Logger _logger = Logger.getLogger(LoggingManagementMBean.class); + private String _log4jConfigFileName; + private int _log4jLogWatchInterval; + private static final String[] LEVELS = new String[]{Level.ALL.toString(), Level.TRACE.toString(), + Level.DEBUG.toString(), Level.INFO.toString(), + Level.WARN.toString(), Level.ERROR.toString(), + Level.FATAL.toString(),Level.OFF.toString()}; + static TabularType _loggerLevelTabularType; + static CompositeType _loggerLevelCompositeType; + + static + { + try + { + OpenType[] loggerLevelItemTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING}; + + _loggerLevelCompositeType = new CompositeType("LoggerLevelList", "Logger Level Data", + COMPOSITE_ITEM_NAMES, COMPOSITE_ITEM_DESCRIPTIONS, loggerLevelItemTypes); + + _loggerLevelTabularType = new TabularType("LoggerLevel", "List of loggers with levels", + _loggerLevelCompositeType, TABULAR_UNIQUE_INDEX); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing logger levels was incorrect."); + _loggerLevelTabularType = null; + } + } + + public LoggingManagementMBean(String log4jConfigFileName, int log4jLogWatchInterval) throws JMException + { + super(LoggingManagement.class, LoggingManagement.TYPE, LoggingManagement.VERSION); + _log4jConfigFileName = log4jConfigFileName; + _log4jLogWatchInterval = log4jLogWatchInterval; + } + + public String getObjectInstanceName() + { + return LoggingManagement.TYPE; + } + + public Integer getLog4jLogWatchInterval() + { + return _log4jLogWatchInterval; + } + + public String[] getAvailableLoggerLevels() + { + return LEVELS; + } + @SuppressWarnings("unchecked") + public synchronized boolean setRuntimeLoggerLevel(String logger, String level) + { + //check specified level is valid + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + //check specified logger exists + Enumeration loggers = LogManager.getCurrentLoggers(); + Boolean loggerExists = false; + + while(loggers.hasMoreElements()) + { + Logger log = (Logger) loggers.nextElement(); + if (log.getName().equals(logger)) + { + loggerExists = true; + break; + } + } + + if(!loggerExists) + { + return false; + } + + //set the logger to the new level + _logger.info("Setting level to " + level + " for logger: " + logger); + + Logger log = Logger.getLogger(logger); + log.setLevel(newLevel); + + return true; + } + + @SuppressWarnings("unchecked") + public synchronized TabularData viewEffectiveRuntimeLoggerLevels() + { + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting levels for currently active log4j loggers"); + + Enumeration loggers = LogManager.getCurrentLoggers(); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + Logger logger; + String loggerName; + String level; + + try + { + while(loggers.hasMoreElements()){ + logger = (Logger) loggers.nextElement(); + + loggerName = logger.getName(); + level = logger.getEffectiveLevel().toString(); + + Object[] itemData = {loggerName, level}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); + loggerLevelList.put(loggerData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + + return loggerLevelList; + + } + + public synchronized String getRuntimeRootLoggerLevel() + { + Logger rootLogger = Logger.getRootLogger(); + + return rootLogger.getLevel().toString(); + } + + public synchronized boolean setRuntimeRootLoggerLevel(String level) + { + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + _logger.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + + return true; + } + + //method to convert from a string to a log4j Level, throws exception if the given value is invalid + private Level getLevel(String level) throws Exception + { + Level newLevel = Level.toLevel(level); + + //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. + if (newLevel.equals(Level.DEBUG) && !(level.equalsIgnoreCase("debug"))) + { + //received DEBUG but we did not ask for it, the Level request failed. + throw new Exception("Invalid level name"); + } + + return newLevel; + } + + //handler to catch errors signalled by the JAXP parser and throw an appropriate exception + private class SaxErrorHandler implements ErrorHandler + { + + public void error(SAXParseException e) throws SAXException + { + throw new SAXException("Error parsing XML file: " + e.getMessage()); + } + + public void fatalError(SAXParseException e) throws SAXException + { + throw new SAXException("Fatal error parsing XML file: " + e.getMessage()); + } + + public void warning(SAXParseException e) throws SAXException + { + throw new SAXException("Warning parsing XML file: " + e.getMessage()); + } + } + + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private synchronized Document parseConfigFile(String fileName) throws IOException + { + //check file was specified, exists, and is readable + if(fileName == null) + { + _logger.warn("No log4j XML configuration file has been set"); + throw new IOException("No log4j XML configuration file has been set"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + _logger.warn("Specified log4j XML configuration file does not exist: " + fileName); + throw new IOException("Specified log4j XML configuration file does not exist"); + } + else if (!configFile.canRead()) + { + _logger.warn("Specified log4j XML configuration file is not readable: " + fileName); + throw new IOException("Specified log4j XML configuration file is not readable"); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; + + ErrorHandler errHandler = new SaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e); + //recommended that MBeans should use java.* and javax.* exceptions only + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The specified log4j XML file is invalid: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The specified log4j XML file is invalid: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("Unable to parse the specified log4j XML file" + e); + throw new IOException("Unable to parse the specified log4j XML file", e); + } + + return doc; + } + + + private synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException + { + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } + + Transformer transformer = null; + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + } + catch (Exception e) + { + _logger.warn("Could not create an XML transformer: " +e); + return false; + } + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + try + { + tmp = File.createTempFile("LogManMBeanTemp", ".tmp"); + tmp.deleteOnExit(); + StreamResult result = new StreamResult(tmp); + transformer.transform(source, result); + } + catch (TransformerException e) + { + _logger.warn("Could not transform the XML into new file: " +e); + return false; + } + catch (IOException e) + { + _logger.warn("Could not create the new file: " +e); + return false; + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + log4jConfigFile.renameTo(old); + return tmp.renameTo(log4jConfigFile); + } + + + /* The log4j XML configuration file DTD defines three possible element + * combinations for specifying optional logger+level settings. + * Must account for the following: + * + * <category name="x"> <priority value="y"/> </category> OR + * <category name="x"> <level value="y"/> </category> OR + * <logger name="x"> <level value="y"/> </logger> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + + public synchronized TabularData viewConfigFileLoggerLevels() throws IOException + { + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting logger levels from log4j configuration file"); + + Document doc = parseConfigFile(_log4jConfigFileName); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + //retrieve the 'category' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + + String categoryName; + String priority = null; + + for (int i = 0; i < categoryElements.getLength(); i++) + { + Element categoryElement = (Element) categoryElements.item(i); + categoryName = categoryElement.getAttribute("name"); + + //retrieve the category's mandatory 'priority' or 'level' element's value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryElement.getElementsByTagName("priority"); + NodeList levelElements = categoryElement.getElementsByTagName("level"); + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value").toUpperCase(); + } + else if (levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value").toUpperCase(); + } + else + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + try + { + Object[] itemData = {categoryName, priority}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); + loggerLevelList.put(loggerData); + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + } + + //retrieve the 'logger' element nodes + NodeList loggerElements = doc.getElementsByTagName("logger"); + + String loggerName; + String level; + + for (int i = 0; i < loggerElements.getLength(); i++) + { + Element loggerElement = (Element) loggerElements.item(i); + loggerName = loggerElement.getAttribute("name"); + + //retrieve the logger's mandatory 'level' element's value + //It may not be the only child node, so request by tag name. + NodeList levelElements = loggerElement.getElementsByTagName("level"); + + Element levelElement = (Element) levelElements.item(0); + level = levelElement.getAttribute("value").toUpperCase(); + + try + { + Object[] itemData = {loggerName, level}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, COMPOSITE_ITEM_NAMES, itemData); + loggerLevelList.put(loggerData); + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + } + + return loggerLevelList; + } + + public synchronized boolean setConfigFileLoggerLevel(String logger, String level) throws IOException + { + //check that the specified level is a valid log4j Level + try + { + getLevel(level); + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List<Element> logElements = new ArrayList<Element>(); + + for (int i = 0; i < categoryElements.getLength(); i++) + { + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); + } + + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } + + if (logElement == null) + { + //no loggers/categories with given name found, does not exist to update + _logger.warn("Specified logger does not exist in the configuration file: " +logger); + return false; + } + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = logElement.getElementsByTagName("priority"); + NodeList levelElements = logElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority or level element to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } + + + /* The log4j XML configuration file DTD defines 2 possible element + * combinations for specifying the optional root logger level settings + * Must account for the following: + * + * <root> <priority value="y"/> </root> OR + * <root> <level value="y"/> </root> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + public synchronized String getConfigFileRootLoggerLevel() throws IOException + { + _logger.info("Getting root logger level from log4j configuration file"); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + //there is not root logger definition + return null; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + String priority = null; + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if(levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + + if(priority != null) + { + return priority.toUpperCase(); + } + else + { + return null; + } + } + + public synchronized boolean setConfigFileRootLoggerLevel(String level) throws IOException + { + //check that the specified level is a valid log4j Level + try + { + getLevel(level); + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + return false; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority/level to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java new file mode 100644 index 0000000000..c6e07f6f48 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java @@ -0,0 +1,97 @@ +/* + * + * 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.management; + +import javax.management.ListenerNotFoundException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +/** + * This class provides additinal feature of Notification Broadcaster to the + * DefaultManagedObject. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public abstract class AMQManagedObject extends DefaultManagedObject + implements NotificationBroadcaster +{ + /** + * broadcaster support class + */ + protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport(); + + /** + * sequence number for notifications + */ + protected long _notificationSequenceNumber = 0; + + protected MBeanInfo _mbeanInfo; + + protected AMQManagedObject(Class<?> managementInterface, String typeName, int version) + throws NotCompliantMBeanException + { + super(managementInterface, typeName, version); + buildMBeanInfo(); + } + + @Override + public MBeanInfo getMBeanInfo() + { + return _mbeanInfo; + } + + private void buildMBeanInfo() throws NotCompliantMBeanException + { + _mbeanInfo = new MBeanInfo(this.getClass().getName(), + MBeanIntrospector.getMBeanDescription(this.getClass()), + MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()), + MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()), + MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()), + this.getNotificationInfo()); + } + + + + // notification broadcaster implementation + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, + Object handback) + { + _broadcaster.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException + { + _broadcaster.removeNotificationListener(listener); + } + + public MBeanNotificationInfo[] getNotificationInfo() + { + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java new file mode 100644 index 0000000000..67aee90ba4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java @@ -0,0 +1,200 @@ +/* + * + * 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.management; + +import javax.management.JMException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.StandardMBean; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +/** + * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful + * to extend this class rather than implementing ManagedObject from scratch. + * + */ +public abstract class DefaultManagedObject extends StandardMBean implements ManagedObject +{ + private Class<?> _managementInterface; + + private String _typeName; + private int _version; + + protected DefaultManagedObject(Class<?> managementInterface, String typeName, int version) + throws NotCompliantMBeanException + { + super(managementInterface); + _managementInterface = managementInterface; + _typeName = typeName; + _version = version; + } + + public String getType() + { + return _typeName; + } + + public Class<?> getManagementInterface() + { + return _managementInterface; + } + + public ManagedObject getParentObject() + { + return null; + } + + public void register() throws AMQException + { + try + { + getManagedObjectRegistry().registerObject(this); + } + catch (JMException e) + { + throw new AMQException("Error registering managed object " + this + ": " + e, e); + } + } + + protected ManagedObjectRegistry getManagedObjectRegistry() + { + return ApplicationRegistry.getInstance().getManagedObjectRegistry(); + } + + public void unregister() throws AMQException + { + try + { + getManagedObjectRegistry().unregisterObject(this); + } + catch (JMException e) + { + throw new AMQException("Error unregistering managed object: " + this + ": " + e, e); + } + } + + public String toString() + { + return getObjectInstanceName() + "[" + getType() + "]"; + } + + + /** + * Created the ObjectName as per the JMX Specs + * @return ObjectName + * @throws MalformedObjectNameException + */ + public ObjectName getObjectName() throws MalformedObjectNameException + { + String name = getObjectInstanceName(); + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + objectName.append(","); + objectName.append(getHierarchicalName(this)); + objectName.append("name=").append(name); + + objectName.append(","); + objectName.append("version=").append(_version); + + + return new ObjectName(objectName.toString()); + } + + protected ObjectName getObjectNameForSingleInstanceMBean() throws MalformedObjectNameException + { + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + String hierarchyName = getHierarchicalName(this); + if (hierarchyName != null) + { + objectName.append(","); + objectName.append(hierarchyName.substring(0, hierarchyName.lastIndexOf(","))); + } + + objectName.append(","); + objectName.append("version=").append(_version); + + return new ObjectName(objectName.toString()); + } + + protected String getHierarchicalType(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentType = getHierarchicalType(obj.getParentObject()).toString(); + return parentType + "." + obj.getType(); + } + else + return obj.getType(); + } + + protected String getHierarchicalName(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentName = obj.getParentObject().getType() + "=" + + obj.getParentObject().getObjectInstanceName() + ","+ + getHierarchicalName(obj.getParentObject()); + + return parentName; + } + else + return ""; + } + + protected static StringBuffer jmxEncode(StringBuffer jmxName, int attrPos) + { + for (int i = attrPos; i < jmxName.length(); i++) + { + if (jmxName.charAt(i) == ',') + { + jmxName.setCharAt(i, ';'); + } + else if (jmxName.charAt(i) == ':') + { + jmxName.setCharAt(i, '-'); + } + else if (jmxName.charAt(i) == '?' || + jmxName.charAt(i) == '*' || + jmxName.charAt(i) == '\\') + { + jmxName.insert(i, '\\'); + i++; + } + else if (jmxName.charAt(i) == '\n') + { + jmxName.insert(i, '\\'); + i++; + jmxName.setCharAt(i, 'n'); + } + } + return jmxName; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..5a113de5be --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -0,0 +1,371 @@ +/* + * + * 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.management; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.management.remote.rmi.RMIServerImpl; +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.rmi.AlreadyBoundException; +import java.rmi.RemoteException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.UnicastRemoteObject; +import java.util.HashMap; +import java.util.Map; + +/** + * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no + * security features implemented like user authentication and authorisation. + */ +public class JMXManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + private static final Logger _startupLog = Logger.getLogger("Qpid.Broker"); + + public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport"; + public static final int MANAGEMENT_PORT_DEFAULT = 8999; + public static final int PORT_EXPORT_OFFSET = 100; + + private final MBeanServer _mbeanServer; + private Registry _rmiRegistry; + + + public JMXManagedObjectRegistry() throws AMQException + { + _log.info("Initialising managed object registry using platform MBean server"); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + + // Retrieve the config parameters + boolean platformServer = appRegistry.getConfiguration().getPlatformMbeanserver(); + + _mbeanServer = + platformServer ? ManagementFactory.getPlatformMBeanServer() + : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN); + } + + + public void start() throws IOException, ConfigurationException + { + //check if system properties are set to use the JVM's out-of-the-box JMXAgent + if (areOutOfTheBoxJMXOptionsSet()) + { + _log.warn("JMX: Using the out of the box JMX Agent"); + _startupLog.warn("JMX: Using the out of the box JMX Agent"); + return; + } + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + int port = appRegistry.getConfiguration().getJMXManagementPort(); + + //retrieve the Principal Database assigned to JMX authentication duties + String jmxDatabaseName = appRegistry.getConfiguration().getJMXPrincipalDatabase(); + Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); + PrincipalDatabase db = map.get(jmxDatabaseName); + + final JMXConnectorServer cs; + HashMap<String,Object> env = new HashMap<String,Object>(); + + //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration + RMIClientSocketFactory csf; + RMIServerSocketFactory ssf; + + //check ssl enabled option in config, default to true if option is not set + boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled(); + + if (sslEnabled) + { + //set the SSL related system properties used by the SSL RMI socket factories to the values + //given in the configuration file, unless command line settings have already been specified + String keyStorePath; + + if(System.getProperty("javax.net.ssl.keyStore") != null) + { + keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + } + else + { + keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath(); + } + + //check the keystore path value is valid + if (keyStorePath == null) + { + throw new ConfigurationException("JMX management SSL keystore path not defined, " + + "unable to start SSL protected JMX ConnectorServer"); + } + else + { + //ensure the system property is set + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + + //check the file is usable + File ksf = new File(keyStorePath); + + if (!ksf.exists()) + { + throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf + "\n" + + "Check broker configuration, or see create-example-ssl-stores script" + + "in the bin/ directory if you need to generate an example store."); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " + + ksf + ". Check permissions."); + } + + _log.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath()); + _startupLog.info("JMX ConnectorServer using SSL keystore file " + ksf.getAbsolutePath()); + } + + //check the key store password is set + if (System.getProperty("javax.net.ssl.keyStorePassword") == null) + { + + if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null) + { + throw new ConfigurationException("JMX management SSL keystore password not defined, " + + "unable to start requested SSL protected JMX server"); + } + else + { + System.setProperty("javax.net.ssl.keyStorePassword", + appRegistry.getConfiguration().getManagementKeyStorePassword()); + } + } + + //create the SSL RMI socket factories + csf = new SslRMIClientSocketFactory(); + ssf = new SslRMIServerSocketFactory(); + + _log.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" + + (port +PORT_EXPORT_OFFSET) + ") with SSL"); + _startupLog.warn("Starting JMX ConnectorServer on port '"+ port + "' (+" + + (port +PORT_EXPORT_OFFSET) + ") with SSL"); + } + else + { + //Do not specify any specific RMI socket factories, resulting in use of the defaults. + csf = null; + ssf = null; + + _log.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")"); + _startupLog.warn("Starting JMX ConnectorServer on port '" + port + "' (+" + (port +PORT_EXPORT_OFFSET) + ")"); + } + + //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server + RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(); + rmipa.setPrincipalDatabase(db); + env.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + + /* + * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub. + * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI. + * As a result, only binds made using the object reference will succeed, thus securing it from external change. + */ + System.setProperty("java.rmi.server.randomIDs", "true"); + _rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory()); + + /* + * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls + * to bind the ConnectorServer to the registry, which will now fail as for security we have + * locked it from any RMI based modifications, including our own. Instead, we will manually bind + * the RMIConnectorServer stub to the registry using its object reference, which will still succeed. + * + * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer + * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's. + */ + final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env); + final String hostname = InetAddress.getLocalHost().getHostName(); + final JMXServiceURL externalUrl = new JMXServiceURL( + "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi"); + + final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET); + cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + { + @Override + public synchronized void start() throws IOException + { + try + { + //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent + _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub); + } + catch (AlreadyBoundException abe) + { + //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means. + + //IOExceptions are the only checked type throwable by the method, wrap and rethrow + IOException ioe = new IOException(abe.getMessage()); + ioe.initCause(abe); + throw ioe; + } + + //now do the normal tasks + super.start(); + } + + @Override + public JMXServiceURL getAddress() + { + //must return our pre-crafted url that includes the full details, inc JNDI details + return externalUrl; + } + + }; + + + //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + cs.setMBeanServerForwarder(mbsf); + cs.start(); + } + + /* + * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. + * Supplied to the registry at creation, this will prevent RMI-based operations on the + * registry such as attempting to bind a new object, thereby securing it from tampering. + * This is accomplished by always returning null when attempting to determine the address + * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc + * made using the object reference will not be affected and continue to operate normally. + */ + + private class CustomRMIServerSocketFactory implements RMIServerSocketFactory + { + + public ServerSocket createServerSocket(int port) throws IOException + { + return new NoLocalAddressServerSocket(port); + } + + private class NoLocalAddressServerSocket extends ServerSocket + { + NoLocalAddressServerSocket(int port) throws IOException + { + super(port); + } + + @Override + public Socket accept() throws IOException + { + Socket s = new NoLocalAddressSocket(); + super.implAccept(s); + return s; + } + } + + private class NoLocalAddressSocket extends Socket + { + @Override + public InetAddress getInetAddress() + { + return null; + } + } + } + + + public void registerObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.unregisterMBean(managedObject.getObjectName()); + } + + // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent. + private boolean areOutOfTheBoxJMXOptionsSet() + { + if (System.getProperty("com.sun.management.jmxremote") != null) + { + return true; + } + + if (System.getProperty("com.sun.management.jmxremote.port") != null) + { + return true; + } + + return false; + } + + // stops the RMIRegistry and unregisters the MBeans from the MBeanServer + public void close() throws RemoteException + { + if (_rmiRegistry != null) + { + // Stopping the RMI registry + UnicastRemoteObject.unexportObject(_rmiRegistry, true); + } + + //ObjectName query to gather all Qpid related MBeans + ObjectName mbeanNameQuery = null; + try + { + mbeanNameQuery = new ObjectName(ManagedObject.DOMAIN + ":*"); + } + catch (Exception e1) + { + _log.warn("Unable to generate MBean ObjectName query for close operation"); + } + + for (ObjectName name : _mbeanServer.queryNames(mbeanNameQuery, null)) + { + try + { + _mbeanServer.unregisterMBean(name); + } + catch (JMException e) + { + _log.error("Exception unregistering MBean '"+ name +"': " + e.getMessage()); + } + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java new file mode 100644 index 0000000000..7d42297699 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanAttribute.java @@ -0,0 +1,41 @@ +/* + * 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.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for MBean attributes. This should be used with getter or setter + * methods of attributes. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Inherited +public @interface MBeanAttribute +{ + String name(); + String description(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java new file mode 100644 index 0000000000..9138e03085 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanConstructor.java @@ -0,0 +1,39 @@ +/* + * 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.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for MBean constructors. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.CONSTRUCTOR) +@Inherited +public @interface MBeanConstructor +{ + String value(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java new file mode 100644 index 0000000000..448fed3280 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanDescription.java @@ -0,0 +1,38 @@ +/* + * 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.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for MBean class. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface MBeanDescription { + String value(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java new file mode 100644 index 0000000000..0c2ec2aebd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java @@ -0,0 +1,388 @@ +/* + * + * 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.management; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.NotCompliantMBeanException; + +/** + * This class is a utility class to introspect the MBean class and the management + * interface class for various purposes. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +class MBeanIntrospector { + + private static final String _defaultAttributeDescription = "Management attribute"; + private static final String _defaultOerationDescription = "Management operation"; + private static final String _defaultConstructorDescription = "MBean constructor"; + private static final String _defaultMbeanDescription = "Management interface of the MBean"; + + /** + * Introspects the management interface class for MBean attributes. + * @param interfaceClass + * @return MBeanAttributeInfo[] + * @throws NotCompliantMBeanException + */ + static MBeanAttributeInfo[] getMBeanAttributesInfo(Class interfaceClass) + throws NotCompliantMBeanException + { + List<MBeanAttributeInfo> attributesList = new ArrayList<MBeanAttributeInfo>(); + + /** + * Using reflection, all methods of the managemetn interface will be analysed, + * and MBeanInfo will be created. + */ + for (Method method : interfaceClass.getMethods()) + { + String name = method.getName(); + Class<?> resultType = method.getReturnType(); + MBeanAttributeInfo attributeInfo = null; + + if (isAttributeGetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + false, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeSetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + false, + true, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeBoolean(method)) + { + attributeInfo = new MBeanAttributeInfo(name.substring(2), + resultType.getName(), + getAttributeDescription(method), + true, + false, + true); + attributesList.add(attributeInfo); + } + } + + return attributesList.toArray(new MBeanAttributeInfo[0]); + } + + /** + * Introspects the management interface class for management operations. + * @param interfaceClass + * @return MBeanOperationInfo[] + */ + static MBeanOperationInfo[] getMBeanOperationsInfo(Class interfaceClass) + { + List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>(); + + for (Method method : interfaceClass.getMethods()) + { + if (!isAttributeGetterMethod(method) && + !isAttributeSetterMethod(method) && + !isAttributeBoolean(method)) + { + operationsList.add(getOperationInfo(method)); + } + } + + return operationsList.toArray(new MBeanOperationInfo[0]); + } + + /** + * Checks if the method is an attribute getter method. + * @param method + * @return true if the method is an attribute getter method. + */ + private static boolean isAttributeGetterMethod(Method method) + { + if (!(method.getName().equals("get")) && + method.getName().startsWith("get") && + method.getParameterTypes().length == 0 && + !method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the method is an attribute setter method. + * @param method + * @return true if the method is an attribute setter method. + */ + private static boolean isAttributeSetterMethod(Method method) + { + if (!(method.getName().equals("set")) && + method.getName().startsWith("set") && + method.getParameterTypes().length == 1 && + method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the attribute is a boolean and the method is a isX kind og method. + * @param method + * @return true if the method is an attribute isX type of method + */ + private static boolean isAttributeBoolean(Method method) + { + if (!(method.getName().equals("is")) && + method.getName().startsWith("is") && + method.getParameterTypes().length == 0 && + method.getReturnType().equals(boolean.class)) + { + return true; + } + + return false; + } + + /** + * Helper method to retrieve the attribute index from the list of attributes. + * @param attribute + * @param list + * @return attribute index no. -1 if attribtue doesn't exist + * @throws NotCompliantMBeanException + */ + private static int getIndexIfAlreadyExists(MBeanAttributeInfo attribute, + List<MBeanAttributeInfo> list) + throws NotCompliantMBeanException + { + String exceptionMsg = "Conflicting attribute methods for attribute " + attribute.getName(); + + for (MBeanAttributeInfo memberAttribute : list) + { + if (attribute.getName().equals(memberAttribute.getName())) + { + if (!attribute.getType().equals(memberAttribute.getType())) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + if (attribute.isReadable() && memberAttribute.isReadable()) + { + if (attribute.isIs() != memberAttribute.isIs()) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + } + + return list.indexOf(memberAttribute); + } + } + + return -1; + } + + /** + * Retrieves the attribute description from annotation + * @param attributeMethod + * @return attribute description + */ + private static String getAttributeDescription(Method attributeMethod) + { + MBeanAttribute anno = attributeMethod.getAnnotation(MBeanAttribute.class); + if (anno != null) + { + return anno.description(); + } + return _defaultAttributeDescription; + } + + /** + * Introspects the method to retrieve the operation information. + * @param operation + * @return MBeanOperationInfo + */ + private static MBeanOperationInfo getOperationInfo(Method operation) + { + MBeanOperationInfo operationInfo = null; + Class<?> returnType = operation.getReturnType(); + + MBeanParameterInfo[] paramsInfo = getParametersInfo(operation.getParameterAnnotations(), + operation.getParameterTypes()); + + String operationDesc = _defaultOerationDescription; + int impact = MBeanOperationInfo.UNKNOWN; + + if (operation.getAnnotation(MBeanOperation.class) != null) + { + operationDesc = operation.getAnnotation(MBeanOperation.class).description(); + impact = operation.getAnnotation(MBeanOperation.class).impact(); + } + operationInfo = new MBeanOperationInfo(operation.getName(), + operationDesc, + paramsInfo, + returnType.getName(), + impact); + + return operationInfo; + } + + /** + * Constructs the parameter info. + * @param paramsAnno + * @param paramTypes + * @return MBeanParameterInfo[] + */ + private static MBeanParameterInfo[] getParametersInfo(Annotation[][] paramsAnno, + Class<?>[] paramTypes) + { + int noOfParams = paramsAnno.length; + + MBeanParameterInfo[] paramsInfo = new MBeanParameterInfo[noOfParams]; + + for (int i = 0; i < noOfParams; i++) + { + MBeanParameterInfo paramInfo = null; + String type = paramTypes[i].getName(); + for (Annotation anno : paramsAnno[i]) + { + String name,desc; + if (MBeanOperationParameter.class.isInstance(anno)) + { + name = MBeanOperationParameter.class.cast(anno).name(); + desc = MBeanOperationParameter.class.cast(anno).description(); + paramInfo = new MBeanParameterInfo(name, type, desc); + } + } + + + if (paramInfo == null) + { + paramInfo = new MBeanParameterInfo("p " + (i + 1), type, "parameter " + (i + 1)); + } + if (paramInfo != null) + paramsInfo[i] = paramInfo; + } + + return paramsInfo; + } + + /** + * Introspects the MBean class for constructors + * @param implClass + * @return MBeanConstructorInfo[] + */ + static MBeanConstructorInfo[] getMBeanConstructorsInfo(Class implClass) + { + List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>(); + + for (Constructor cons : implClass.getConstructors()) + { + MBeanConstructorInfo constructorInfo = getMBeanConstructorInfo(cons); + //MBeanConstructorInfo constructorInfo = new MBeanConstructorInfo("desc", cons); + if (constructorInfo != null) + constructors.add(constructorInfo); + } + + return constructors.toArray(new MBeanConstructorInfo[0]); + } + + /** + * Retrieves the constructor info from given constructor. + * @param cons + * @return MBeanConstructorInfo + */ + private static MBeanConstructorInfo getMBeanConstructorInfo(Constructor cons) + { + String desc = null; + Annotation anno = cons.getAnnotation(MBeanConstructor.class); + if (anno != null && MBeanConstructor.class.isInstance(anno)) + { + desc = MBeanConstructor.class.cast(anno).value(); + } + + //MBeanParameterInfo[] paramsInfo = getParametersInfo(cons.getParameterAnnotations(), + // cons.getParameterTypes()); + + return new MBeanConstructorInfo(cons.getName(), + desc != null ? _defaultConstructorDescription : desc , + null); + } + + /** + * Retrieves the description from the annotations of given class + * @param annotatedClass + * @return class description + */ + static String getMBeanDescription(Class annotatedClass) + { + Annotation anno = annotatedClass.getAnnotation(MBeanDescription.class); + if (anno != null && MBeanDescription.class.isInstance(anno)) + { + return MBeanDescription.class.cast(anno).value(); + } + return _defaultMbeanDescription; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java new file mode 100644 index 0000000000..e9b4d85e66 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java @@ -0,0 +1,257 @@ +/* + * + * 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.management; + +import org.apache.qpid.server.configuration.management.ConfigurationManagement; +import org.apache.qpid.server.logging.management.LoggingManagement; +import org.apache.qpid.server.security.access.management.UserManagement; +import org.apache.log4j.Logger; + +import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.JMXPrincipal; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.JMException; +import javax.security.auth.Subject; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.Principal; +import java.security.AccessControlContext; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.Properties; + +/** + * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements + * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite + * and admin users. + */ +public class MBeanInvocationHandlerImpl implements InvocationHandler +{ + private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class); + + public final static String ADMIN = "admin"; + public final static String READWRITE = "readwrite"; + public final static String READONLY = "readonly"; + private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; + private MBeanServer _mbs; + private static Properties _userRoles = new Properties(); + + private static HashSet<String> _adminOnlyMethods = new HashSet<String>(); + { + _adminOnlyMethods.add(UserManagement.TYPE); + _adminOnlyMethods.add(LoggingManagement.TYPE); + _adminOnlyMethods.add(ConfigurationManagement.TYPE); + } + + public static MBeanServerForwarder newProxyInstance() + { + final InvocationHandler handler = new MBeanInvocationHandlerImpl(); + final Class[] interfaces = new Class[]{MBeanServerForwarder.class}; + + Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); + return MBeanServerForwarder.class.cast(proxy); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + final String methodName = method.getName(); + + if (methodName.equals("getMBeanServer")) + { + return _mbs; + } + + if (methodName.equals("setMBeanServer")) + { + if (args[0] == null) + { + throw new IllegalArgumentException("Null MBeanServer"); + } + if (_mbs != null) + { + throw new IllegalArgumentException("MBeanServer object already initialized"); + } + _mbs = (MBeanServer) args[0]; + return null; + } + + // Retrieve Subject from current AccessControlContext + AccessControlContext acc = AccessController.getContext(); + Subject subject = Subject.getSubject(acc); + + // Allow operations performed locally on behalf of the connector server itself + if (subject == null) + { + return method.invoke(_mbs, args); + } + + if (args == null || DELEGATE.equals(args[0])) + { + return method.invoke(_mbs, args); + } + + // Restrict access to "createMBean" and "unregisterMBean" to any user + if (methodName.equals("createMBean") || methodName.equals("unregisterMBean")) + { + _logger.debug("User trying to create or unregister an MBean"); + throw new SecurityException("Access denied"); + } + + // Retrieve JMXPrincipal from Subject + Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); + if (principals == null || principals.isEmpty()) + { + throw new SecurityException("Access denied"); + } + + Principal principal = principals.iterator().next(); + String identity = principal.getName(); + + if (isAdminMethod(args)) + { + if (isAdmin(identity)) + { + return method.invoke(_mbs, args); + } + else + { + throw new SecurityException("Access denied"); + } + } + + // Following users can perform any operation other than "createMBean" and "unregisterMBean" + if (isAllowedToModify(identity)) + { + return method.invoke(_mbs, args); + } + + // These users can only call "getAttribute" on the MBeanServerDelegate MBean + // Here we can add other fine grained permissions like specific method for a particular mbean + if (isReadOnlyUser(identity) && isReadOnlyMethod(method, args)) + { + return method.invoke(_mbs, args); + } + + throw new SecurityException("Access denied"); + } + + private boolean isAdminMethod(Object[] args) + { + if (args[0] instanceof ObjectName) + { + ObjectName object = (ObjectName) args[0]; + + return _adminOnlyMethods.contains(object.getKeyProperty("type")); + } + return false; + } + + // Initialises the user roles + public static void setAccessRights(Properties accessRights) + { + _userRoles = accessRights; + } + + private boolean isAdmin(String userName) + { + if (ADMIN.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isAllowedToModify(String userName) + { + if (ADMIN.equals(_userRoles.getProperty(userName)) + || READWRITE.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isReadOnlyUser(String userName) + { + if (READONLY.equals(_userRoles.getProperty(userName))) + { + return true; + } + return false; + } + + private boolean isReadOnlyMethod(Method method, Object[] args) + { + String methodName = method.getName(); + + //handle standard get/set/query and select 'is' methods from MBeanServer + if (methodName.startsWith("query") || methodName.startsWith("get") + ||methodName.startsWith("isInstanceOf") || methodName.startsWith("isRegistered")) + { + return true; + } + else if (methodName.startsWith("set")) + { + return false; + } + + //handle invocation of other methods on mbeans + if ((args[0] instanceof ObjectName) && (methodName.equals("invoke"))) + { + + //get invoked method name + String mbeanMethod = (args.length > 1) ? (String) args[1] : null; + if (mbeanMethod == null) + { + return false; + } + + try + { + //check if the given method is tagged with an INFO impact attribute + MBeanInfo mbeanInfo = _mbs.getMBeanInfo((ObjectName) args[0]); + if (mbeanInfo != null) + { + MBeanOperationInfo[] opInfos = mbeanInfo.getOperations(); + for (MBeanOperationInfo opInfo : opInfos) + { + if (opInfo.getName().equals(mbeanMethod) && (opInfo.getImpact() == MBeanOperationInfo.INFO)) + { + return true; + } + } + } + } + catch (JMException ex) + { + ex.printStackTrace(); + } + } + + return false; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.java new file mode 100644 index 0000000000..a2dca3e51d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperation.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.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.management.MBeanOperationInfo; + +/** + * Annotation for MBean operations. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Inherited +public @interface MBeanOperation +{ + String name(); + String description(); + int impact() default MBeanOperationInfo.INFO; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java new file mode 100644 index 0000000000..aba5ec70d8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanOperationParameter.java @@ -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. + * + * + */ + +package org.apache.qpid.server.management; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for MBean operation parameters. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface MBeanOperationParameter { + String name(); + String description(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java new file mode 100644 index 0000000000..166a2a376d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java @@ -0,0 +1,34 @@ +/* + * + * 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.management; + +/** + * Any object that can return a related MBean should implement this interface. + * + * This enables other classes to get the managed object, which in turn is useful when + * constructing relationships between managed objects without having to maintain + * separate data structures containing MBeans. + * + */ +public interface Managable +{ + ManagedObject getManagedObject(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java new file mode 100644 index 0000000000..c18417fc43 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedBroker.java @@ -0,0 +1,98 @@ +/* + * + * 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.management; + +import java.io.IOException; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; + +import org.apache.qpid.server.exchange.ManagedExchange; +import org.apache.qpid.server.queue.ManagedQueue; + +/** + * The ManagedBroker is the management interface to expose management + * features of the Broker. + * + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedBroker +{ + static final String TYPE = "VirtualHostManager"; + + static final int VERSION = 1 ; + + /** + * Creates a new Exchange. + * @param name + * @param type + * @param durable + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="createNewExchange", description="Creates a new Exchange", impact= MBeanOperationInfo.ACTION) + void createNewExchange(@MBeanOperationParameter(name="name", description="Name of the new exchange")String name, + @MBeanOperationParameter(name="ExchangeType", description="Type of the exchange")String type, + @MBeanOperationParameter(name="durable", description="true if the Exchang should be durable")boolean durable) + throws IOException, JMException; + + /** + * unregisters all the channels, queuebindings etc and unregisters + * this exchange from managed objects. + * @param exchange + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="unregisterExchange", + description="Unregisters all the related channels and queuebindings of this exchange", + impact= MBeanOperationInfo.ACTION) + void unregisterExchange(@MBeanOperationParameter(name= ManagedExchange.TYPE, description="Exchange Name")String exchange) + throws IOException, JMException; + + /** + * Create a new Queue on the Broker server + * @param queueName + * @param durable + * @param owner + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="createNewQueue", description="Create a new Queue on the Broker server", impact= MBeanOperationInfo.ACTION) + void createNewQueue(@MBeanOperationParameter(name="queue name", description="Name of the new queue")String queueName, + @MBeanOperationParameter(name="owner", description="Owner name")String owner, + @MBeanOperationParameter(name="durable", description="true if the queue should be durable")boolean durable) + throws IOException, JMException; + + /** + * Unregisters the Queue bindings, removes the subscriptions and unregisters + * from the managed objects. + * @param queueName + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="deleteQueue", + description="Unregisters the Queue bindings, removes the subscriptions and deletes the queue", + impact= MBeanOperationInfo.ACTION) + void deleteQueue(@MBeanOperationParameter(name= ManagedQueue.TYPE, description="Queue Name")String queueName) + throws IOException, JMException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java new file mode 100644 index 0000000000..42ea8921a4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.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.management; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.qpid.AMQException; + +/** + * This should be implemented by all Managable objects. + */ +public interface ManagedObject +{ + static final String DOMAIN = "org.apache.qpid"; + + /** + * @return the name that uniquely identifies this object instance. It must be + * unique only among objects of this type at this level in the hierarchy so + * the uniqueness should not be too difficult to ensure. + */ + String getObjectInstanceName(); + + String getType(); + + Class<?> getManagementInterface(); + + ManagedObject getParentObject(); + + void register() throws AMQException; + + void unregister() throws AMQException; + + /** + * Returns the ObjectName required for the mbeanserver registration. + * @return ObjectName + * @throws MalformedObjectNameException + */ + ObjectName getObjectName() throws MalformedObjectNameException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java new file mode 100644 index 0000000000..b58b17ba86 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java @@ -0,0 +1,51 @@ +/* + * + * 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.management; + +import javax.management.JMException; + +import org.apache.commons.configuration.ConfigurationException; + +import java.rmi.RemoteException; +import java.io.IOException; + +/** + * Handles the registration (and unregistration and so on) of managed objects. + * + * Managed objects are responsible for exposting attributes, operations and notifications. They will expose + * these outside the JVM therefore it is important not to use implementation objects directly as managed objects. + * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a + * controlled way. + * + * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will + * be the obvious choice for managed objects. + * + */ +public interface ManagedObjectRegistry +{ + void start() throws IOException, ConfigurationException; + + void registerObject(ManagedObject managedObject) throws JMException; + + void unregisterObject(ManagedObject managedObject) throws JMException; + + void close() throws RemoteException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java new file mode 100644 index 0000000000..b4fbed6948 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java @@ -0,0 +1,60 @@ +/* + * + * 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.management; + +import javax.management.JMException; + +import org.apache.log4j.Logger; + +import java.rmi.RemoteException; + +/** + * This managed object registry does not actually register MBeans. This can be used in tests when management is + * not required or when management has been disabled. + * + */ +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(NoopManagedObjectRegistry.class); + + public NoopManagedObjectRegistry() + { + _log.info("Management is disabled"); + } + + public void start() + { + //no-op + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + } + + public void close() throws RemoteException + { + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java new file mode 100644 index 0000000000..e01c5aabbf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java @@ -0,0 +1,57 @@ +/*
+ *
+ * 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 file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.AMQException;
+
+public interface ProtocolOutputConverter
+{
+ void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag);
+
+ interface Factory
+ {
+ ProtocolOutputConverter newInstance(AMQProtocolSession session);
+ }
+
+ void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException;
+
+ void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException;
+
+ byte getProtocolMinorVersion();
+
+ byte getProtocolMajorVersion();
+
+ void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException;
+
+ void writeFrame(AMQDataBlock block);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java new file mode 100644 index 0000000000..36e7e88fd6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java @@ -0,0 +1,61 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter.Factory;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.ProtocolVersion;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public class ProtocolOutputConverterRegistry
+{
+
+ private static final Map<ProtocolVersion, Factory> _registry =
+ new HashMap<ProtocolVersion, Factory>();
+
+
+ static
+ {
+ register(ProtocolVersion.v8_0, org.apache.qpid.server.output.amqp0_8.ProtocolOutputConverterImpl.getInstanceFactory());
+ register(ProtocolVersion.v0_9, org.apache.qpid.server.output.amqp0_9.ProtocolOutputConverterImpl.getInstanceFactory());
+
+ }
+
+ private static void register(ProtocolVersion version, Factory converter)
+ {
+
+ _registry.put(version,converter);
+ }
+
+
+ public static ProtocolOutputConverter getConverter(AMQProtocolSession session)
+ {
+ return _registry.get(session.getProtocolVersion()).newInstance(session);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..2b55d294b5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java @@ -0,0 +1,284 @@ +/*
+ *
+ * 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 file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output.amqp0_8;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.AMQException;
+
+import org.apache.mina.common.ByteBuffer;
+
+import java.util.Iterator;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQDataBlock deliver = createEncodedDeliverFrame(message, channelId, deliveryTag, consumerTag);
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+ AMQDataBlock deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, getProtocolSession().getMethodRegistry().getProtocolVersionMethodConverter().convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ private AMQDataBlock createEncodedDeliverFrame(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicDeliverBody deliverBody =
+ methodRegistry.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey());
+ AMQFrame deliverFrame = deliverBody.generateFrame(channelId);
+
+
+ return deliverFrame;
+ }
+
+ private AMQDataBlock createEncodedGetOkFrame(AMQMessage message, int channelId, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicGetOkBody getOkBody =
+ methodRegistry.createBasicGetOkBody(deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey(),
+ queueSize);
+ AMQFrame getOkFrame = getOkBody.generateFrame(channelId);
+
+ return getOkFrame;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQDataBlock createEncodedReturnFrame(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicReturnBody basicReturnBody =
+ methodRegistry.createBasicReturnBody(replyCode,
+ replyText,
+ message.getMessagePublishInfo().getExchange(),
+ message.getMessagePublishInfo().getRoutingKey());
+ AMQFrame returnFrame = basicReturnBody.generateFrame(channelId);
+
+ return returnFrame;
+ }
+
+ public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+ AMQDataBlock returnFrame = createEncodedReturnFrame(message, channelId, replyCode, replyText);
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ message.getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = message.getBodyFrameIterator(getProtocolSession(), channelId);
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ if (bodyFrameIterator.hasNext())
+ {
+ AMQDataBlock firstContentBody = bodyFrameIterator.next();
+ AMQDataBlock[] blocks = new AMQDataBlock[]{returnFrame, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(new AMQDataBlock[]{returnFrame, contentHeader});
+
+ writeFrame(compositeBlock);
+ }
+
+ //
+ // Now start writing out the other content bodies
+ // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded
+ //
+ while (bodyFrameIterator.hasNext())
+ {
+ writeFrame(bodyFrameIterator.next());
+ }
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+ MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+ BasicCancelOkBody basicCancelOkBody = methodRegistry.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..65184fe744 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java @@ -0,0 +1,391 @@ +package org.apache.qpid.server.output.amqp0_9;
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +
+import org.apache.mina.common.ByteBuffer;
+
+import java.util.Iterator;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.queue.AMQMessage;
+import org.apache.qpid.server.queue.AMQMessageHandle;
+import org.apache.qpid.server.store.StoreContext;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+ private static final MethodRegistry METHOD_REGISTRY = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9);
+ private static final ProtocolVersionMethodConverter PROTOCOL_METHOD_CONVERTER = METHOD_REGISTRY.getProtocolVersionMethodConverter();
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQBody deliverBody = createEncodedDeliverFrame(message, channelId, deliveryTag, consumerTag);
+ final ContentHeaderBody contentHeaderBody = message.getContentHeaderBody();
+
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQBodyBlock compositeBlock = new SmallCompositeAMQBodyBlock(channelId, deliverBody,
+ contentHeaderBody);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQBody firstContentBody = PROTOCOL_METHOD_CONVERTER.convertToBody(cb);
+
+ CompositeAMQBodyBlock compositeBlock = new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, firstContentBody);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb)));
+ }
+
+
+ }
+
+ }
+
+ private AMQDataBlock createContentHeaderBlock(final int channelId, final ContentHeaderBody contentHeaderBody)
+ {
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ contentHeaderBody);
+ return contentHeader;
+ }
+
+
+ public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+ final StoreContext storeContext = message.getStoreContext();
+
+ AMQFrame deliver = createEncodedGetOkFrame(message, channelId, deliveryTag, queueSize);
+
+
+ AMQDataBlock contentHeader = createContentHeaderBlock(channelId, message.getContentHeaderBody());
+
+ final int bodyCount = messageHandle.getBodyCount(storeContext);
+ if(bodyCount == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+
+
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ ContentChunk cb = messageHandle.getContentChunk(storeContext, 0);
+
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ //
+ // Now start writing out the other content bodies
+ //
+ for(int i = 1; i < bodyCount; i++)
+ {
+ cb = messageHandle.getContentChunk(storeContext, i);
+ writeFrame(new AMQFrame(channelId, PROTOCOL_METHOD_CONVERTER.convertToBody(cb)));
+ }
+
+
+ }
+
+
+ }
+
+
+ private AMQBody createEncodedDeliverFrame(AMQMessage message, final int channelId, final long deliveryTag, final AMQShortString consumerTag)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+
+ final boolean isRedelivered = messageHandle.isRedelivered();
+ final AMQShortString exchangeName = pb.getExchange();
+ final AMQShortString routingKey = pb.getRoutingKey();
+
+ final AMQBody returnBlock = new AMQBody()
+ {
+
+ public AMQBody _underlyingBody;
+
+ public AMQBody createAMQBody()
+ {
+ return METHOD_REGISTRY.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey);
+
+
+
+
+
+ }
+
+ public byte getFrameType()
+ {
+ return AMQMethodBody.TYPE;
+ }
+
+ public int getSize()
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ return _underlyingBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ _underlyingBody.writePayload(buffer);
+ }
+
+ public void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession)
+ throws AMQException
+ {
+ throw new AMQException("This block should never be dispatched!");
+ }
+ };
+ return returnBlock;
+ }
+
+ private AMQFrame createEncodedGetOkFrame(AMQMessage message, int channelId, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ final AMQMessageHandle messageHandle = message.getMessageHandle();
+
+
+ BasicGetOkBody getOkBody =
+ METHOD_REGISTRY.createBasicGetOkBody(deliveryTag,
+ messageHandle.isRedelivered(),
+ pb.getExchange(),
+ pb.getRoutingKey(),
+ queueSize);
+ AMQFrame getOkFrame = getOkBody.generateFrame(channelId);
+
+ return getOkFrame;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQDataBlock createEncodedReturnFrame(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+
+ BasicReturnBody basicReturnBody =
+ METHOD_REGISTRY.createBasicReturnBody(replyCode,
+ replyText,
+ message.getMessagePublishInfo().getExchange(),
+ message.getMessagePublishInfo().getRoutingKey());
+ AMQFrame returnFrame = basicReturnBody.generateFrame(channelId);
+
+ return returnFrame;
+ }
+
+ public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+ AMQDataBlock returnFrame = createEncodedReturnFrame(message, channelId, replyCode, replyText);
+
+ AMQDataBlock contentHeader = createContentHeaderBlock(channelId, message.getContentHeaderBody());
+
+ Iterator<AMQDataBlock> bodyFrameIterator = message.getBodyFrameIterator(getProtocolSession(), channelId);
+ //
+ // Optimise the case where we have a single content body. In that case we create a composite block
+ // so that we can writeDeliver out the deliver, header and body with a single network writeDeliver.
+ //
+ if (bodyFrameIterator.hasNext())
+ {
+ AMQDataBlock firstContentBody = bodyFrameIterator.next();
+ AMQDataBlock[] blocks = new AMQDataBlock[]{returnFrame, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(new AMQDataBlock[]{returnFrame, contentHeader});
+
+ writeFrame(compositeBlock);
+ }
+
+ //
+ // Now start writing out the other content bodies
+ // TODO: MINA needs to be fixed so the the pending writes buffer is not unbounded
+ //
+ while (bodyFrameIterator.hasNext())
+ {
+ writeFrame(bodyFrameIterator.next());
+ }
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+
+ BasicCancelOkBody basicCancelOkBody = METHOD_REGISTRY.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+
+
+ public static final class CompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 3 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final AMQBody _contentBody;
+ private final int _channel;
+
+
+ public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+ _contentBody = contentBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() + _contentBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody, _contentBody);
+ }
+ }
+
+ public static final class SmallCompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 2 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final int _channel;
+
+
+ public SmallCompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() ;
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody);
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java new file mode 100644 index 0000000000..b0ebf197f9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.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.plugins; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator +{ + + BundleContext _context = null; + + public void start(BundleContext ctx) throws Exception + { + _context = ctx; + } + + public void stop(BundleContext ctx) throws Exception + { + start(null); + } + + public BundleContext getContext() + { + return _context; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java new file mode 100644 index 0000000000..dbfcefb6ab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java @@ -0,0 +1,176 @@ +/* + * 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.plugins; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.framework.Felix; +import org.apache.felix.framework.cache.BundleCache; +import org.apache.felix.framework.util.FelixConstants; +import org.apache.felix.framework.util.StringMap; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; +import org.apache.qpid.server.security.access.plugins.AllowAll; +import org.apache.qpid.server.security.access.plugins.DenyAll; +import org.apache.qpid.server.security.access.plugins.LegacyAccessPlugin; +import org.apache.qpid.server.security.access.plugins.SimpleXML; +import org.apache.qpid.server.security.access.plugins.network.FirewallPlugin; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleException; +import org.osgi.util.tracker.ServiceTracker; + +/** + * + * @author aidan + * + * Provides access to pluggable elements, such as exchanges + */ + +public class PluginManager +{ + + private Felix _felix = null; + private ServiceTracker _exchangeTracker = null; + private ServiceTracker _securityTracker = null; + private Activator _activator = null; + private boolean _empty; + private Map<String, ACLPluginFactory> _securityPlugins; + + public PluginManager(String plugindir) throws Exception + { + StringMap configMap = new StringMap(false); + + // Tell felix it's being embedded + configMap.put(FelixConstants.EMBEDDED_EXECUTION_PROP, "true"); + // Add the bundle provided service interface package and the core OSGi + // packages to be exported from the class path via the system bundle. + configMap.put(FelixConstants.FRAMEWORK_SYSTEMPACKAGES, "org.osgi.framework; version=1.3.0," + + "org.osgi.service.packageadmin; version=1.2.0," + + "org.osgi.service.startlevel; version=1.0.0," + + "org.osgi.service.url; version=1.0.0," + + "org.apache.qpid.framing; version=0.2.1," + + "org.apache.qpid.server.exchange; version=0.2.1," + + "org.apache.qpid.server.management; version=0.2.1,"+ + "org.apache.qpid.protocol; version=0.2.1,"+ + "org.apache.qpid.server.virtualhost; version=0.2.1," + + "org.apache.qpid; version=0.2.1," + + "org.apache.qpid.server.queue; version=0.2.1," + + "javax.management.openmbean; version=1.0.0,"+ + "javax.management; version=1.0.0,"+ + "org.apache.qpid.junit.extensions.util; version=0.6.1," + ); + + if (plugindir == null) + { + _empty = true; + return; + } + + // Set the list of bundles to load + File dir = new File(plugindir); + if (!dir.exists()) + { + _empty = true; + return; + } + StringBuffer pluginJars = new StringBuffer(); + + if (dir.isDirectory()) + { + for (String child : dir.list()) + { + if (child.endsWith("jar")) + { + pluginJars.append(String.format(" file:%s%s%s", plugindir,File.separator,child)); + } + } + } + if (pluginJars.length() == 0) + { + _empty = true; + return; + } + + configMap.put(FelixConstants.AUTO_START_PROP + ".1", pluginJars.toString()); + configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, plugindir); + + List<BundleActivator> activators = new ArrayList<BundleActivator>(); + _activator = new Activator(); + activators.add(_activator); + + _felix = new Felix(configMap, activators); + try + { + _felix.start(); + + _exchangeTracker = new ServiceTracker(_activator.getContext(), ExchangeType.class.getName(), null); + _exchangeTracker.open(); + + _securityTracker = new ServiceTracker(_activator.getContext(), ACLPlugin.class.getName(), null); + _exchangeTracker.open(); + + } + catch (BundleException e) + { + throw new Exception("Could not create bundle"); + } + } + + private <type> Map<String, type> getServices(ServiceTracker tracker) + { + Map<String, type>exchanges = new HashMap<String, type>(); + + if (tracker != null) + { + for (Object service : tracker.getServices()) + { + exchanges.put(service.getClass().getName(), (type) service); + } + } + + return exchanges; + } + + public Map<String, ExchangeType<?>> getExchanges() + { + return getServices(_exchangeTracker); + } + + public Map<String, ACLPluginFactory> getSecurityPlugins() + { + if (_securityPlugins == null) + { + _securityPlugins = getServices(_securityTracker); + // A little gross that we have to add them here, but not all the plugins are OSGIfied + _securityPlugins.put(SimpleXML.class.getName(), SimpleXML.FACTORY); + _securityPlugins.put(AllowAll.class.getName(), AllowAll.FACTORY); + _securityPlugins.put(DenyAll.class.getName(), DenyAll.FACTORY); + _securityPlugins.put(LegacyAccessPlugin.class.getName(), LegacyAccessPlugin.FACTORY); + _securityPlugins.put(FirewallPlugin.class.getName(), FirewallPlugin.FACTORY); + } + return _securityPlugins; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java new file mode 100644 index 0000000000..205ca73f13 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java @@ -0,0 +1,859 @@ +/* + * + * 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.protocol; + +import org.apache.log4j.Logger; + +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.transport.vmpipe.VmPipeAddress; + +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.*; +import org.apache.qpid.pool.ReadWriteThreadModel; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.handler.ServerMethodDispatcherImpl; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.Sender; + +import javax.management.JMException; +import javax.security.sasl.SaslServer; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +public class AMQMinaProtocolSession implements AMQProtocolSession, Managable +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class); + + private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString(); + + // to save boxing the channelId and looking up in a map... cache in an array the low numbered + // channels. This value must be of the form 2^x - 1. + private static final int CHANNEL_CACHE_SIZE = 0xff; + + private final IoSession _minaProtocolSession; + + private AMQShortString _contextKey; + + private AMQShortString _clientVersion = null; + + private VirtualHost _virtualHost; + + private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + private final AMQChannel[] _cachedChannels = new AMQChannel[CHANNEL_CACHE_SIZE + 1]; + + private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>(); + + private final AMQStateManager _stateManager; + + private AMQCodecFactory _codecFactory; + + private AMQProtocolSessionMBean _managedObject; + + private SaslServer _saslServer; + + private Object _lastReceived; + + private Object _lastSent; + + protected boolean _closed; + // maximum number of channels this session should have + private long _maxNoOfChannels = 1000; + + /* AMQP Version for this session */ + private ProtocolVersion _protocolVersion = ProtocolVersion.getLatestSupportedVersion(); + + private FieldTable _clientProperties; + private final List<Task> _taskList = new CopyOnWriteArrayList<Task>(); + + private List<Integer> _closingChannelsList = new CopyOnWriteArrayList<Integer>(); + private ProtocolOutputConverter _protocolOutputConverter; + private Principal _authorizedID; + private MethodDispatcher _dispatcher; + private ProtocolSessionIdentifier _sessionIdentifier; + + private static final long LAST_WRITE_FUTURE_JOIN_TIMEOUT = 60000L; + private org.apache.mina.common.WriteFuture _lastWriteFuture; + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + public AMQMinaProtocolSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQCodecFactory codecFactory) + throws AMQException + { + _stateManager = new AMQStateManager(virtualHostRegistry, this); + _minaProtocolSession = session; + session.setAttachment(this); + + _codecFactory = codecFactory; + + try + { + IoServiceConfig config = session.getServiceConfig(); + ReadWriteThreadModel threadModel = (ReadWriteThreadModel) config.getThreadModel(); + threadModel.getAsynchronousReadFilter().createNewJobForSession(session); + threadModel.getAsynchronousWriteFilter().createNewJobForSession(session); + } + catch (RuntimeException e) + { + e.printStackTrace(); + throw e; + + } + } + + public AMQMinaProtocolSession(IoSession session, VirtualHostRegistry virtualHostRegistry, AMQCodecFactory codecFactory, + AMQStateManager stateManager) throws AMQException + { + _stateManager = stateManager; + _minaProtocolSession = session; + session.setAttachment(this); + + _codecFactory = codecFactory; + + } + + private AMQProtocolSessionMBean createMBean() throws AMQException + { + try + { + return new AMQProtocolSessionMBean(this); + } + catch (JMException ex) + { + _logger.error("AMQProtocolSession MBean creation has failed ", ex); + throw new AMQException("AMQProtocolSession MBean creation has failed ", ex); + } + } + + public IoSession getIOSession() + { + return _minaProtocolSession; + } + + public static AMQProtocolSession getAMQProtocolSession(IoSession minaProtocolSession) + { + return (AMQProtocolSession) minaProtocolSession.getAttachment(); + } + + public void dataBlockReceived(AMQDataBlock message) throws Exception + { + _lastReceived = message; + if (message instanceof ProtocolInitiation) + { + protocolInitiationReceived((ProtocolInitiation) message); + + } + else if (message instanceof AMQFrame) + { + AMQFrame frame = (AMQFrame) message; + frameReceived(frame); + + } + else + { + throw new UnknnownMessageTypeException(message); + } + } + + private void frameReceived(AMQFrame frame) throws AMQException + { + int channelId = frame.getChannel(); + AMQBody body = frame.getBodyFrame(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Frame Received: " + frame); + } + + // Check that this channel is not closing + if (channelAwaitingClosure(channelId)) + { + if ((frame.getBodyFrame() instanceof ChannelCloseOkBody)) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Channel[" + channelId + "] awaiting closure - processing close-ok"); + } + } + else + { + if (_logger.isInfoEnabled()) + { + _logger.info("Channel[" + channelId + "] awaiting closure. Should close socket as client did not close-ok :" + frame); + } + + closeProtocolSession(); + return; + } + } + + try + { + body.handle(channelId, this); + } + catch (AMQException e) + { + closeChannel(channelId); + throw e; + } + + } + + private void protocolInitiationReceived(ProtocolInitiation pi) + { + // this ensures the codec never checks for a PI message again + ((AMQDecoder) _codecFactory.getDecoder()).setExpectProtocolInitiation(false); + try + { + ProtocolVersion pv = pi.checkVersion(); // Fails if not correct + + // This sets the protocol version (and hence framing classes) for this session. + setProtocolVersion(pv); + + String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms(); + + String locales = "en_US"; + + AMQMethodBody responseBody = getMethodRegistry().createConnectionStartBody((short) getProtocolMajorVersion(), + (short) getProtocolMinorVersion(), + null, + mechanisms.getBytes(), + locales.getBytes()); + _minaProtocolSession.write(responseBody.generateFrame(0)); + + } + catch (AMQException e) + { + _logger.info("Received unsupported protocol initiation for protocol version: " + getProtocolVersion()); + + _minaProtocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + + // TODO: Close connection (but how to wait until message is sent?) + // ritchiem 2006-12-04 will this not do? + // WriteFuture future = _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOLgetProtocolMajorVersion()], pv[i][PROTOCOLgetProtocolMinorVersion()])); + // future.join(); + // close connection + + } + } + + public void methodFrameReceived(int channelId, AMQMethodBody methodBody) + { + + final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(channelId, methodBody); + + try + { + try + { + + boolean wasAnyoneInterested = _stateManager.methodReceived(evt); + + if (!_frameListeners.isEmpty()) + { + for (AMQMethodListener listener : _frameListeners) + { + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; + } + } + + if (!wasAnyoneInterested) + { + throw new AMQNoMethodHandlerException(evt); + } + } + catch (AMQChannelException e) + { + if (getChannel(channelId) != null) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing channel due to: " + e.getMessage()); + } + + writeFrame(e.getCloseFrame(channelId)); + closeChannel(channelId); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("ChannelException occured on non-existent channel:" + e.getMessage()); + } + + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e.getMessage()); + } + + AMQConnectionException ce = + evt.getMethod().getConnectionException(AMQConstant.CHANNEL_ERROR, + AMQConstant.CHANNEL_ERROR.getName().toString()); + + closeConnection(channelId, ce, false); + } + } + catch (AMQConnectionException e) + { + closeConnection(channelId, e, false); + } + } + catch (Exception e) + { + + for (AMQMethodListener listener : _frameListeners) + { + listener.error(e); + } + + _logger.error("Unexpected exception while processing frame. Closing connection.", e); + + closeProtocolSession(); + } + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException + { + + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentHeader(body); + + } + + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException + { + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentBody(body); + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + // NO - OP + } + + /** + * 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) + { + _lastSent = frame; + + _lastWriteFuture = _minaProtocolSession.write(frame); + } + + public AMQShortString getContextKey() + { + return _contextKey; + } + + public void setContextKey(AMQShortString contextKey) + { + _contextKey = contextKey; + } + + public List<AMQChannel> getChannels() + { + return new ArrayList<AMQChannel>(_channelMap.values()); + } + + public AMQChannel getAndAssertChannel(int channelId) throws AMQException + { + AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Channel not found with id:" + channelId); + } + + return channel; + } + + public AMQChannel getChannel(int channelId) throws AMQException + { + final AMQChannel channel = + ((channelId & CHANNEL_CACHE_SIZE) == channelId) ? _cachedChannels[channelId] : _channelMap.get(channelId); + if ((channel == null) || channel.isClosing()) + { + return null; + } + else + { + return channel; + } + } + + public boolean channelAwaitingClosure(int channelId) + { + return !_closingChannelsList.isEmpty() && _closingChannelsList.contains(channelId); + } + + public void addChannel(AMQChannel channel) throws AMQException + { + if (_closed) + { + throw new AMQException("Session is closed"); + } + + final int channelId = channel.getChannelId(); + + if (_closingChannelsList.contains(channelId)) + { + throw new AMQException("Session is marked awaiting channel close"); + } + + if (_channelMap.size() == _maxNoOfChannels) + { + String errorMessage = + toString() + ": maximum number of channels has been reached (" + _maxNoOfChannels + + "); can't create channel"; + _logger.error(errorMessage); + throw new AMQException(AMQConstant.NOT_ALLOWED, errorMessage); + } + else + { + _channelMap.put(channel.getChannelId(), channel); + } + + if (((channelId & CHANNEL_CACHE_SIZE) == channelId)) + { + _cachedChannels[channelId] = channel; + } + + checkForNotification(); + } + + private void checkForNotification() + { + int channelsCount = _channelMap.size(); + if (channelsCount >= _maxNoOfChannels) + { + _managedObject.notifyClients("Channel count (" + channelsCount + ") has reached the threshold value"); + } + } + + public Long getMaximumNumberOfChannels() + { + return _maxNoOfChannels; + } + + public void setMaximumNumberOfChannels(Long value) + { + _maxNoOfChannels = value; + } + + public void commitTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.commit(); + } + } + + public void rollbackTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.rollback(); + } + } + + /** + * Close a specific channel. This will remove any resources used by the channel, including: <ul><li>any queue + * subscriptions (this may in turn remove queues if they are auto delete</li> </ul> + * + * @param channelId id of the channel to close + * + * @throws AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + public void closeChannel(int channelId) throws AMQException + { + final AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Unknown channel id"); + } + else + { + try + { + channel.close(); + markChannelAwaitingCloseOk(channelId); + } + finally + { + removeChannel(channelId); + } + } + } + + public void closeChannelOk(int channelId) + { + // todo QPID-847 - This is called from two lcoations ChannelCloseHandler and ChannelCloseOkHandler. + // When it is the CC_OK_Handler then it makes sence to remove the channel else we will leak memory. + // We do it from the Close Handler as we are sending the OK back to the client. + // While this is AMQP spec compliant. The Java client in the event of an IllegalArgumentException + // will send a close-ok.. Where we should call removeChannel. + // However, due to the poor exception handling on the client. The client-user will be notified of the + // InvalidArgument and if they then decide to close the session/connection then the there will be time + // for that to occur i.e. a new close method be sent before the exeption handling can mark the session closed. + //removeChannel(channelId); + _closingChannelsList.remove(new Integer(channelId)); + } + + private void markChannelAwaitingCloseOk(int channelId) + { + _closingChannelsList.add(channelId); + } + + /** + * In our current implementation this is used by the clustering code. + * + * @param channelId The channel to remove + */ + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + if ((channelId & CHANNEL_CACHE_SIZE) == channelId) + { + _cachedChannels[channelId] = null; + } + } + + /** + * Initialise heartbeats on the session. + * + * @param delay delay in seconds (not ms) + */ + public void initHeartbeats(int delay) + { + if (delay > 0) + { + _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay); + _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, (int) (ApplicationRegistry.getInstance().getConfiguration().getHeartBeatTimeout() * delay)); + } + } + + /** + * Closes all channels that were opened by this protocol session. This frees up all resources used by the channel. + * + * @throws AMQException if an error occurs while closing any channel + */ + private void closeAllChannels() throws AMQException + { + for (AMQChannel channel : _channelMap.values()) + { + channel.close(); + } + + _channelMap.clear(); + for (int i = 0; i <= CHANNEL_CACHE_SIZE; i++) + { + _cachedChannels[i] = null; + } + } + + /** This must be called when the session is _closed in order to free up any resources managed by the session. */ + public void closeSession() throws AMQException + { + if (!_closed) + { + _closed = true; + + if (_virtualHost != null) + { + _virtualHost.getConnectionRegistry().deregisterConnection(this); + } + + closeAllChannels(); + if (_managedObject != null) + { + _managedObject.unregister(); + } + + for (Task task : _taskList) + { + task.doTask(this); + } + } + } + + public void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) throws AMQException + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e.getMessage()); + } + + markChannelAwaitingCloseOk(channelId); + closeSession(); + _stateManager.changeState(AMQState.CONNECTION_CLOSING); + writeFrame(e.getCloseFrame(channelId)); + + if (closeProtocolSession) + { + closeProtocolSession(); + } + } + + public void closeProtocolSession() + { + closeProtocolSession(true); + } + + public void closeProtocolSession(boolean waitLast) + { + if (waitLast && (_lastWriteFuture != null)) + { + _logger.debug("Waiting for last write to join."); + _lastWriteFuture.join(LAST_WRITE_FUTURE_JOIN_TIMEOUT); + } + + _logger.debug("REALLY Closing protocol session:" + _minaProtocolSession); + final CloseFuture future = _minaProtocolSession.close(); + future.join(LAST_WRITE_FUTURE_JOIN_TIMEOUT); + + try + { + _stateManager.changeState(AMQState.CONNECTION_CLOSED); + } + catch (AMQException e) + { + _logger.info(e.getMessage()); + } + } + + public String toString() + { + return _minaProtocolSession.getRemoteAddress() + "(" + (getAuthorizedID() == null ? "?" : getAuthorizedID().getName() + ")"); + } + + public String dump() + { + return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived; + } + + /** @return an object that can be used to identity */ + public Object getKey() + { + return _minaProtocolSession.getRemoteAddress(); + } + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers may + * be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + public String getLocalFQDN() + { + SocketAddress address = _minaProtocolSession.getLocalAddress(); + // we use the vmpipe address in some tests hence the need for this rather ugly test. The host + // information is used by SASL primary. + if (address instanceof InetSocketAddress) + { + return ((InetSocketAddress) address).getHostName(); + } + else if (address instanceof VmPipeAddress) + { + return "vmpipe:" + ((VmPipeAddress) address).getPort(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + } + + public SaslServer getSaslServer() + { + return _saslServer; + } + + public void setSaslServer(SaslServer saslServer) + { + _saslServer = saslServer; + } + + public FieldTable getClientProperties() + { + return _clientProperties; + } + + public void setClientProperties(FieldTable clientProperties) + { + _clientProperties = clientProperties; + if (_clientProperties != null) + { + if (_clientProperties.getString(CLIENT_PROPERTIES_INSTANCE) != null) + { + setContextKey(new AMQShortString(_clientProperties.getString(CLIENT_PROPERTIES_INSTANCE))); + } + + if (_clientProperties.getString(ClientProperties.version.toString()) != null) + { + _clientVersion = new AMQShortString(_clientProperties.getString(ClientProperties.version.toString())); + } + } + _sessionIdentifier = new ProtocolSessionIdentifier(this); + } + + private void setProtocolVersion(ProtocolVersion pv) + { + _protocolVersion = pv; + + _protocolOutputConverter = ProtocolOutputConverterRegistry.getConverter(this); + _dispatcher = ServerMethodDispatcherImpl.createMethodDispatcher(_stateManager, _protocolVersion); + } + + public byte getProtocolMajorVersion() + { + return _protocolVersion.getMajorVersion(); + } + + public ProtocolVersion getProtocolVersion() + { + return _protocolVersion; + } + + public byte getProtocolMinorVersion() + { + return _protocolVersion.getMinorVersion(); + } + + public boolean isProtocolVersion(byte major, byte minor) + { + return (getProtocolMajorVersion() == major) && (getProtocolMinorVersion() == minor); + } + + public MethodRegistry getRegistry() + { + return getMethodRegistry(); + } + + public Object getClientIdentifier() + { + return (_minaProtocolSession != null) ? _minaProtocolSession.getRemoteAddress() : null; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) throws AMQException + { + _virtualHost = virtualHost; + + _virtualHost.getConnectionRegistry().registerConnection(this); + + _managedObject = createMBean(); + _managedObject.register(); + } + + public void addSessionCloseTask(Task task) + { + _taskList.add(task); + } + + public void removeSessionCloseTask(Task task) + { + _taskList.remove(task); + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return _protocolOutputConverter; + } + + public void setAuthorizedID(Principal authorizedID) + { + _authorizedID = authorizedID; + } + + public Principal getAuthorizedID() + { + return _authorizedID; + } + + public MethodRegistry getMethodRegistry() + { + return MethodRegistry.getMethodRegistry(getProtocolVersion()); + } + + public MethodDispatcher getMethodDispatcher() + { + return _dispatcher; + } + + public ProtocolSessionIdentifier getSessionIdentifier() + { + return _sessionIdentifier; + } + + public String getClientVersion() + { + return (_clientVersion == null) ? null : _clientVersion.toString(); + } + + public void setSender(Sender<java.nio.ByteBuffer> sender) + { + // No-op, interface munging between this and AMQProtocolSession + } + + public void init() + { + // No-op, interface munging between this and AMQProtocolSession + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java new file mode 100644 index 0000000000..a7599a3e0d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.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.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * AMQNoMethodHandlerException represents the case where no method handler exists to handle an AQMP method.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to handle an AMQP method.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Missing method handler. Unlikely to ever happen, and if it does its a coding error. Consider replacing with a
+ * Runtime.
+ */
+public class AMQNoMethodHandlerException extends AMQException
+{
+ public AMQNoMethodHandlerException(AMQMethodEvent<AMQMethodBody> evt)
+ {
+ super("AMQMethodEvent " + evt + " was not processed by any listener on Broker.");
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java new file mode 100644 index 0000000000..0dbefd8798 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java @@ -0,0 +1,284 @@ +/* + * + * 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.protocol; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.filter.codec.QpidProtocolCodecFilter; +import org.apache.mina.filter.executor.ExecutorFilter; +import org.apache.mina.util.SessionUtil; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQProtocolHeaderException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.ssl.SSLContextFactory; + +/** + * The protocol handler handles "protocol events" for all connections. The state + * associated with an individual connection is accessed through the protocol session. + * + * We delegate all frame (message) processing to the AMQProtocolSession which wraps + * the state for the connection. + */ +public class AMQPFastProtocolHandler extends IoHandlerAdapter +{ + private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class); + + private final IApplicationRegistry _applicationRegistry; + + private final int BUFFER_READ_LIMIT_SIZE; + private final int BUFFER_WRITE_LIMIT_SIZE; + + public AMQPFastProtocolHandler(Integer applicationRegistryInstance) + { + this(ApplicationRegistry.getInstance(applicationRegistryInstance)); + } + + public AMQPFastProtocolHandler(IApplicationRegistry applicationRegistry) + { + _applicationRegistry = applicationRegistry; + + // Read the configuration from the application registry + BUFFER_READ_LIMIT_SIZE = _applicationRegistry.getConfiguration().getBufferReadLimit(); + BUFFER_WRITE_LIMIT_SIZE = _applicationRegistry.getConfiguration().getBufferWriteLimit(); + + _logger.debug("AMQPFastProtocolHandler created"); + } + + protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler) + { + this(handler._applicationRegistry); + } + + public void sessionCreated(IoSession protocolSession) throws Exception + { + SessionUtil.initialize(protocolSession); + final AMQCodecFactory codecFactory = new AMQCodecFactory(true); + + createSession(protocolSession, _applicationRegistry, codecFactory); + _logger.info("Protocol session created for:" + protocolSession.getRemoteAddress()); + + final QpidProtocolCodecFilter pcf = new QpidProtocolCodecFilter(codecFactory); + final ServerConfiguration config = _applicationRegistry.getConfiguration(); + + String keystorePath = config.getKeystorePath(); + String keystorePassword = config.getKeystorePassword(); + String certType = config.getCertType(); + SSLContextFactory sslContextFactory = null; + boolean isSsl = false; + if (config.getEnableSSL() && isSSLClient(config, protocolSession)) + { + sslContextFactory = new SSLContextFactory(keystorePath, keystorePassword, certType); + isSsl = true; + } + if (config.getEnableExecutorPool()) + { + if (isSsl) + { + protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter", + new SSLFilter(sslContextFactory.buildServerContext())); + } + protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf); + } + else + { + protocolSession.getFilterChain().addLast("protocolFilter", pcf); + if (isSsl) + { + protocolSession.getFilterChain().addBefore("protocolFilter", "sslFilter", + new SSLFilter(sslContextFactory.buildServerContext())); + } + } + + if (ApplicationRegistry.getInstance().getConfiguration().getProtectIOEnabled()) + { + try + { +// //Add IO Protection Filters + IoFilterChain chain = protocolSession.getFilterChain(); + + + protocolSession.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter()); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(BUFFER_READ_LIMIT_SIZE); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + writefilter.setMaximumConnectionBufferSize(BUFFER_WRITE_LIMIT_SIZE); + writefilter.attach(chain); + + protocolSession.getFilterChain().remove("tempExecutorFilterForFilterBuilder"); + _logger.info("Using IO Read/Write Filter Protection"); + } + catch (Exception e) + { + _logger.error("Unable to attach IO Read/Write Filter Protection :" + e.getMessage()); + } + } + } + + /** Separated into its own, protected, method to allow easier reuse */ + protected void createSession(IoSession session, IApplicationRegistry applicationRegistry, AMQCodecFactory codec) throws AMQException + { + new AMQMinaProtocolSession(session, applicationRegistry.getVirtualHostRegistry(), codec); + } + + public void sessionOpened(IoSession protocolSession) throws Exception + { + _logger.info("Session opened for:" + protocolSession.getRemoteAddress()); + } + + public void sessionClosed(IoSession protocolSession) throws Exception + { + _logger.info("Protocol Session closed for:" + protocolSession.getRemoteAddress()); + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + //fixme -- this can be null + if (amqProtocolSession != null) + { + try + { + amqProtocolSession.closeSession(); + } + catch (AMQException e) + { + _logger.error("Caught AMQException whilst closingSession:" + e); + } + } + } + + public void sessionIdle(IoSession session, IdleStatus status) throws Exception + { + _logger.debug("Protocol Session [" + this + "] idle: " + status + " :for:" + session.getRemoteAddress()); + if (IdleStatus.WRITER_IDLE.equals(status)) + { + //write heartbeat frame: + session.write(HeartbeatBody.FRAME); + } + else if (IdleStatus.READER_IDLE.equals(status)) + { + //failover: + throw new IOException("Timed out while waiting for heartbeat from peer."); + } + + } + + public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception + { + AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + if (throwable instanceof AMQProtocolHeaderException) + { + + protocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + + protocolSession.close(); + + _logger.error("Error in protocol initiation " + session + ":" + protocolSession.getRemoteAddress() + " :" + throwable.getMessage(), throwable); + } + else if (throwable instanceof IOException) + { + _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable); + } + else + { + _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable); + + + MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(session.getProtocolVersion()); + ConnectionCloseBody closeBody = methodRegistry.createConnectionCloseBody(200,new AMQShortString(throwable.getMessage()),0,0); + + protocolSession.write(closeBody.generateFrame(0)); + + protocolSession.close(); + } + } + + /** + * Invoked when a message is received on a particular protocol session. Note that a + * protocol session is directly tied to a particular physical connection. + * + * @param protocolSession the protocol session that received the message + * @param message the message itself (i.e. a decoded frame) + * + * @throws Exception if the message cannot be processed + */ + public void messageReceived(IoSession protocolSession, Object message) throws Exception + { + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + + if (message instanceof AMQDataBlock) + { + amqProtocolSession.dataBlockReceived((AMQDataBlock) message); + + } + else if (message instanceof ByteBuffer) + { + throw new IllegalStateException("Handed undecoded ByteBuffer buf = " + message); + } + else + { + throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message); + } + } + + /** + * Called after a message has been sent out on a particular protocol session + * + * @param protocolSession the protocol session (i.e. connection) on which this + * message was sent + * @param object the message (frame) that was encoded and sent + * + * @throws Exception if we want to indicate an error + */ + public void messageSent(IoSession protocolSession, Object object) throws Exception + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Message sent: " + object); + } + } + + protected boolean isSSLClient(ServerConfiguration connectionConfig, + IoSession protocolSession) + { + InetSocketAddress addr = (InetSocketAddress) protocolSession.getLocalAddress(); + return addr.getPort() == connectionConfig.getSSLPort(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.java new file mode 100644 index 0000000000..07c153bfe8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQPProtocolProvider.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.protocol; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +/** + * The protocol provide's role is to encapsulate the initialisation of the protocol handler. + * + * The protocol handler (see AMQPFastProtocolHandler class) handles protocol events + * such as connection closing or a frame being received. It can either do this directly + * or pass off to the protocol session in the cases where state information is required to + * deal with the event. + * + */ +public class AMQPProtocolProvider +{ + /** + * Handler for protocol events + */ + private AMQPFastProtocolHandler _handler; + + public AMQPProtocolProvider() + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(); + _handler = new AMQPFastProtocolHandler(registry); + } + + public AMQPFastProtocolHandler getHandler() + { + return _handler; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java new file mode 100644 index 0000000000..1bac601225 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java @@ -0,0 +1,207 @@ +/* + * + * 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.protocol; + +import javax.security.sasl.SaslServer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.security.Principal; + + +public interface AMQProtocolSession extends AMQVersionAwareProtocolSession +{ + + public static final class ProtocolSessionIdentifier + { + private final Object _sessionIdentifier; + private final Object _sessionInstance; + + ProtocolSessionIdentifier(AMQProtocolSession session) + { + _sessionIdentifier = session.getClientIdentifier(); + _sessionInstance = session.getClientProperties() == null ? null : session.getClientProperties().getObject(ClientProperties.instance.toAMQShortString()); + } + + public Object getSessionIdentifier() + { + return _sessionIdentifier; + } + + public Object getSessionInstance() + { + return _sessionInstance; + } + } + + public static interface Task + { + public void doTask(AMQProtocolSession session) throws AMQException; + } + + /** + * Called when a protocol data block is received + * + * @param message the data block that has been received + * + * @throws Exception if processing the datablock fails + */ + void dataBlockReceived(AMQDataBlock message) throws Exception; + + /** + * Get the context key associated with this session. Context key is described in the AMQ protocol specification (RFC + * 6). + * + * @return the context key + */ + AMQShortString getContextKey(); + + /** + * Set the context key associated with this session. Context key is described in the AMQ protocol specification (RFC + * 6). + * + * @param contextKey the context key + */ + void setContextKey(AMQShortString contextKey); + + /** + * Get the channel for this session associated with the specified id. A channel id is unique per connection (i.e. + * per session). + * + * @param channelId the channel id which must be valid + * + * @return null if no channel exists, the channel otherwise + */ + AMQChannel getChannel(int channelId) throws AMQException; + + /** + * Associate a channel with this session. + * + * @param channel the channel to associate with this session. It is an error to associate the same channel with more + * than one session but this is not validated. + */ + void addChannel(AMQChannel channel) throws AMQException; + + /** + * Close a specific channel. This will remove any resources used by the channel, including: <ul><li>any queue + * subscriptions (this may in turn remove queues if they are auto delete</li> </ul> + * + * @param channelId id of the channel to close + * + * @throws org.apache.qpid.AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + void closeChannel(int channelId) throws AMQException; + + /** + * Markes the specific channel as closed. This will release the lock for that channel id so a new channel can be + * created on that id. + * + * @param channelId id of the channel to close + */ + void closeChannelOk(int channelId); + + /** + * Check to see if this chanel is closing + * + * @param channelId id to check + * @return boolean with state of channel awaiting closure + */ + boolean channelAwaitingClosure(int channelId); + + /** + * Remove a channel from the session but do not close it. + * + * @param channelId + */ + void removeChannel(int channelId); + + /** + * Initialise heartbeats on the session. + * + * @param delay delay in seconds (not ms) + */ + void initHeartbeats(int delay); + + /** This must be called when the session is _closed in order to free up any resources managed by the session. */ + void closeSession() throws AMQException; + + /** This must be called to close the session in order to free up any resources managed by the session. */ + void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) throws AMQException; + + + /** @return a key that uniquely identifies this session */ + Object getKey(); + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers may + * be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + String getLocalFQDN(); + + /** @return the sasl server that can perform authentication for this session. */ + SaslServer getSaslServer(); + + /** + * Set the sasl server that is to perform authentication for this session. + * + * @param saslServer + */ + void setSaslServer(SaslServer saslServer); + + + FieldTable getClientProperties(); + + void setClientProperties(FieldTable clientProperties); + + Object getClientIdentifier(); + + VirtualHost getVirtualHost(); + + void setVirtualHost(VirtualHost virtualHost) throws AMQException; + + void addSessionCloseTask(Task task); + + void removeSessionCloseTask(Task task); + + public ProtocolOutputConverter getProtocolOutputConverter(); + + void setAuthorizedID(Principal authorizedID); + + /** @return a Principal that was used to authorized this session */ + Principal getAuthorizedID(); + + public MethodRegistry getMethodRegistry(); + + public MethodDispatcher getMethodDispatcher(); + + public ProtocolSessionIdentifier getSessionIdentifier(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java new file mode 100644 index 0000000000..5dd3cc075a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java @@ -0,0 +1,304 @@ +/* + * + * 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. + * + */ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import java.util.Date; +import java.util.List; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MBeanNotificationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.Notification; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.ManagedObject; + +/** + * This MBean class implements the management interface. In order to make more attributes, operations and notifications + * available over JMX simply augment the ManagedConnection interface and add the appropriate implementation here. + */ +@MBeanDescription("Management Bean for an AMQ Broker Connection") +public class AMQProtocolSessionMBean extends AMQManagedObject implements ManagedConnection +{ + private AMQMinaProtocolSession _session = null; + private String _name = null; + + // openmbean data types for representing the channel attributes + private static final String[] _channelAtttibuteNames = + { "Channel Id", "Transactional", "Default Queue", "Unacknowledged Message Count" }; + private static final String[] _indexNames = { _channelAtttibuteNames[0] }; + private static final OpenType[] _channelAttributeTypes = + { SimpleType.INTEGER, SimpleType.BOOLEAN, SimpleType.STRING, SimpleType.INTEGER }; + private static CompositeType _channelType = null; // represents the data type for channel data + private static TabularType _channelsType = null; // Data type for list of channels type + private static final AMQShortString BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION = + new AMQShortString("Broker Management Console has closed the connection."); + + @MBeanConstructor("Creates an MBean exposing an AMQ Broker Connection") + public AMQProtocolSessionMBean(AMQMinaProtocolSession session) throws NotCompliantMBeanException, OpenDataException + { + super(ManagedConnection.class, ManagedConnection.TYPE, ManagedConnection.VERSION); + _session = session; + String remote = getRemoteAddress(); + remote = "anonymous".equals(remote) ? (remote + hashCode()) : remote; + _name = jmxEncode(new StringBuffer(remote), 0).toString(); + init(); + } + + static + { + try + { + init(); + } + catch (JMException ex) + { + // This is not expected to ever occur. + throw new RuntimeException("Got JMException in static initializer.", ex); + } + } + + /** + * initialises the openmbean data types + */ + private static void init() throws OpenDataException + { + _channelType = + new CompositeType("Channel", "Channel Details", _channelAtttibuteNames, _channelAtttibuteNames, + _channelAttributeTypes); + _channelsType = new TabularType("Channels", "Channels", _channelType, _indexNames); + } + + public String getClientId() + { + return (_session.getContextKey() == null) ? null : _session.getContextKey().toString(); + } + + public String getAuthorizedId() + { + return (_session.getAuthorizedID() != null ) ? _session.getAuthorizedID().getName() : null; + } + + public String getVersion() + { + return (_session.getClientVersion() == null) ? null : _session.getClientVersion().toString(); + } + + public Date getLastIoTime() + { + return new Date(_session.getIOSession().getLastIoTime()); + } + + public String getRemoteAddress() + { + return _session.getIOSession().getRemoteAddress().toString(); + } + + public ManagedObject getParentObject() + { + return _session.getVirtualHost().getManagedObject(); + } + + public Long getWrittenBytes() + { + return _session.getIOSession().getWrittenBytes(); + } + + public Long getReadBytes() + { + return _session.getIOSession().getReadBytes(); + } + + public Long getMaximumNumberOfChannels() + { + return _session.getMaximumNumberOfChannels(); + } + + public void setMaximumNumberOfChannels(Long value) + { + _session.setMaximumNumberOfChannels(value); + } + + public String getObjectInstanceName() + { + return _name; + } + + /** + * commits transactions for a transactional channel + * + * @param channelId + * @throws JMException if channel with given id doesn't exist or if commit fails + */ + public void commitTransactions(int channelId) throws JMException + { + try + { + AMQChannel channel = _session.getChannel(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + + _session.commitTransactions(channel); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * rollsback the transactions for a transactional channel + * + * @param channelId + * @throws JMException if channel with given id doesn't exist or if rollback fails + */ + public void rollbackTransactions(int channelId) throws JMException + { + try + { + AMQChannel channel = _session.getChannel(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + + _session.rollbackTransactions(channel); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Creates the list of channels in tabular form from the _channelMap. + * + * @return list of channels in tabular form. + * @throws OpenDataException + */ + public TabularData channels() throws OpenDataException + { + TabularDataSupport channelsList = new TabularDataSupport(_channelsType); + List<AMQChannel> list = _session.getChannels(); + + for (AMQChannel channel : list) + { + Object[] itemValues = + { + channel.getChannelId(), channel.isTransactional(), + (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getName().asString() : null, + channel.getUnacknowledgedMessageMap().size() + }; + + CompositeData channelData = new CompositeDataSupport(_channelType, _channelAtttibuteNames, itemValues); + channelsList.put(channelData); + } + + return channelsList; + } + + /** + * closes the connection. The administrator can use this management operation to close connection to free up + * resources. + * @throws JMException + */ + public void closeConnection() throws JMException + { + + MethodRegistry methodRegistry = _session.getMethodRegistry(); + ConnectionCloseBody responseBody = + methodRegistry.createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), + // replyCode + BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION, + // replyText, + 0, + 0); + + _session.writeFrame(responseBody.generateFrame(0)); + + try + { + _session.closeSession(); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED }; + String name = MonitorNotification.class.getName(); + String description = "Channel count has reached threshold value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description); + + return new MBeanNotificationInfo[] { info1 }; + } + + public void notifyClients(String notificationMsg) + { + Notification n = + new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber, + System.currentTimeMillis(), notificationMsg); + _broadcaster.sendNotification(n); + } + +} // End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java new file mode 100644 index 0000000000..2abcecb6de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ExchangeInitialiser.java @@ -0,0 +1,51 @@ +/* + * + * 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.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeType; + +public class ExchangeInitialiser +{ + public void initialise(ExchangeFactory factory, ExchangeRegistry registry) throws AMQException{ + for (ExchangeType<? extends Exchange> type : factory.getRegisteredTypes()) + { + define (registry, factory, type.getDefaultExchangeName(), type.getName()); + } + + define(registry, factory, ExchangeDefaults.DEFAULT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS); + registry.setDefaultExchange(registry.getExchange(ExchangeDefaults.DEFAULT_EXCHANGE_NAME)); + } + + private void define(ExchangeRegistry r, ExchangeFactory f, + AMQShortString name, AMQShortString type) throws AMQException + { + if(r.getExchange(name)== null) + { + r.registerExchange(f.createExchange(name, type, true, false, 0)); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java new file mode 100644 index 0000000000..e75b09a0cb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ManagedConnection.java @@ -0,0 +1,136 @@ +/* + * + * 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.protocol; + +import java.io.IOException; +import java.util.Date; +import java.security.Principal; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.TabularData; + +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; + +/** + * The management interface exposed to allow management of Connections. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedConnection +{ + static final String TYPE = "Connection"; + static final int VERSION = 1; + + @MBeanAttribute(name = "ClientId", description = "Client Id") + String getClientId(); + + @MBeanAttribute(name = "AuthorizedId", description = "User Name") + String getAuthorizedId(); + + @MBeanAttribute(name = "Version", description = "Client Version") + String getVersion(); + + /** + * Tells the remote address of this connection. + * @return remote address + */ + @MBeanAttribute(name="RemoteAddress", description=TYPE + " Address") + String getRemoteAddress(); + + /** + * Tells the last time, the IO operation was done. + * @return last IO time. + */ + @MBeanAttribute(name="LastIOTime", description="The last time, the IO operation was done") + Date getLastIoTime(); + + /** + * Tells the total number of bytes written till now. + * @return number of bytes written. + * + @MBeanAttribute(name="WrittenBytes", description="The total number of bytes written till now") + Long getWrittenBytes(); + */ + /** + * Tells the total number of bytes read till now. + * @return number of bytes read. + * + @MBeanAttribute(name="ReadBytes", description="The total number of bytes read till now") + Long getReadBytes(); + */ + + /** + * Threshold high value for no of channels. This is useful in setting notifications or + * taking required action is there are more channels being created. + * @return threshold limit for no of channels + */ + Long getMaximumNumberOfChannels(); + + /** + * Sets the threshold high value for number of channels for a connection + * @param value + */ + @MBeanAttribute(name="MaximumNumberOfChannels", description="The threshold high value for number of channels for this connection") + void setMaximumNumberOfChannels(Long value); + + //********** Operations *****************// + + /** + * channel details of all the channels opened for this connection. + * @return general channel details + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="channels", description="Channel details for this connection") + TabularData channels() throws IOException, JMException; + + /** + * Commits the transactions if the channel is transactional. + * @param channelId + * @throws JMException + */ + @MBeanOperation(name="commitTransaction", + description="Commits the transactions for given channel Id, if the channel is transactional", + impact= MBeanOperationInfo.ACTION) + void commitTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException; + + /** + * Rollsback the transactions if the channel is transactional. + * @param channelId + * @throws JMException + */ + @MBeanOperation(name="rollbackTransactions", + description="Rollsback the transactions for given channel Id, if the channel is transactional", + impact= MBeanOperationInfo.ACTION) + void rollbackTransactions(@MBeanOperationParameter(name="channel Id", description="channel Id")int channelId) throws JMException; + + /** + * Closes all the related channels and unregisters this connection from managed objects. + */ + @MBeanOperation(name="closeConnection", + description="Closes this connection and all related channels", + impact= MBeanOperationInfo.ACTION) + void closeConnection() throws Exception; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.java new file mode 100644 index 0000000000..6e72aa062f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/UnknnownMessageTypeException.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.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQDataBlock;
+
+/**
+ * UnknnownMessageTypeException represents a failure when Mina passes an unexpected frame type.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to cast a frame to its expected type.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Seems like this exception was created to handle an unsafe type cast that will never happen in practice. Would
+ * be better just to leave that as a ClassCastException. However, check the framing layer catches this error
+ * first.
+ */
+public class UnknnownMessageTypeException extends AMQException
+{
+ public UnknnownMessageTypeException(AMQDataBlock message)
+ {
+ super("Unknown message type: " + message.getClass().getName() + ": " + message);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java new file mode 100644 index 0000000000..a485649410 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessage.java @@ -0,0 +1,482 @@ +/* + * + * 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.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQBody; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.txn.TransactionalContext; + + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A deliverable message. + */ +public class AMQMessage implements Filterable<AMQException> +{ + /** Used for debugging purposes. */ + private static final Logger _log = Logger.getLogger(AMQMessage.class); + + private final AtomicInteger _referenceCount = new AtomicInteger(1); + + private final AMQMessageHandle _messageHandle; + + /** Holds the transactional context in which this message is being processed. */ + private StoreContext _storeContext; + + /** Flag to indicate that this message requires 'immediate' delivery. */ + + private static final byte IMMEDIATE = 0x01; + + /** + * Flag to indicate whether this message has been delivered to a consumer. Used in implementing return functionality + * for messages published with the 'immediate' flag. + */ + + private static final byte DELIVERED_TO_CONSUMER = 0x02; + + private byte _flags = 0; + + private long _expiration; + + private final long _size; + + private AMQProtocolSession.ProtocolSessionIdentifier _sessionIdentifier; + private static final byte IMMEDIATE_AND_DELIVERED = (byte) (IMMEDIATE | DELIVERED_TO_CONSUMER); + + + + /** + * Used to iterate through all the body frames associated with this message. Will not keep all the data in memory + * therefore is memory-efficient. + */ + private class BodyFrameIterator implements Iterator<AMQDataBlock> + { + private int _channel; + + private int _index = -1; + private AMQProtocolSession _protocolSession; + + private BodyFrameIterator(AMQProtocolSession protocolSession, int channel) + { + _channel = channel; + _protocolSession = protocolSession; + } + + public boolean hasNext() + { + try + { + return _index < (_messageHandle.getBodyCount(getStoreContext()) - 1); + } + catch (AMQException e) + { + _log.error("Unable to get body count: " + e, e); + + return false; + } + } + + public AMQDataBlock next() + { + try + { + + AMQBody cb = + getProtocolVersionMethodConverter().convertToBody(_messageHandle.getContentChunk(getStoreContext(), + ++_index)); + + return new AMQFrame(_channel, cb); + } + catch (AMQException e) + { + // have no choice but to throw a runtime exception + throw new RuntimeException("Error getting content body: " + e, e); + } + + } + + private ProtocolVersionMethodConverter getProtocolVersionMethodConverter() + { + return _protocolSession.getMethodRegistry().getProtocolVersionMethodConverter(); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + public void clearStoreContext() + { + _storeContext = new StoreContext(); + } + + public StoreContext getStoreContext() + { + return _storeContext; + } + + private class BodyContentIterator implements Iterator<ContentChunk> + { + + private int _index = -1; + + public boolean hasNext() + { + try + { + return _index < (_messageHandle.getBodyCount(getStoreContext()) - 1); + } + catch (AMQException e) + { + _log.error("Error getting body count: " + e, e); + + return false; + } + } + + public ContentChunk next() + { + try + { + return _messageHandle.getContentChunk(getStoreContext(), ++_index); + } + catch (AMQException e) + { + throw new RuntimeException("Error getting content body: " + e, e); + } + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + } + + + + /** + * Used when recovering, i.e. when the message store is creating references to messages. In that case, the normal + * enqueue/routingComplete is not done since the recovery process is responsible for routing the messages to + * queues. + * + * @param messageId + * @param store + * @param factory + * + * @throws AMQException + */ + public AMQMessage(Long messageId, MessageStore store, MessageHandleFactory factory, TransactionalContext txnConext) + throws AMQException + { + _messageHandle = factory.createMessageHandle(messageId, store, true); + _storeContext = txnConext.getStoreContext(); + _size = _messageHandle.getBodySize(txnConext.getStoreContext()); + } + + /** + * Used when recovering, i.e. when the message store is creating references to messages. In that case, the normal + * enqueue/routingComplete is not done since the recovery process is responsible for routing the messages to + * queues. + * + * @param messageHandle + * + * @throws AMQException + */ + public AMQMessage( + AMQMessageHandle messageHandle, + StoreContext storeConext, + MessagePublishInfo info) + throws AMQException + { + _messageHandle = messageHandle; + _storeContext = storeConext; + + if(info.isImmediate()) + { + _flags |= IMMEDIATE; + } + _size = messageHandle.getBodySize(storeConext); + + } + + + protected AMQMessage(AMQMessage msg) throws AMQException + { + _messageHandle = msg._messageHandle; + _storeContext = msg._storeContext; + _flags = msg._flags; + _size = msg._size; + + } + + + public String debugIdentity() + { + return "(HC:" + System.identityHashCode(this) + " ID:" + getMessageId() + " Ref:" + _referenceCount.get() + ")"; + } + + public void setExpiration(final long expiration) + { + + _expiration = expiration; + + } + + public boolean isReferenced() + { + return _referenceCount.get() > 0; + } + + public Iterator<AMQDataBlock> getBodyFrameIterator(AMQProtocolSession protocolSession, int channel) + { + return new BodyFrameIterator(protocolSession, channel); + } + + public Iterator<ContentChunk> getContentBodyIterator() + { + return new BodyContentIterator(); + } + + public ContentHeaderBody getContentHeaderBody() throws AMQException + { + return _messageHandle.getContentHeaderBody(getStoreContext()); + } + + + + public Long getMessageId() + { + return _messageHandle.getMessageId(); + } + + /** + * Creates a long-lived reference to this message, and increments the count of such references, as an atomic + * operation. + */ + public AMQMessage takeReference() + { + incrementReference(); // _referenceCount.incrementAndGet(); + + return this; + } + + public boolean incrementReference() + { + return incrementReference(1); + } + + /* Threadsafe. Increment the reference count on the message. */ + public boolean incrementReference(int count) + { + if(_referenceCount.addAndGet(count) <= 1) + { + _referenceCount.addAndGet(-count); + return false; + } + else + { + return true; + } + + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + * + * @param storeContext + * + * @throws MessageCleanupException when an attempt was made to remove the message from the message store and that + * failed + */ + public void decrementReference(StoreContext storeContext) throws MessageCleanupException + { + + int count = _referenceCount.decrementAndGet(); + + // note that the operation of decrementing the reference count and then removing the message does not + // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after + // the message has been passed to all queues. i.e. we are + // not relying on the all the increments having taken place before the delivery manager decrements. + if (count == 0) + { + // set the reference count way below 0 so that we can detect that the message has been deleted + // this is to guard against the message being spontaneously recreated (from the mgmt console) + // by copying from other queues at the same time as it is being removed. + _referenceCount.set(Integer.MIN_VALUE/2); + + try + { + // must check if the handle is null since there may be cases where we decide to throw away a message + // and the handle has not yet been constructed + if (_messageHandle != null) + { + _messageHandle.removeMessage(storeContext); + } + } + catch (AMQException e) + { + // to maintain consistency, we revert the count + incrementReference(); + throw new MessageCleanupException(getMessageId(), e); + } + } + else + { + if (count < 0) + { + throw new MessageCleanupException("Reference count for message id " + debugIdentity() + + " has gone below 0."); + } + } + } + + + /** + * Called selectors to determin if the message has already been sent + * + * @return _deliveredToConsumer + */ + public boolean getDeliveredToConsumer() + { + return (_flags & DELIVERED_TO_CONSUMER) != 0; + } + + public boolean isPersistent() throws AMQException + { + return _messageHandle.isPersistent(); + } + + /** + * Called to enforce the 'immediate' flag. + * + * @returns true if the message is marked for immediate delivery but has not been marked as delivered + * to a consumer + */ + public boolean immediateAndNotDelivered() + { + + return (_flags & IMMEDIATE_AND_DELIVERED) == IMMEDIATE; + + } + + public MessagePublishInfo getMessagePublishInfo() throws AMQException + { + return _messageHandle.getMessagePublishInfo(getStoreContext()); + } + + public boolean isRedelivered() + { + return _messageHandle.isRedelivered(); + } + + public void setRedelivered(boolean redelivered) + { + _messageHandle.setRedelivered(redelivered); + } + + public long getArrivalTime() + { + return _messageHandle.getArrivalTime(); + } + + /** + * Checks to see if the message has expired. If it has the message is dequeued. + * + * @param queue The queue to check the expiration against. (Currently not used) + * + * @return true if the message has expire + * + * @throws AMQException + */ + public boolean expired(AMQQueue queue) throws AMQException + { + + if (_expiration != 0L) + { + long now = System.currentTimeMillis(); + + return (now > _expiration); + } + + return false; + } + + /** + * Called when this message is delivered to a consumer. (used to implement the 'immediate' flag functionality). + * And for selector efficiency. + */ + public void setDeliveredToConsumer() + { + _flags |= DELIVERED_TO_CONSUMER; + } + + + + public AMQMessageHandle getMessageHandle() + { + return _messageHandle; + } + + public long getSize() + { + return _size; + + } + + public Object getPublisherClientInstance() + { + return _sessionIdentifier.getSessionInstance(); + } + + public Object getPublisherIdentifier() + { + return _sessionIdentifier.getSessionIdentifier(); + } + + public void setClientIdentifier(final AMQProtocolSession.ProtocolSessionIdentifier sessionIdentifier) + { + _sessionIdentifier = sessionIdentifier; + } + + + public String toString() + { + // return "Message[" + debugIdentity() + "]: " + _messageId + "; ref count: " + _referenceCount + "; taken : " + + // _taken + " by :" + _takenBySubcription; + + return "Message[" + debugIdentity() + "]: " + getMessageId() + "; ref count: " + _referenceCount; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java new file mode 100644 index 0000000000..0ddd4e4d92 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQMessageHandle.java @@ -0,0 +1,79 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +/** + * A pluggable way of getting message data. Implementations can provide intelligent caching for example or + * even no caching at all to minimise the broker memory footprint. + */ +public interface AMQMessageHandle +{ + ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException; + + /** + * + * @return the messageId for the message associated with this handle + */ + Long getMessageId(); + + + /** + * @return the number of body frames associated with this message + */ + int getBodyCount(StoreContext context) throws AMQException; + + /** + * @return the size of the body + */ + long getBodySize(StoreContext context) throws AMQException; + + /** + * Get a particular content body + * @param index the index of the body to retrieve, must be between 0 and getBodyCount() - 1 + * @return a content body + * @throws IllegalArgumentException if the index is invalid + */ + ContentChunk getContentChunk(StoreContext context, int index) throws IllegalArgumentException, AMQException; + + void addContentBodyFrame(StoreContext storeContext, ContentChunk contentBody, boolean isLastContentBody) throws AMQException; + + MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException; + + boolean isRedelivered(); + + void setRedelivered(boolean redelivered); + + boolean isPersistent(); + + void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo messagePublishInfo, + ContentHeaderBody contentHeaderBody) + throws AMQException; + + void removeMessage(StoreContext storeContext) throws AMQException; + + long getArrivalTime(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java new file mode 100644 index 0000000000..bb6ce65d42 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java @@ -0,0 +1,70 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.AMQException; + +public class AMQPriorityQueue extends SimpleAMQQueue +{ + protected AMQPriorityQueue(final AMQShortString name, + final boolean durable, + final AMQShortString owner, + final boolean autoDelete, + final VirtualHost virtualHost, + int priorities) + throws AMQException + { + super(name, durable, owner, autoDelete, virtualHost, new PriorityQueueList.Factory(priorities)); + } + + public int getPriorities() + { + return ((PriorityQueueList) _entries).getPriorities(); + } + + @Override + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // check that all subscriptions are not in advance of the entry + SubscriptionList.SubscriptionNodeIterator subIter = _subscriptionList.iterator(); + while(subIter.advance() && !entry.isAcquired()) + { + final Subscription subscription = subIter.getNode().getSubscription(); + QueueEntry subnode = subscription.getLastSeenEntry(); + while(subnode != null && entry.compareTo(subnode) < 0 && !entry.isAcquired()) + { + if(subscription.setLastSeenEntry(subnode,entry)) + { + break; + } + else + { + subnode = subscription.getLastSeenEntry(); + } + } + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java new file mode 100644 index 0000000000..014b348822 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java @@ -0,0 +1,220 @@ +/* + * + * 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.queue; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; + +import java.util.List; +import java.util.Set; + +public interface AMQQueue extends Managable, Comparable<AMQQueue> +{ + + AMQShortString getName(); + + boolean isDurable(); + + boolean isAutoDelete(); + + AMQShortString getOwner(); + + VirtualHost getVirtualHost(); + + + void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException; + + void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException; + + List<ExchangeBinding> getExchangeBindings(); + + + void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException; + + void unregisterSubscription(final Subscription subscription) throws AMQException; + + + int getConsumerCount(); + + int getActiveConsumerCount(); + + boolean isUnused(); + + boolean isEmpty(); + + int getMessageCount(); + + int getUndeliveredMessageCount(); + + + long getQueueDepth(); + + long getReceivedMessageCount(); + + long getOldestMessageArrivalTime(); + + boolean isDeleted(); + + + int delete() throws AMQException; + + + QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException; + + void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException; + + void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException; + + + + boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException; + + + + void addQueueDeleteTask(final Task task); + + + List<QueueEntry> getMessagesOnTheQueue(); + + List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId); + + List<Long> getMessagesOnTheQueue(int num); + + List<Long> getMessagesOnTheQueue(int num, int offest); + + QueueEntry getMessageOnTheQueue(long messageId); + + + void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, + StoreContext storeContext); + + void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext); + + void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext); + + + + long getMaximumMessageSize(); + + void setMaximumMessageSize(long value); + + + long getMaximumMessageCount(); + + void setMaximumMessageCount(long value); + + + long getMaximumQueueDepth(); + + void setMaximumQueueDepth(long value); + + + long getMaximumMessageAge(); + + void setMaximumMessageAge(final long maximumMessageAge); + + + long getMinimumAlertRepeatGap(); + + void setMinimumAlertRepeatGap(long value); + + + void deleteMessageFromTop(StoreContext storeContext) throws AMQException; + + long clearQueue(StoreContext storeContext) throws AMQException; + + /** + * Checks the status of messages on the queue, purging expired ones, firing age related alerts etc. + * @throws AMQException + */ + void checkMessageStatus() throws AMQException; + + Set<NotificationCheck> getNotificationChecks(); + + void flushSubscription(final Subscription sub) throws AMQException; + + void deliverAsync(final Subscription sub); + + void deliverAsync(); + + void stop(); + + /** + * ExistingExclusiveSubscription signals a failure to create a subscription, because an exclusive subscription + * already exists. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to create a subscription, because an exclusive subscription already exists. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingExclusiveSubscription extends AMQException + { + + public ExistingExclusiveSubscription() + { + super(""); + } + } + + /** + * ExistingSubscriptionPreventsExclusive signals a failure to create an exclusize subscription, as a subscription + * already exists. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to create an exclusize subscription, as a subscription already exists. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingSubscriptionPreventsExclusive extends AMQException + { + public ExistingSubscriptionPreventsExclusive() + { + super(""); + } + } + + static interface Task + { + public void doTask(AMQQueue queue) throws AMQException; + } + + void configure(QueueConfiguration config); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java new file mode 100644 index 0000000000..84b59456cb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java @@ -0,0 +1,86 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +public class AMQQueueFactory +{ + public static final AMQShortString X_QPID_PRIORITIES = new AMQShortString("x-qpid-priorities"); + + public static AMQQueue createAMQQueueImpl(AMQShortString name, + boolean durable, + AMQShortString owner, + boolean autoDelete, + VirtualHost virtualHost, final FieldTable arguments) + throws AMQException + { + + final int priorities = arguments == null ? 1 : arguments.containsKey(X_QPID_PRIORITIES) ? arguments.getInteger(X_QPID_PRIORITIES) : 1; + + AMQQueue q = null; + if(priorities > 1) + { + q = new AMQPriorityQueue(name, durable, owner, autoDelete, virtualHost, priorities); + } + else + { + q = new SimpleAMQQueue(name, durable, owner, autoDelete, virtualHost); + } + + //Register the new queue + virtualHost.getQueueRegistry().registerQueue(q); + return q; + } + + public static AMQQueue createAMQQueueImpl(QueueConfiguration config, VirtualHost host) throws AMQException + { + AMQShortString queueName = new AMQShortString(config.getName()); + + boolean durable = config.getDurable(); + boolean autodelete = config.getAutoDelete(); + AMQShortString owner = (config.getOwner() != null) ? new AMQShortString(config.getOwner()) : null; + FieldTable arguments = null; + boolean priority = config.getPriority(); + int priorities = config.getPriorities(); + if(priority || priorities > 0) + { + if(arguments == null) + { + arguments = new FieldTable(); + } + if (priorities < 0) + { + priorities = 10; + } + arguments.put(new AMQShortString("x-qpid-priorities"), priorities); + } + + AMQQueue q = createAMQQueueImpl(queueName, durable, owner, autodelete, host, arguments); + q.configure(config); + return q; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java new file mode 100644 index 0000000000..c8ead67b1f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java @@ -0,0 +1,478 @@ +/* + * + * 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.queue; + +import org.apache.log4j.Logger; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.CommonContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.MBeanConstructor; +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.store.StoreContext; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.OperationsException; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * AMQQueueMBean is the management bean for an {@link AMQQueue}. + * + * <p/><tablse id="crc"><caption>CRC Caption</caption> + * <tr><th> Responsibilities <th> Collaborations + * </table> + */ +@MBeanDescription("Management Interface for AMQQueue") +public class AMQQueueMBean extends AMQManagedObject implements ManagedQueue, QueueNotificationListener +{ + /** Used for debugging purposes. */ + private static final Logger _logger = Logger.getLogger(AMQQueueMBean.class); + + private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm:ss.SSS z"); + + /** + * Since the MBean is not associated with a real channel we can safely create our own store context + * for use in the few methods that require one. + */ + private StoreContext _storeContext = new StoreContext(); + + private AMQQueue _queue = null; + private String _queueName = null; + // OpenMBean data types for viewMessages method + private static final String[] _msgAttributeNames = { "AMQ MessageId", "Header", "Size(bytes)", "Redelivered" }; + private static String[] _msgAttributeIndex = { _msgAttributeNames[0] }; + private static OpenType[] _msgAttributeTypes = new OpenType[4]; // AMQ message attribute types. + private static CompositeType _messageDataType = null; // Composite type for representing AMQ Message data. + private static TabularType _messagelistDataType = null; // Datatype for representing AMQ messages list. + + // OpenMBean data types for viewMessageContent method + private static CompositeType _msgContentType = null; + private static final String[] _msgContentAttributes = { "AMQ MessageId", "MimeType", "Encoding", "Content" }; + private static OpenType[] _msgContentAttributeTypes = new OpenType[4]; + + private final long[] _lastNotificationTimes = new long[NotificationCheck.values().length]; + private Notification _lastNotification = null; + + + + + @MBeanConstructor("Creates an MBean exposing an AMQQueue") + public AMQQueueMBean(AMQQueue queue) throws JMException + { + super(ManagedQueue.class, ManagedQueue.TYPE, ManagedQueue.VERSION); + _queue = queue; + _queueName = jmxEncode(new StringBuffer(queue.getName()), 0).toString(); + } + + public ManagedObject getParentObject() + { + return _queue.getVirtualHost().getManagedObject(); + } + + static + { + try + { + init(); + } + catch (JMException ex) + { + // This is not expected to ever occur. + throw new RuntimeException("Got JMException in static initializer.", ex); + } + } + + /** + * initialises the openmbean data types + */ + private static void init() throws OpenDataException + { + _msgContentAttributeTypes[0] = SimpleType.LONG; // For message id + _msgContentAttributeTypes[1] = SimpleType.STRING; // For MimeType + _msgContentAttributeTypes[2] = SimpleType.STRING; // For Encoding + _msgContentAttributeTypes[3] = new ArrayType(1, SimpleType.BYTE); // For message content + _msgContentType = + new CompositeType("Message Content", "AMQ Message Content", _msgContentAttributes, _msgContentAttributes, + _msgContentAttributeTypes); + + _msgAttributeTypes[0] = SimpleType.LONG; // For message id + _msgAttributeTypes[1] = new ArrayType(1, SimpleType.STRING); // For header attributes + _msgAttributeTypes[2] = SimpleType.LONG; // For size + _msgAttributeTypes[3] = SimpleType.BOOLEAN; // For redelivered + + _messageDataType = + new CompositeType("Message", "AMQ Message", _msgAttributeNames, _msgAttributeNames, _msgAttributeTypes); + _messagelistDataType = new TabularType("Messages", "List of messages", _messageDataType, _msgAttributeIndex); + } + + public String getObjectInstanceName() + { + return _queueName; + } + + public String getName() + { + return _queueName; + } + + public boolean isDurable() + { + return _queue.isDurable(); + } + + public String getOwner() + { + return String.valueOf(_queue.getOwner()); + } + + public boolean isAutoDelete() + { + return _queue.isAutoDelete(); + } + + public Integer getMessageCount() + { + return _queue.getMessageCount(); + } + + public Long getMaximumMessageSize() + { + return _queue.getMaximumMessageSize(); + } + + public Long getMaximumMessageAge() + { + return _queue.getMaximumMessageAge(); + } + + public void setMaximumMessageAge(Long maximumMessageAge) + { + _queue.setMaximumMessageAge(maximumMessageAge); + } + + public void setMaximumMessageSize(Long value) + { + _queue.setMaximumMessageSize(value); + } + + public Integer getConsumerCount() + { + return _queue.getConsumerCount(); + } + + public Integer getActiveConsumerCount() + { + return _queue.getActiveConsumerCount(); + } + + public Long getReceivedMessageCount() + { + return _queue.getReceivedMessageCount(); + } + + public Long getMaximumMessageCount() + { + return _queue.getMaximumMessageCount(); + } + + public void setMaximumMessageCount(Long value) + { + _queue.setMaximumMessageCount(value); + } + + /** + * returns the maximum total size of messages(bytes) in the queue. + */ + public Long getMaximumQueueDepth() + { + return _queue.getMaximumQueueDepth(); + } + + public void setMaximumQueueDepth(Long value) + { + _queue.setMaximumQueueDepth(value); + } + + /** + * returns the total size of messages(bytes) in the queue. + */ + public Long getQueueDepth() throws JMException + { + return _queue.getQueueDepth(); + } + + /** + * Checks if there is any notification to be send to the listeners + */ + public void checkForNotification(AMQMessage msg) throws AMQException + { + + final Set<NotificationCheck> notificationChecks = _queue.getNotificationChecks(); + + if(!notificationChecks.isEmpty()) + { + final long currentTime = System.currentTimeMillis(); + final long thresholdTime = currentTime - _queue.getMinimumAlertRepeatGap(); + + for (NotificationCheck check : notificationChecks) + { + if (check.isMessageSpecific() || (_lastNotificationTimes[check.ordinal()] < thresholdTime)) + { + if (check.notifyIfNecessary(msg, _queue, this)) + { + _lastNotificationTimes[check.ordinal()] = currentTime; + } + } + } + } + + } + + /** + * Sends the notification to the listeners + */ + public void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg) + { + // important : add log to the log file - monitoring tools may be looking for this + _logger.info(notification.name() + " On Queue " + queue.getName() + " - " + notificationMsg); + notificationMsg = notification.name() + " " + notificationMsg; + + _lastNotification = + new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber, + System.currentTimeMillis(), notificationMsg); + + _broadcaster.sendNotification(_lastNotification); + } + + public Notification getLastNotification() + { + return _lastNotification; + } + + /** + * @see AMQQueue#deleteMessageFromTop + */ + public void deleteMessageFromTop() throws JMException + { + try + { + _queue.deleteMessageFromTop(_storeContext); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * @see AMQQueue#clearQueue + */ + public void clearQueue() throws JMException + { + try + { + _queue.clearQueue(_storeContext); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * returns message content as byte array and related attributes for the given message id. + */ + public CompositeData viewMessageContent(long msgId) throws JMException + { + QueueEntry entry = _queue.getMessageOnTheQueue(msgId); + + if (entry == null) + { + throw new OperationsException("AMQMessage with message id = " + msgId + " is not in the " + _queueName); + } + + AMQMessage msg = entry.getMessage(); + // get message content + Iterator<ContentChunk> cBodies = msg.getContentBodyIterator(); + List<Byte> msgContent = new ArrayList<Byte>(); + while (cBodies.hasNext()) + { + ContentChunk body = cBodies.next(); + if (body.getSize() != 0) + { + if (body.getSize() != 0) + { + ByteBuffer slice = body.getData().slice(); + for (int j = 0; j < slice.limit(); j++) + { + msgContent.add(slice.get()); + } + } + } + } + + try + { + // Create header attributes list + CommonContentHeaderProperties headerProperties = + (CommonContentHeaderProperties) msg.getContentHeaderBody().properties; + String mimeType = null, encoding = null; + if (headerProperties != null) + { + AMQShortString mimeTypeShortSting = headerProperties.getContentType(); + mimeType = (mimeTypeShortSting == null) ? null : mimeTypeShortSting.toString(); + encoding = (headerProperties.getEncoding() == null) ? "" : headerProperties.getEncoding().toString(); + } + + Object[] itemValues = { msgId, mimeType, encoding, msgContent.toArray(new Byte[0]) }; + + return new CompositeDataSupport(_msgContentType, _msgContentAttributes, itemValues); + } + catch (AMQException e) + { + JMException jme = new JMException("Error creating header attributes list: " + e); + jme.initCause(e); + throw jme; + } + } + + /** + * Returns the header contents of the messages stored in this queue in tabular form. + */ + public TabularData viewMessages(int beginIndex, int endIndex) throws JMException + { + if ((beginIndex > endIndex) || (beginIndex < 1)) + { + throw new OperationsException("From Index = " + beginIndex + ", To Index = " + endIndex + + "\n\"From Index\" should be greater than 0 and less than \"To Index\""); + } + + List<QueueEntry> list = _queue.getMessagesOnTheQueue(); + TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType); + + try + { + // Create the tabular list of message header contents + for (int i = beginIndex; (i <= endIndex) && (i <= list.size()); i++) + { + AMQMessage msg = list.get(i - 1).getMessage(); + ContentHeaderBody headerBody = msg.getContentHeaderBody(); + // Create header attributes list + String[] headerAttributes = getMessageHeaderProperties(headerBody); + Object[] itemValues = { msg.getMessageId(), headerAttributes, headerBody.bodySize, msg.isRedelivered() }; + CompositeData messageData = new CompositeDataSupport(_messageDataType, _msgAttributeNames, itemValues); + _messageList.put(messageData); + } + } + catch (AMQException e) + { + JMException jme = new JMException("Error creating message contents: " + e); + jme.initCause(e); + throw jme; + } + + return _messageList; + } + + private String[] getMessageHeaderProperties(ContentHeaderBody headerBody) + { + List<String> list = new ArrayList<String>(); + BasicContentHeaderProperties headerProperties = (BasicContentHeaderProperties) headerBody.properties; + list.add("reply-to = " + headerProperties.getReplyToAsString()); + list.add("propertyFlags = " + headerProperties.getPropertyFlags()); + list.add("ApplicationID = " + headerProperties.getAppIdAsString()); + list.add("ClusterID = " + headerProperties.getClusterIdAsString()); + list.add("UserId = " + headerProperties.getUserIdAsString()); + list.add("JMSMessageID = " + headerProperties.getMessageIdAsString()); + list.add("JMSCorrelationID = " + headerProperties.getCorrelationIdAsString()); + + int delMode = headerProperties.getDeliveryMode(); + list.add("JMSDeliveryMode = " + ((delMode == 1) ? "Persistent" : "Non_Persistent")); + + list.add("JMSPriority = " + headerProperties.getPriority()); + list.add("JMSType = " + headerProperties.getType()); + + long longDate = headerProperties.getExpiration(); + String strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSExpiration = " + strDate); + + longDate = headerProperties.getTimestamp(); + strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSTimestamp = " + strDate); + + return list.toArray(new String[list.size()]); + } + + /** + * @see ManagedQueue#moveMessages + * @param fromMessageId + * @param toMessageId + * @param toQueueName + * @throws JMException + */ + public void moveMessages(long fromMessageId, long toMessageId, String toQueueName) throws JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater then 0 and less then \"To MessageId\""); + } + + _queue.moveMessagesToAnotherQueue(fromMessageId, toMessageId, toQueueName, _storeContext); + } + + /** + * returns Notifications sent by this MBean. + */ + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED }; + String name = MonitorNotification.class.getName(); + String description = "Either Message count or Queue depth or Message size has reached threshold high value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description); + + return new MBeanNotificationInfo[] { info1 }; + } + +} // End of AMQQueueMBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java new file mode 100644 index 0000000000..cbe9246f09 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java @@ -0,0 +1,71 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultQueueRegistry implements QueueRegistry +{ + private ConcurrentMap<AMQShortString, AMQQueue> _queueMap = new ConcurrentHashMap<AMQShortString, AMQQueue>(); + + private final VirtualHost _virtualHost; + + public DefaultQueueRegistry(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void registerQueue(AMQQueue queue) throws AMQException + { + _queueMap.put(queue.getName(), queue); + } + + public void unregisterQueue(AMQShortString name) throws AMQException + { + _queueMap.remove(name); + } + + public AMQQueue getQueue(AMQShortString name) + { + return _queueMap.get(name); + } + + public Collection<AMQShortString> getQueueNames() + { + return _queueMap.keySet(); + } + + public Collection<AMQQueue> getQueues() + { + return _queueMap.values(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java new file mode 100644 index 0000000000..a2fcab9e73 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBinding.java @@ -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. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; + +public class ExchangeBinding +{ + private final Exchange _exchange; + private final AMQShortString _routingKey; + private final FieldTable _arguments; + + private static final FieldTable EMPTY_ARGUMENTS = new FieldTable(); + + ExchangeBinding(AMQShortString routingKey, Exchange exchange) + { + this(routingKey, exchange, EMPTY_ARGUMENTS); + } + + ExchangeBinding(AMQShortString routingKey, Exchange exchange, FieldTable arguments) + { + _routingKey = routingKey == null ? AMQShortString.EMPTY_STRING : routingKey; + _exchange = exchange; + _arguments = arguments == null ? EMPTY_ARGUMENTS : arguments; + } + + void unbind(AMQQueue queue) throws AMQException + { + _exchange.deregisterQueue(_routingKey, queue, _arguments); + } + + public Exchange getExchange() + { + return _exchange; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + public FieldTable getArguments() + { + return _arguments; + } + + public int hashCode() + { + return (_exchange == null ? 0 : _exchange.hashCode()) + + (_routingKey == null ? 0 : _routingKey.hashCode()); + } + + public boolean equals(Object o) + { + if (!(o instanceof ExchangeBinding)) + { + return false; + } + ExchangeBinding eb = (ExchangeBinding) o; + return _exchange.equals(eb._exchange) + && _routingKey.equals(eb._routingKey); + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.java new file mode 100644 index 0000000000..fb839c1783 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ExchangeBindings.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.server.queue; + +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.Exchange; + +/** + * When a queue is deleted, it should be deregistered from any + * exchange it has been bound to. This class assists in this task, + * by keeping track of all bindings for a given queue. + */ +class ExchangeBindings +{ + private final List<ExchangeBinding> _bindings = new CopyOnWriteArrayList<ExchangeBinding>(); + private final AMQQueue _queue; + + ExchangeBindings(AMQQueue queue) + { + _queue = queue; + } + + /** + * Adds the specified binding to those being tracked. + * @param routingKey the routing key with which the queue whose bindings + * are being tracked by the instance has been bound to the exchange + * @param exchange the exchange bound to + */ + void addBinding(AMQShortString routingKey, FieldTable arguments, Exchange exchange) + { + _bindings.add(new ExchangeBinding(routingKey, exchange, arguments)); + } + + + public boolean remove(AMQShortString routingKey, FieldTable arguments, Exchange exchange) + { + return _bindings.remove(new ExchangeBinding(routingKey, exchange, arguments)); + } + + + /** + * Deregisters this queue from any exchange it has been bound to + */ + void deregister() throws AMQException + { + //remove duplicates at this point + HashSet<ExchangeBinding> copy = new HashSet<ExchangeBinding>(_bindings); + for (ExchangeBinding b : copy) + { + b.unbind(_queue); + } + } + + List<ExchangeBinding> getExchangeBindings() + { + return _bindings; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java new file mode 100644 index 0000000000..6466e81dd2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java @@ -0,0 +1,50 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; + +/** + * Signals that the dequeue of a message from a queue failed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Indicates the a message could not be dequeued from a queue. + * <tr><td> + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Happens as a consequence of a message store failure, or reference counting error. Both of which migh become + * runtime exceptions, as unrecoverable conditions? In which case this one might be dropped too. + */ +public class FailedDequeueException extends AMQException +{ + public FailedDequeueException(String queue) + { + super("Failed to dequeue message from " + queue); + } + + public FailedDequeueException(String queue, AMQException e) + { + super("Failed to dequeue message from " + queue, e); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java new file mode 100644 index 0000000000..d38932bb61 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java @@ -0,0 +1,33 @@ +/* +* +* 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.queue; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.AMQException; + +public interface Filterable<E extends Exception> +{ + ContentHeaderBody getContentHeaderBody() throws E; + + boolean isPersistent() throws E; + + boolean isRedelivered(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java new file mode 100644 index 0000000000..35ad5be4e0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InMemoryMessageHandle.java @@ -0,0 +1,157 @@ +/* + * + * 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.queue; + +import java.util.LinkedList; +import java.util.List; +import java.util.Collections; +import java.util.ArrayList; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.store.StoreContext; + +/** + */ +public class InMemoryMessageHandle implements AMQMessageHandle +{ + + private ContentHeaderBody _contentHeaderBody; + + private MessagePublishInfo _messagePublishInfo; + + private List<ContentChunk> _contentBodies; + + private boolean _redelivered; + + private long _arrivalTime; + + private final Long _messageId; + + public InMemoryMessageHandle(final Long messageId) + { + _messageId = messageId; + } + + public ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException + { + return _contentHeaderBody; + } + + public Long getMessageId() + { + return _messageId; + } + + public int getBodyCount(StoreContext context) + { + return _contentBodies.size(); + } + + public long getBodySize(StoreContext context) throws AMQException + { + return getContentHeaderBody(context).bodySize; + } + + public ContentChunk getContentChunk(StoreContext context, int index) throws AMQException, IllegalArgumentException + { + if (index > _contentBodies.size() - 1) + { + throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " + + (_contentBodies.size() - 1)); + } + return _contentBodies.get(index); + } + + public void addContentBodyFrame(StoreContext storeContext, ContentChunk contentBody, boolean isLastContentBody) + throws AMQException + { + if(_contentBodies == null) + { + if(isLastContentBody) + { + _contentBodies = Collections.singletonList(contentBody); + } + else + { + _contentBodies = new ArrayList<ContentChunk>(); + _contentBodies.add(contentBody); + } + } + else + { + _contentBodies.add(contentBody); + } + } + + public MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException + { + return _messagePublishInfo; + } + + public boolean isRedelivered() + { + return _redelivered; + } + + + public void setRedelivered(boolean redelivered) + { + _redelivered = redelivered; + } + + public boolean isPersistent() + { + return false; + } + + /** + * This is called when all the content has been received. + * @param messagePublishInfo + * @param contentHeaderBody + * @throws AMQException + */ + public void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo messagePublishInfo, + ContentHeaderBody contentHeaderBody) + throws AMQException + { + _messagePublishInfo = messagePublishInfo; + _contentHeaderBody = contentHeaderBody; + if(contentHeaderBody.bodySize == 0) + { + _contentBodies = Collections.EMPTY_LIST; + } + _arrivalTime = System.currentTimeMillis(); + } + + public void removeMessage(StoreContext storeContext) throws AMQException + { + // NO OP + } + + public long getArrivalTime() + { + return _arrivalTime; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java new file mode 100644 index 0000000000..091baf2751 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java @@ -0,0 +1,319 @@ +/* + * + * 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.queue; + +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.exchange.NoRouteException; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; + +public class IncomingMessage implements Filterable<RuntimeException> +{ + + /** Used for debugging purposes. */ + private static final Logger _logger = Logger.getLogger(IncomingMessage.class); + + private static final boolean SYNCHED_CLOCKS = + ApplicationRegistry.getInstance().getConfiguration().getSynchedClocks(); + + private final MessagePublishInfo _messagePublishInfo; + private ContentHeaderBody _contentHeaderBody; + private AMQMessageHandle _messageHandle; + private final Long _messageId; + private final TransactionalContext _txnContext; + + private static final boolean MSG_AUTH = + ApplicationRegistry.getInstance().getConfiguration().getMsgAuth(); + + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + /** + * This is stored during routing, to know the queues to which this message should immediately be + * delivered. It is <b>cleared after delivery has been attempted</b>. Any persistent record of destinations is done + * by the message handle. + */ + private ArrayList<AMQQueue> _destinationQueues; + + private AMQProtocolSession _publisher; + private MessageStore _messageStore; + private long _expiration; + + private Exchange _exchange; + + + public IncomingMessage(final Long messageId, + final MessagePublishInfo info, + final TransactionalContext txnContext, + final AMQProtocolSession publisher) + { + _messageId = messageId; + _messagePublishInfo = info; + _txnContext = txnContext; + _publisher = publisher; + + } + + public void setContentHeaderBody(final ContentHeaderBody contentHeaderBody) throws AMQException + { + _contentHeaderBody = contentHeaderBody; + } + + public void setExpiration() + { + long expiration = + ((BasicContentHeaderProperties) _contentHeaderBody.properties).getExpiration(); + long timestamp = + ((BasicContentHeaderProperties) _contentHeaderBody.properties).getTimestamp(); + + if (SYNCHED_CLOCKS) + { + _expiration = expiration; + } + else + { + // Update TTL to be in broker time. + if (expiration != 0L) + { + if (timestamp != 0L) + { + // todo perhaps use arrival time + long diff = (System.currentTimeMillis() - timestamp); + + if ((diff > 1000L) || (diff < 1000L)) + { + _expiration = expiration + diff; + } + } + } + } + + } + + public void routingComplete(final MessageStore store, + final MessageHandleFactory factory) throws AMQException + { + + final boolean persistent = isPersistent(); + _messageHandle = factory.createMessageHandle(_messageId, store, persistent); + if (persistent) + { + _txnContext.beginTranIfNecessary(); + // enqueuing the messages ensure that if required the destinations are recorded to a + // persistent store + + if(_destinationQueues != null) + { + for (int i = 0; i < _destinationQueues.size(); i++) + { + store.enqueueMessage(_txnContext.getStoreContext(), + _destinationQueues.get(i), _messageId); + } + } + } + } + + public AMQMessage deliverToQueues() + throws AMQException + { + + // we get a reference to the destination queues now so that we can clear the + // transient message data as quickly as possible + if (_logger.isDebugEnabled()) + { + _logger.debug("Delivering message " + _messageId + " to " + _destinationQueues); + } + + AMQMessage message = null; + + try + { + // first we allow the handle to know that the message has been fully received. This is useful if it is + // maintaining any calculated values based on content chunks + _messageHandle.setPublishAndContentHeaderBody(_txnContext.getStoreContext(), + _messagePublishInfo, getContentHeaderBody()); + + + + message = new AMQMessage(_messageHandle,_txnContext.getStoreContext(), _messagePublishInfo); + + message.setExpiration(_expiration); + message.setClientIdentifier(_publisher.getSessionIdentifier()); + + // we then allow the transactional context to do something with the message content + // now that it has all been received, before we attempt delivery + _txnContext.messageFullyReceived(isPersistent()); + + AMQShortString userID = getContentHeaderBody().properties instanceof BasicContentHeaderProperties ? + ((BasicContentHeaderProperties) getContentHeaderBody().properties).getUserId() : null; + + if (MSG_AUTH && !_publisher.getAuthorizedID().getName().equals(userID == null? "" : userID.toString())) + { + throw new UnauthorizedAccessException("Acccess Refused",message); + } + + if ((_destinationQueues == null) || _destinationQueues.size() == 0) + { + + if (isMandatory() || isImmediate()) + { + throw new NoRouteException("No Route for message", message); + + } + else + { + _logger.warn("MESSAGE DISCARDED: No routes for message - " + message); + } + } + else + { + int offset; + final int queueCount = _destinationQueues.size(); + message.incrementReference(queueCount); + if(queueCount == 1) + { + offset = 0; + } + else + { + offset = ((int)(message.getMessageId().longValue())) % queueCount; + if(offset < 0) + { + offset = -offset; + } + } + for (int i = offset; i < queueCount; i++) + { + // normal deliver so add this message at the end. + _txnContext.deliver(_destinationQueues.get(i), message); + } + for (int i = 0; i < offset; i++) + { + // normal deliver so add this message at the end. + _txnContext.deliver(_destinationQueues.get(i), message); + } + } + + message.clearStoreContext(); + return message; + } + finally + { + // Remove refence for routing process . Reference count should now == delivered queue count + if(message != null) message.decrementReference(_txnContext.getStoreContext()); + } + + } + + public void addContentBodyFrame(final ContentChunk contentChunk) + throws AMQException + { + + _bodyLengthReceived += contentChunk.getSize(); + + _messageHandle.addContentBodyFrame(_txnContext.getStoreContext(), contentChunk, allContentReceived()); + + } + + public boolean allContentReceived() + { + return (_bodyLengthReceived == getContentHeaderBody().bodySize); + } + + public AMQShortString getExchange() throws AMQException + { + return _messagePublishInfo.getExchange(); + } + + public AMQShortString getRoutingKey() throws AMQException + { + return _messagePublishInfo.getRoutingKey(); + } + + public boolean isMandatory() throws AMQException + { + return _messagePublishInfo.isMandatory(); + } + + + public boolean isImmediate() throws AMQException + { + return _messagePublishInfo.isImmediate(); + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + + public boolean isPersistent() + { + //todo remove literal values to a constant file such as AMQConstants in common + return getContentHeaderBody().properties instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) getContentHeaderBody().properties).getDeliveryMode() == 2; + } + + public boolean isRedelivered() + { + return false; + } + + public void setMessageStore(final MessageStore messageStore) + { + _messageStore = messageStore; + } + + public Long getMessageId() + { + return _messageId; + } + + public void setExchange(final Exchange e) + { + _exchange = e; + } + + public void route() throws AMQException + { + _exchange.route(this); + } + + public void enqueue(final ArrayList<AMQQueue> queues) + { + _destinationQueues = queues; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java new file mode 100644 index 0000000000..579656893b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ManagedQueue.java @@ -0,0 +1,245 @@ +/* + * + * 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.queue; + +import java.io.IOException; + +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; + +/** + * The management interface exposed to allow management of a queue. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedQueue +{ + static final String TYPE = "Queue"; + static final int VERSION = 2; + + /** + * Returns the Name of the ManagedQueue. + * @return the name of the managedQueue. + * @throws IOException + */ + @MBeanAttribute(name="Name", description = TYPE + " Name") + String getName() throws IOException; + + /** + * Total number of messages on the queue, which are yet to be delivered to the consumer(s). + * @return number of undelivered message in the Queue. + * @throws IOException + */ + @MBeanAttribute(name="MessageCount", description = "Total number of undelivered messages on the queue") + Integer getMessageCount() throws IOException; + + /** + * Tells the total number of messages receieved by the queue since startup. + * @return total number of messages received. + * @throws IOException + */ + @MBeanAttribute(name="ReceivedMessageCount", description="The total number of messages receieved by the queue since startup") + Long getReceivedMessageCount() throws IOException; + + /** + * Size of messages in the queue + * @return + * @throws IOException + */ + @MBeanAttribute(name="QueueDepth", description="The total size(Bytes) of messages in the queue") + Long getQueueDepth() throws IOException, JMException; + + /** + * Returns the total number of active subscribers to the queue. + * @return the number of active subscribers + * @throws IOException + */ + @MBeanAttribute(name="ActiveConsumerCount", description="The total number of active subscribers to the queue") + Integer getActiveConsumerCount() throws IOException; + + /** + * Returns the total number of subscribers to the queue. + * @return the number of subscribers. + * @throws IOException + */ + @MBeanAttribute(name="ConsumerCount", description="The total number of subscribers to the queue") + Integer getConsumerCount() throws IOException; + + /** + * Tells the Owner of the ManagedQueue. + * @return the owner's name. + * @throws IOException + */ + @MBeanAttribute(name="Owner", description = "Owner") + String getOwner() throws IOException; + + /** + * Tells whether this ManagedQueue is durable or not. + * @return true if this ManagedQueue is a durable queue. + * @throws IOException + */ + @MBeanAttribute(name="Durable", description = "true if the AMQQueue is durable") + boolean isDurable() throws IOException; + + /** + * Tells if the ManagedQueue is set to AutoDelete. + * @return true if the ManagedQueue is set to AutoDelete. + * @throws IOException + */ + @MBeanAttribute(name="AutoDelete", description = "true if the AMQQueue is AutoDelete") + boolean isAutoDelete() throws IOException; + + /** + * Returns the maximum age of a message (expiration time) in milliseconds + * @return the maximum age + * @throws IOException + */ + Long getMaximumMessageAge() throws IOException; + + /** + * Sets the maximum age of a message in milliseconds + * @param age maximum age of message. + * @throws IOException + */ + @MBeanAttribute(name="MaximumMessageAge", description="Threshold high value(milliseconds) for message age") + void setMaximumMessageAge(Long age) throws IOException; + + /** + * Returns the maximum size of a message (in Bytes) allowed to be accepted by the + * ManagedQueue. This is useful in setting notifications or taking + * appropriate action, if the size of the message received is more than + * the allowed size. + * @return the maximum size of a message allowed to be aceepted by the + * ManagedQueue. + * @throws IOException + */ + Long getMaximumMessageSize() throws IOException; + + /** + * Sets the maximum size of the message (in Bytes) that is allowed to be + * accepted by the Queue. + * @param size maximum size of message. + * @throws IOException + */ + @MBeanAttribute(name="MaximumMessageSize", description="Threshold high value(Bytes) for a message size") + void setMaximumMessageSize(Long size) throws IOException; + + /** + * Tells the maximum number of messages that can be stored in the queue. + * This is useful in setting the notifications or taking required + * action is the number of message increase this limit. + * @return maximum muber of message allowed to be stored in the queue. + * @throws IOException + */ + Long getMaximumMessageCount() throws IOException; + + /** + * Sets the maximum number of messages allowed to be stored in the queue. + * @param value the maximum number of messages allowed to be stored in the queue. + * @throws IOException + */ + @MBeanAttribute(name="MaximumMessageCount", description="Threshold high value for number of undelivered messages in the queue") + void setMaximumMessageCount(Long value) throws IOException; + + /** + * This is useful for setting notifications or taking required action if the size of messages + * stored in the queue increases over this limit. + * @return threshold high value for Queue Depth + * @throws IOException + */ + Long getMaximumQueueDepth() throws IOException; + + /** + * Sets the maximum size of all the messages together, that can be stored + * in the queue. + * @param value + * @throws IOException + */ + @MBeanAttribute(name="MaximumQueueDepth", description="The threshold high value(Bytes) for Queue Depth") + void setMaximumQueueDepth(Long value) throws IOException; + + + //********** Operations *****************// + + + /** + * Returns a subset of all the messages stored in the queue. The messages + * are returned based on the given index numbers. + * @param fromIndex + * @param toIndex + * @return + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="viewMessages", + description="Message headers for messages in this queue within given index range. eg. from index 1 - 100") + TabularData viewMessages(@MBeanOperationParameter(name="from index", description="from index")int fromIndex, + @MBeanOperationParameter(name="to index", description="to index")int toIndex) + throws IOException, JMException, AMQException; + + @MBeanOperation(name="viewMessageContent", description="The message content for given Message Id") + CompositeData viewMessageContent(@MBeanOperationParameter(name="Message Id", description="Message Id")long messageId) + throws IOException, JMException; + + /** + * Deletes the first message from top. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="deleteMessageFromTop", description="Deletes the first message from top", + impact= MBeanOperationInfo.ACTION) + void deleteMessageFromTop() throws IOException, JMException; + + /** + * Clears the queue by deleting all the undelivered messages from the queue. + * @throws IOException + * @throws JMException + */ + @MBeanOperation(name="clearQueue", + description="Clears the queue by deleting all the undelivered messages from the queue", + impact= MBeanOperationInfo.ACTION) + void clearQueue() throws IOException, JMException; + + /** + * Moves the messages in given range of message Ids to given Queue. QPID-170 + * @param fromMessageId first in the range of message ids + * @param toMessageId last in the range of message ids + * @param toQueue where the messages are to be moved + * @throws IOException + * @throws JMException + * @throws AMQException + */ + @MBeanOperation(name="moveMessages", + description="You can move messages to another queue from this queue ", + impact= MBeanOperationInfo.ACTION) + void moveMessages(@MBeanOperationParameter(name="from MessageId", description="from MessageId")long fromMessageId, + @MBeanOperationParameter(name="to MessageId", description="to MessageId")long toMessageId, + @MBeanOperationParameter(name= ManagedQueue.TYPE, description="to Queue Name")String toQueue) + throws IOException, JMException, AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java new file mode 100644 index 0000000000..090096d3c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.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.queue; + +import org.apache.qpid.AMQException; + +/** + * MessageCleanupException represents the failure to perform reference counting on messages correctly. This should not + * happen, but there may be programming errors giving race conditions that cause the reference counting to go wrong. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Signals that the reference count of a message has gone below zero. + * <tr><td> Indicates that a message store has lost a message which is still referenced. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo The race conditions leading to this error should be cleaned up, and a runtime exception used instead. If the + * message store loses messages, then something is seriously wrong and it would be sensible to terminate the + * broker. This may be disguising out of memory errors. + */ +public class MessageCleanupException extends AMQException +{ + public MessageCleanupException(long messageId, AMQException e) + { + super("Failed to cleanup message with id " + messageId, e); + } + + public MessageCleanupException(String message) + { + super(message); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.java new file mode 100644 index 0000000000..0b214ca336 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageHandleFactory.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.queue; + +import org.apache.qpid.server.store.MessageStore; + +/** + * Constructs a message handle based on the publish body, the content header and the queue to which the message + * has been routed. + * + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public class MessageHandleFactory +{ + + public AMQMessageHandle createMessageHandle(Long messageId, MessageStore store, boolean persistent) + { + // just hardcoded for now + if (persistent) + { + return new WeakReferenceMessageHandle(messageId, store); + } + else + { + return new InMemoryMessageHandle(messageId); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java new file mode 100644 index 0000000000..6118a4c11f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageMetaData.java @@ -0,0 +1,92 @@ +/* + * + * 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.queue; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +/** + * Encapsulates a publish body and a content header. In the context of the message store these are treated as a + * single unit. + */ +public class MessageMetaData +{ + private MessagePublishInfo _messagePublishInfo; + + private ContentHeaderBody _contentHeaderBody; + + private int _contentChunkCount; + + private long _arrivalTime; + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount) + { + this(publishBody,contentHeaderBody, contentChunkCount, System.currentTimeMillis()); + } + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount, long arrivalTime) + { + _contentHeaderBody = contentHeaderBody; + _messagePublishInfo = publishBody; + _contentChunkCount = contentChunkCount; + _arrivalTime = arrivalTime; + } + + public int getContentChunkCount() + { + return _contentChunkCount; + } + + public void setContentChunkCount(int contentChunkCount) + { + _contentChunkCount = contentChunkCount; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + public MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo) + { + _messagePublishInfo = messagePublishInfo; + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public void setArrivalTime(long arrivalTime) + { + _arrivalTime = arrivalTime; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.java new file mode 100644 index 0000000000..d6fd1eec89 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NoConsumersException.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.queue; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.RequiredDeliveryException; + +/** + * NoConsumersException is a {@link RequiredDeliveryException} that represents the failure case where an immediate + * message cannot be delivered because there are presently no consumers for the message. The AMQP status code, 313, is + * always used to report this condition. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to deliver a message that must be delivered. + * </table> + */ +public class NoConsumersException extends RequiredDeliveryException +{ + public NoConsumersException(AMQMessage message) + { + super("Immediate delivery is not possible.", message); + } + + public AMQConstant getReplyCode() + { + return AMQConstant.NO_CONSUMERS; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java new file mode 100644 index 0000000000..6f9efd3200 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java @@ -0,0 +1,138 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+
+public enum NotificationCheck
+{
+
+ MESSAGE_COUNT_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ int msgCount;
+ final long maximumMessageCount = queue.getMaximumMessageCount();
+ if (maximumMessageCount!= 0 && (msgCount = queue.getMessageCount()) >= maximumMessageCount)
+ {
+ listener.notifyClients(this, queue, msgCount + ": Maximum count on queue threshold ("+ maximumMessageCount +") breached.");
+ return true;
+ }
+ return false;
+ }
+ },
+ MESSAGE_SIZE_ALERT(true)
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ final long maximumMessageSize = queue.getMaximumMessageSize();
+ if(maximumMessageSize != 0)
+ {
+ // Check for threshold message size
+ long messageSize;
+ try
+ {
+ messageSize = (msg == null) ? 0 : msg.getContentHeaderBody().bodySize;
+ }
+ catch (AMQException e)
+ {
+ messageSize = 0;
+ }
+
+
+ if (messageSize >= maximumMessageSize)
+ {
+ listener.notifyClients(this, queue, messageSize + "b : Maximum message size threshold ("+ maximumMessageSize +") breached. [Message ID=" + msg.getMessageId() + "]");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ QUEUE_DEPTH_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ // Check for threshold queue depth in bytes
+ final long maximumQueueDepth = queue.getMaximumQueueDepth();
+
+ if(maximumQueueDepth != 0)
+ {
+ final long queueDepth = queue.getQueueDepth();
+
+ if (queueDepth >= maximumQueueDepth)
+ {
+ listener.notifyClients(this, queue, (queueDepth>>10) + "Kb : Maximum queue depth threshold ("+(maximumQueueDepth>>10)+"Kb) breached.");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ MESSAGE_AGE_ALERT
+ {
+ boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+
+ final long maxMessageAge = queue.getMaximumMessageAge();
+ if(maxMessageAge != 0)
+ {
+ final long currentTime = System.currentTimeMillis();
+ final long thresholdTime = currentTime - maxMessageAge;
+ final long firstArrivalTime = queue.getOldestMessageArrivalTime();
+
+ if(firstArrivalTime < thresholdTime)
+ {
+ long oldestAge = currentTime - firstArrivalTime;
+ listener.notifyClients(this, queue, (oldestAge/1000) + "s : Maximum age on queue threshold ("+(maxMessageAge /1000)+"s) breached.");
+
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ }
+ ;
+
+ private final boolean _messageSpecific;
+
+ NotificationCheck()
+ {
+ this(false);
+ }
+
+ NotificationCheck(boolean messageSpecific)
+ {
+ _messageSpecific = messageSpecific;
+ }
+
+ public boolean isMessageSpecific()
+ {
+ return _messageSpecific;
+ }
+
+ abstract boolean notifyIfNecessary(AMQMessage msg, AMQQueue queue, QueueNotificationListener listener);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java new file mode 100644 index 0000000000..fd46a8a5ff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java @@ -0,0 +1,169 @@ +/* +* +* 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.queue; + +import org.apache.qpid.framing.CommonContentHeaderProperties; +import org.apache.qpid.AMQException; + +public class PriorityQueueList implements QueueEntryList +{ + private final AMQQueue _queue; + private final QueueEntryList[] _priorityLists; + private final int _priorities; + private final int _priorityOffset; + + public PriorityQueueList(AMQQueue queue, int priorities) + { + _queue = queue; + _priorityLists = new QueueEntryList[priorities]; + _priorities = priorities; + _priorityOffset = 5-((priorities + 1)/2); + for(int i = 0; i < priorities; i++) + { + _priorityLists[i] = new SimpleQueueEntryList(queue); + } + } + + public int getPriorities() + { + return _priorities; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public QueueEntry add(AMQMessage message) + { + try + { + int index = ((CommonContentHeaderProperties)((message.getContentHeaderBody().properties))).getPriority() - _priorityOffset; + if(index >= _priorities) + { + index = _priorities-1; + } + else if(index < 0) + { + index = 0; + } + return _priorityLists[index].add(message); + } + catch (AMQException e) + { + // TODO - fix AMQ Exception + throw new RuntimeException(e); + } + + } + + public QueueEntry next(QueueEntry node) + { + QueueEntryImpl nodeImpl = (QueueEntryImpl)node; + QueueEntry next = nodeImpl.getNext(); + + if(next == null) + { + QueueEntryList nodeEntryList = nodeImpl.getQueueEntryList(); + int index; + for(index = _priorityLists.length-1; _priorityLists[index] != nodeEntryList; index--); + + while(next == null && index != 0) + { + index--; + next = ((QueueEntryImpl)_priorityLists[index].getHead()).getNext(); + } + + } + return next; + } + + private final class PriorityQueueEntryListIterator implements QueueEntryIterator + { + private final QueueEntryIterator[] _iterators = new QueueEntryIterator[ _priorityLists.length ]; + private QueueEntry _lastNode; + + PriorityQueueEntryListIterator() + { + for(int i = 0; i < _priorityLists.length; i++) + { + _iterators[i] = _priorityLists[i].iterator(); + } + _lastNode = _iterators[_iterators.length - 1].getNode(); + } + + + public boolean atTail() + { + for(int i = 0; i < _iterators.length; i++) + { + if(!_iterators[i].atTail()) + { + return false; + } + } + return true; + } + + public QueueEntry getNode() + { + return _lastNode; + } + + public boolean advance() + { + for(int i = _iterators.length-1; i >= 0; i--) + { + if(_iterators[i].advance()) + { + _lastNode = _iterators[i].getNode(); + return true; + } + } + return false; + } + } + + public QueueEntryIterator iterator() + { + return new PriorityQueueEntryListIterator(); + } + + public QueueEntry getHead() + { + return _priorityLists[_priorities-1].getHead(); + } + + static class Factory implements QueueEntryListFactory + { + private final int _priorities; + + Factory(int priorities) + { + _priorities = priorities; + } + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + return new PriorityQueueList(queue, _priorities); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java new file mode 100644 index 0000000000..2657c459a9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java @@ -0,0 +1,184 @@ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.subscription.Subscription; + +/* +* +* 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. +* +*/ +public interface QueueEntry extends Comparable<QueueEntry> +{ + + + + public static enum State + { + AVAILABLE, + ACQUIRED, + EXPIRED, + DEQUEUED, + DELETED + } + + public static interface StateChangeListener + { + public void stateChanged(QueueEntry entry, State oldSate, State newState); + } + + public abstract class EntryState + { + private EntryState() + { + } + + public abstract State getState(); + } + + + public final class AvailableState extends EntryState + { + + public State getState() + { + return State.AVAILABLE; + } + } + + + public final class DequeuedState extends EntryState + { + + public State getState() + { + return State.DEQUEUED; + } + } + + + public final class DeletedState extends EntryState + { + + public State getState() + { + return State.DELETED; + } + } + + public final class ExpiredState extends EntryState + { + + public State getState() + { + return State.EXPIRED; + } + } + + + public final class NonSubscriptionAcquiredState extends EntryState + { + public State getState() + { + return State.ACQUIRED; + } + } + + public final class SubscriptionAcquiredState extends EntryState + { + private final Subscription _subscription; + + public SubscriptionAcquiredState(Subscription subscription) + { + _subscription = subscription; + } + + + public State getState() + { + return State.ACQUIRED; + } + + public Subscription getSubscription() + { + return _subscription; + } + } + + + final static EntryState AVAILABLE_STATE = new AvailableState(); + final static EntryState DELETED_STATE = new DeletedState(); + final static EntryState DEQUEUED_STATE = new DequeuedState(); + final static EntryState EXPIRED_STATE = new ExpiredState(); + final static EntryState NON_SUBSCRIPTION_ACQUIRED_STATE = new NonSubscriptionAcquiredState(); + + + + + AMQQueue getQueue(); + + AMQMessage getMessage(); + + long getSize(); + + boolean getDeliveredToConsumer(); + + boolean expired() throws AMQException; + + boolean isAcquired(); + + boolean acquire(); + boolean acquire(Subscription sub); + + boolean delete(); + boolean isDeleted(); + + boolean acquiredBySubscription(); + + void setDeliveredToSubscription(); + + void release(); + + String debugIdentity(); + + boolean immediateAndNotDelivered(); + + void setRedelivered(boolean b); + + Subscription getDeliveredSubscription(); + + void reject(); + + void reject(Subscription subscription); + + boolean isRejectedBy(Subscription subscription); + + void requeue(StoreContext storeContext) throws AMQException; + + void dequeue(final StoreContext storeContext) throws FailedDequeueException; + + void dispose(final StoreContext storeContext) throws MessageCleanupException; + + void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException; + + boolean isQueueDeleted(); + + void addStateChangeListener(StateChangeListener listener); + boolean removeStateChangeListener(StateChangeListener listener); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java new file mode 100644 index 0000000000..dbad5438dc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java @@ -0,0 +1,388 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.log4j.Logger; + +import java.util.Set; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.CopyOnWriteArraySet; + + +public class QueueEntryImpl implements QueueEntry +{ + + /** + * Used for debugging purposes. + */ + private static final Logger _log = Logger.getLogger(QueueEntryImpl.class); + + private final SimpleQueueEntryList _queueEntryList; + + private AMQMessage _message; + + + private Set<Subscription> _rejectedBy = null; + + private volatile EntryState _state = AVAILABLE_STATE; + + private static final + AtomicReferenceFieldUpdater<QueueEntryImpl, EntryState> + _stateUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, EntryState.class, "_state"); + + + private volatile Set<StateChangeListener> _stateChangeListeners; + + private static final + AtomicReferenceFieldUpdater<QueueEntryImpl, Set> + _listenersUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, Set.class, "_stateChangeListeners"); + + + private static final + AtomicLongFieldUpdater<QueueEntryImpl> + _entryIdUpdater = + AtomicLongFieldUpdater.newUpdater + (QueueEntryImpl.class, "_entryId"); + + + private volatile long _entryId; + + volatile QueueEntryImpl _next; + + + QueueEntryImpl(SimpleQueueEntryList queueEntryList) + { + this(queueEntryList,null,Long.MIN_VALUE); + _state = DELETED_STATE; + } + + + public QueueEntryImpl(SimpleQueueEntryList queueEntryList, AMQMessage message, final long entryId) + { + _queueEntryList = queueEntryList; + _message = message; + + _entryIdUpdater.set(this, entryId); + } + + public QueueEntryImpl(SimpleQueueEntryList queueEntryList, AMQMessage message) + { + _queueEntryList = queueEntryList; + _message = message; + } + + protected void setEntryId(long entryId) + { + _entryIdUpdater.set(this, entryId); + } + + protected long getEntryId() + { + return _entryId; + } + + public AMQQueue getQueue() + { + return _queueEntryList.getQueue(); + } + + public AMQMessage getMessage() + { + return _message; + } + + public long getSize() + { + return getMessage().getSize(); + } + + public boolean getDeliveredToConsumer() + { + return getMessage().getDeliveredToConsumer(); + } + + public boolean expired() throws AMQException + { + return getMessage().expired(getQueue()); + } + + public boolean isAcquired() + { + return _state.getState() == State.ACQUIRED; + } + + public boolean acquire() + { + return acquire(NON_SUBSCRIPTION_ACQUIRED_STATE); + } + + private boolean acquire(final EntryState state) + { + boolean acquired = _stateUpdater.compareAndSet(this,AVAILABLE_STATE, state); + if(acquired && _stateChangeListeners != null) + { + notifyStateChange(State.AVAILABLE, State.ACQUIRED); + } + + return acquired; + } + + public boolean acquire(Subscription sub) + { + return acquire(sub.getOwningState()); + } + + public boolean acquiredBySubscription() + { + + return (_state instanceof SubscriptionAcquiredState); + } + + public void setDeliveredToSubscription() + { + getMessage().setDeliveredToConsumer(); + } + + public void release() + { + _stateUpdater.set(this,AVAILABLE_STATE); + } + + public String debugIdentity() + { + return getMessage().debugIdentity(); + } + + + public boolean immediateAndNotDelivered() + { + return _message.immediateAndNotDelivered(); + } + + public void setRedelivered(boolean b) + { + getMessage().setRedelivered(b); + } + + public Subscription getDeliveredSubscription() + { + EntryState state = _state; + if (state instanceof SubscriptionAcquiredState) + { + return ((SubscriptionAcquiredState) state).getSubscription(); + } + else + { + return null; + } + + } + + public void reject() + { + reject(getDeliveredSubscription()); + } + + public void reject(Subscription subscription) + { + if (subscription != null) + { + if (_rejectedBy == null) + { + _rejectedBy = new HashSet<Subscription>(); + } + + _rejectedBy.add(subscription); + } + else + { + _log.warn("Requesting rejection by null subscriber:" + debugIdentity()); + } + } + + public boolean isRejectedBy(Subscription subscription) + { + + if (_rejectedBy != null) // We have subscriptions that rejected this message + { + return _rejectedBy.contains(subscription); + } + else // This messasge hasn't been rejected yet. + { + return false; + } + } + + + public void requeue(final StoreContext storeContext) throws AMQException + { + getQueue().requeue(storeContext, this); + if(_stateChangeListeners != null) + { + notifyStateChange(QueueEntry.State.ACQUIRED, QueueEntry.State.AVAILABLE); + } + } + + public void dequeue(final StoreContext storeContext) throws FailedDequeueException + { + EntryState state = _state; + + if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, DEQUEUED_STATE)) + { + if (state instanceof SubscriptionAcquiredState) + { + Subscription s = ((SubscriptionAcquiredState) state).getSubscription(); + s.restoreCredit(this); + } + + getQueue().dequeue(storeContext, this); + if(_stateChangeListeners != null) + { + notifyStateChange(state.getState() , QueueEntry.State.DEQUEUED); + } + + } + + } + + private void notifyStateChange(final State oldState, final State newState) + { + for(StateChangeListener l : _stateChangeListeners) + { + l.stateChanged(this, oldState, newState); + } + } + + public void dispose(final StoreContext storeContext) throws MessageCleanupException + { + if(delete()) + { + getMessage().decrementReference(storeContext); + } + } + + public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException + { + //if the queue is null then the message is waiting to be acked, but has been removed. + if (getQueue() != null) + { + dequeue(storeContext); + } + + dispose(storeContext); + } + + public boolean isQueueDeleted() + { + return getQueue().isDeleted(); + } + + public void addStateChangeListener(StateChangeListener listener) + { + Set<StateChangeListener> listeners = _stateChangeListeners; + if(listeners == null) + { + _listenersUpdater.compareAndSet(this, null, new CopyOnWriteArraySet<StateChangeListener>()); + listeners = _stateChangeListeners; + } + + listeners.add(listener); + } + + public boolean removeStateChangeListener(StateChangeListener listener) + { + Set<StateChangeListener> listeners = _stateChangeListeners; + if(listeners != null) + { + return listeners.remove(listener); + } + + return false; + } + + + public int compareTo(final QueueEntry o) + { + QueueEntryImpl other = (QueueEntryImpl)o; + return getEntryId() > other.getEntryId() ? 1 : getEntryId() < other.getEntryId() ? -1 : 0; + } + + public QueueEntryImpl getNext() + { + + QueueEntryImpl next = nextNode(); + while(next != null && next.isDeleted()) + { + + final QueueEntryImpl newNext = next.nextNode(); + if(newNext != null) + { + SimpleQueueEntryList._nextUpdater.compareAndSet(this,next, newNext); + next = nextNode(); + } + else + { + next = null; + } + + } + return next; + } + + QueueEntryImpl nextNode() + { + return _next; + } + + public boolean isDeleted() + { + return _state == DELETED_STATE; + } + + public boolean delete() + { + EntryState state = _state; + + if(state != DELETED_STATE && _stateUpdater.compareAndSet(this,state,DELETED_STATE)) + { + _queueEntryList.advanceHead(); + return true; + } + else + { + return false; + } + } + + public QueueEntryList getQueueEntryList() + { + return _queueEntryList; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java new file mode 100644 index 0000000000..c5c115a2d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java @@ -0,0 +1,30 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +public interface QueueEntryIterator +{ + boolean atTail(); + + QueueEntry getNode(); + + boolean advance(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java new file mode 100644 index 0000000000..313e076f61 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java @@ -0,0 +1,34 @@ +/* +* +* 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.queue; + +public interface QueueEntryList +{ + AMQQueue getQueue(); + + QueueEntry add(AMQMessage message); + + QueueEntry next(QueueEntry node); + + QueueEntryIterator iterator(); + + QueueEntry getHead(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java new file mode 100644 index 0000000000..4dbce45f67 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java @@ -0,0 +1,26 @@ +/* +* +* 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.queue; + +interface QueueEntryListFactory +{ + public QueueEntryList createQueueEntryList(AMQQueue queue); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java new file mode 100644 index 0000000000..959ca03c80 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java @@ -0,0 +1,27 @@ +/*
+ *
+ * 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.queue;
+
+
+public interface QueueNotificationListener
+{
+ void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java new file mode 100644 index 0000000000..1210f0e97c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; + +public interface QueueRegistry +{ + VirtualHost getVirtualHost(); + + void registerQueue(AMQQueue queue) throws AMQException; + + void unregisterQueue(AMQShortString name) throws AMQException; + + AMQQueue getQueue(AMQShortString name); + + Collection<AMQShortString> getQueueNames(); + + Collection<AMQQueue> getQueues(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java new file mode 100644 index 0000000000..09258682bd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java @@ -0,0 +1,1600 @@ +package org.apache.qpid.server.queue; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import javax.management.JMException; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.pool.ReadWriteRunnable; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/* +* +* 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. +* +*/ +public class SimpleAMQQueue implements AMQQueue, Subscription.StateListener +{ + private static final Logger _logger = Logger.getLogger(SimpleAMQQueue.class); + + private final AMQShortString _name; + + /** null means shared */ + private final AMQShortString _owner; + + private final boolean _durable; + + /** If true, this queue is deleted when the last subscriber is removed */ + private final boolean _autoDelete; + + private final VirtualHost _virtualHost; + + /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */ + private final ExchangeBindings _bindings = new ExchangeBindings(this); + + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + private final List<Task> _deleteTaskList = new CopyOnWriteArrayList<Task>(); + + private final AtomicInteger _atomicQueueCount = new AtomicInteger(0); + + private final AtomicLong _atomicQueueSize = new AtomicLong(0L); + + private final AtomicInteger _activeSubscriberCount = new AtomicInteger(); + + protected final SubscriptionList _subscriptionList = new SubscriptionList(this); + private final AtomicReference<SubscriptionList.SubscriptionNode> _lastSubscriptionNode = new AtomicReference<SubscriptionList.SubscriptionNode>(_subscriptionList.getHead()); + + private volatile Subscription _exclusiveSubscriber; + + protected final QueueEntryList _entries; + + private final AMQQueueMBean _managedObject; + private final Executor _asyncDelivery; + private final AtomicLong _totalMessagesReceived = new AtomicLong(); + + /** max allowed size(KB) of a single message */ + public long _maximumMessageSize = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageSize(); + + /** max allowed number of messages on a queue. */ + public long _maximumMessageCount = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageCount(); + + /** max queue depth for the queue */ + public long _maximumQueueDepth = ApplicationRegistry.getInstance().getConfiguration().getMaximumQueueDepth(); + + /** maximum message age before alerts occur */ + public long _maximumMessageAge = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageAge(); + + /** the minimum interval between sending out consecutive alerts of the same type */ + public long _minimumAlertRepeatGap = ApplicationRegistry.getInstance().getConfiguration().getMinimumAlertRepeatGap(); + + private static final int MAX_ASYNC_DELIVERIES = 10; + + private final Set<NotificationCheck> _notificationChecks = EnumSet.noneOf(NotificationCheck.class); + + private final AtomicLong _stateChangeCount = new AtomicLong(Long.MIN_VALUE); + private AtomicReference _asynchronousRunner = new AtomicReference(null); + private AtomicInteger _deliveredMessages = new AtomicInteger(); + private AtomicBoolean _stopped = new AtomicBoolean(false); + + protected SimpleAMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, VirtualHost virtualHost) + throws AMQException + { + this(name, durable, owner, autoDelete, virtualHost, new SimpleQueueEntryList.Factory()); + } + + protected SimpleAMQQueue(AMQShortString name, + boolean durable, + AMQShortString owner, + boolean autoDelete, + VirtualHost virtualHost, + QueueEntryListFactory entryListFactory) + throws AMQException + { + + if (name == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + + if (virtualHost == null) + { + throw new IllegalArgumentException("Virtual Host must not be null"); + } + + _name = name; + _durable = durable; + _owner = owner; + _autoDelete = autoDelete; + _virtualHost = virtualHost; + _entries = entryListFactory.createQueueEntryList(this); + + _asyncDelivery = ReferenceCountingExecutorService.getInstance().acquireExecutorService(); + + try + { + _managedObject = new AMQQueueMBean(this); + _managedObject.register(); + } + catch (JMException e) + { + throw new AMQException("AMQQueue MBean creation has failed ", e); + } + + resetNotifications(); + + } + + public void resetNotifications() + { + // This ensure that the notification checks for the configured alerts are created. + setMaximumMessageAge(_maximumMessageAge); + setMaximumMessageCount(_maximumMessageCount); + setMaximumMessageSize(_maximumMessageSize); + setMaximumQueueDepth(_maximumQueueDepth); + } + + // ------ Getters and Setters + + public AMQShortString getName() + { + return _name; + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public AMQShortString getOwner() + { + return _owner; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + // ------ bind and unbind + + public void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException + { + exchange.registerQueue(routingKey, this, arguments); + if (isDurable() && exchange.isDurable()) + { + _virtualHost.getMessageStore().bindQueue(exchange, routingKey, this, arguments); + } + + _bindings.addBinding(routingKey, arguments, exchange); + } + + public void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException + { + exchange.deregisterQueue(routingKey, this, arguments); + if (isDurable() && exchange.isDurable()) + { + _virtualHost.getMessageStore().unbindQueue(exchange, routingKey, this, arguments); + } + + boolean removed = _bindings.remove(routingKey, arguments, exchange); + if (!removed) + { + _logger.error("Mismatch between queue bindings and exchange record of bindings"); + } + } + + public List<ExchangeBinding> getExchangeBindings() + { + return new ArrayList<ExchangeBinding>(_bindings.getExchangeBindings()); + } + + // ------ Manage Subscriptions + + public synchronized void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException + { + + if (isExclusiveSubscriber()) + { + throw new ExistingExclusiveSubscription(); + } + + if (exclusive) + { + if (getConsumerCount() != 0) + { + throw new ExistingSubscriptionPreventsExclusive(); + } + else + { + _exclusiveSubscriber = subscription; + + } + } + + _activeSubscriberCount.incrementAndGet(); + subscription.setStateListener(this); + subscription.setLastSeenEntry(null, _entries.getHead()); + + if (!isDeleted()) + { + subscription.setQueue(this); + _subscriptionList.add(subscription); + if (isDeleted()) + { + subscription.queueDeleted(this); + } + } + else + { + // TODO + } + + deliverAsync(subscription); + + } + + public synchronized void unregisterSubscription(final Subscription subscription) throws AMQException + { + if (subscription == null) + { + throw new NullPointerException("subscription argument is null"); + } + + boolean removed = _subscriptionList.remove(subscription); + + if (removed) + { + subscription.close(); + // No longer can the queue have an exclusive consumer + setExclusiveSubscriber(null); + + QueueEntry lastSeen; + + while ((lastSeen = subscription.getLastSeenEntry()) != null) + { + subscription.setLastSeenEntry(lastSeen, null); + } + + // auto-delete queues must be deleted if there are no remaining subscribers + + if (_autoDelete && getConsumerCount() == 0) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Auto-deleteing queue:" + this); + } + + delete(); + + // we need to manually fire the event to the removed subscription (which was the last one left for this + // queue. This is because the delete method uses the subscription set which has just been cleared + subscription.queueDeleted(this); + } + } + + } + + // ------ Enqueue / Dequeue + + public QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException + { + + incrementQueueCount(); + incrementQueueSize(message); + + _totalMessagesReceived.incrementAndGet(); + + QueueEntry entry; + Subscription exclusiveSub = _exclusiveSubscriber; + + if (exclusiveSub != null) + { + exclusiveSub.getSendLock(); + + try + { + entry = _entries.add(message); + + deliverToSubscription(exclusiveSub, entry); + + // where there is more than one producer there's a reasonable chance that even though there is + // no "queueing" we do not deliver because we get an interleving of _entries.add and + // deliverToSubscription between threads. Therefore have one more try. + if (!(entry.isAcquired() || entry.isDeleted())) + { + deliverToSubscription(exclusiveSub, entry); + } + } + finally + { + exclusiveSub.releaseSendLock(); + } + } + else + { + entry = _entries.add(message); + /* + + iterate over subscriptions and if any is at the end of the queue and can deliver this message, then deliver the message + + */ + SubscriptionList.SubscriptionNode node = _lastSubscriptionNode.get(); + SubscriptionList.SubscriptionNode nextNode = node.getNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().getNext(); + } + while (nextNode != null) + { + if (_lastSubscriptionNode.compareAndSet(node, nextNode)) + { + break; + } + else + { + node = _lastSubscriptionNode.get(); + nextNode = node.getNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().getNext(); + } + } + } + + // always do one extra loop after we believe we've finished + // this catches the case where we *just* miss an update + int loops = 2; + + while (!(entry.isAcquired() || entry.isDeleted()) && loops != 0) + { + if (nextNode == null) + { + loops--; + nextNode = _subscriptionList.getHead(); + } + else + { + // if subscription at end, and active, offer + Subscription sub = nextNode.getSubscription(); + deliverToSubscription(sub, entry); + } + nextNode = nextNode.getNext(); + + } + } + + if (entry.immediateAndNotDelivered()) + { + dequeue(storeContext, entry); + entry.dispose(storeContext); + } + else if (!(entry.isAcquired() || entry.isDeleted())) + { + checkSubscriptionsNotAheadOfDelivery(entry); + + deliverAsync(); + } + + _managedObject.checkForNotification(entry.getMessage()); + + return entry; + } + + private void deliverToSubscription(final Subscription sub, final QueueEntry entry) + throws AMQException + { + + sub.getSendLock(); + try + { + if (subscriptionReadyAndHasInterest(sub, entry) + && !sub.isSuspended()) + { + if (!sub.wouldSuspend(entry)) + { + if (!sub.isBrowser() && !entry.acquire(sub)) + { + // restore credit here that would have been taken away by wouldSuspend since we didn't manage + // to acquire the entry for this subscription + sub.restoreCredit(entry); + } + else + { + + deliverMessage(sub, entry); + + } + } + } + } + finally + { + sub.releaseSendLock(); + } + } + + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // This method is only required for queues which mess with ordering + // Simple Queues don't :-) + } + + private void incrementQueueSize(final AMQMessage message) + { + getAtomicQueueSize().addAndGet(message.getSize()); + } + + private void incrementQueueCount() + { + getAtomicQueueCount().incrementAndGet(); + } + + private void deliverMessage(final Subscription sub, final QueueEntry entry) + throws AMQException + { + _deliveredMessages.incrementAndGet(); + sub.send(entry); + + } + + private boolean subscriptionReadyAndHasInterest(final Subscription sub, final QueueEntry entry) + { + + // We need to move this subscription on, past entries which are already acquired, or deleted or ones it has no + // interest in. + QueueEntry node = sub.getLastSeenEntry(); + while (node != null && (node.isAcquired() || node.isDeleted() || !sub.hasInterest(node))) + { + + QueueEntry newNode = _entries.next(node); + if (newNode != null) + { + sub.setLastSeenEntry(node, newNode); + node = sub.getLastSeenEntry(); + } + else + { + node = null; + break; + } + + } + + if (node == entry) + { + // If the first entry that subscription can process is the one we are trying to deliver to it, then we are + // good + return true; + } + else + { + // Otherwise we should try to update the subscription's last seen entry to the entry we got to, providing + // no-one else has updated it to something furhter on in the list + //TODO - check + //updateLastSeenEntry(sub, entry); + return false; + } + + } + + private void updateLastSeenEntry(final Subscription sub, final QueueEntry entry) + { + QueueEntry node = sub.getLastSeenEntry(); + + if (node != null && entry.compareTo(node) < 0 && sub.hasInterest(entry)) + { + do + { + if (sub.setLastSeenEntry(node, entry)) + { + return; + } + else + { + node = sub.getLastSeenEntry(); + } + } + while (node != null && entry.compareTo(node) < 0); + } + + } + + public void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException + { + + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards + while (subscriberIter.advance()) + { + Subscription sub = subscriberIter.getNode().getSubscription(); + + // we don't make browsers send the same stuff twice + if (!sub.isBrowser()) + { + updateLastSeenEntry(sub, entry); + } + } + + deliverAsync(); + + } + + public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException + { + decrementQueueCount(); + decrementQueueSize(entry); + if (entry.acquiredBySubscription()) + { + _deliveredMessages.decrementAndGet(); + } + + try + { + AMQMessage msg = entry.getMessage(); + if (msg.isPersistent()) + { + _virtualHost.getMessageStore().dequeueMessage(storeContext, this, msg.getMessageId()); + } + //entry.dispose(storeContext); + + } + catch (MessageCleanupException e) + { + // Message was dequeued, but could not then be deleted + // though it is no longer referenced. This should be very + // rare and can be detected and cleaned up on recovery or + // done through some form of manual intervention. + _logger.error(e, e); + } + catch (AMQException e) + { + throw new FailedDequeueException(_name.toString(), e); + } + + } + + private void decrementQueueSize(final QueueEntry entry) + { + getAtomicQueueSize().addAndGet(-entry.getMessage().getSize()); + } + + void decrementQueueCount() + { + getAtomicQueueCount().decrementAndGet(); + } + + public boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException + { + /* TODO : This is wrong as the subscription may be suspended, we should instead change the state of the message + entry to resend and move back the subscription pointer. */ + + subscription.getSendLock(); + try + { + if (!subscription.isClosed()) + { + deliverMessage(subscription, entry); + return true; + } + else + { + return false; + } + } + finally + { + subscription.releaseSendLock(); + } + } + + public int getConsumerCount() + { + return _subscriptionList.size(); + } + + public int getActiveConsumerCount() + { + return _activeSubscriberCount.get(); + } + + public boolean isUnused() + { + return getConsumerCount() == 0; + } + + public boolean isEmpty() + { + return getMessageCount() == 0; + } + + public int getMessageCount() + { + return getAtomicQueueCount().get(); + } + + public long getQueueDepth() + { + return getAtomicQueueSize().get(); + } + + public int getUndeliveredMessageCount() + { + int count = getMessageCount() - _deliveredMessages.get(); + if (count < 0) + { + return 0; + } + else + { + return count; + } + } + + public long getReceivedMessageCount() + { + return _totalMessagesReceived.get(); + } + + public long getOldestMessageArrivalTime() + { + QueueEntry entry = getOldestQueueEntry(); + return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime(); + } + + protected QueueEntry getOldestQueueEntry() + { + return _entries.next(_entries.getHead()); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + public List<QueueEntry> getMessagesOnTheQueue() + { + ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (node != null && !node.isDeleted()) + { + entryList.add(node); + } + } + return entryList; + + } + + public void stateChange(Subscription sub, Subscription.State oldState, Subscription.State newState) + { + if (oldState == Subscription.State.ACTIVE && newState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.decrementAndGet(); + + } + else if (newState == Subscription.State.ACTIVE) + { + if (oldState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.incrementAndGet(); + + } + deliverAsync(sub); + } + } + + public int compareTo(final AMQQueue o) + { + return _name.compareTo(o.getName()); + } + + public AtomicInteger getAtomicQueueCount() + { + return _atomicQueueCount; + } + + public AtomicLong getAtomicQueueSize() + { + return _atomicQueueSize; + } + + private boolean isExclusiveSubscriber() + { + return _exclusiveSubscriber != null; + } + + private void setExclusiveSubscriber(Subscription exclusiveSubscriber) + { + _exclusiveSubscriber = exclusiveSubscriber; + } + + public static interface QueueEntryFilter + { + public boolean accept(QueueEntry entry); + + public boolean filterComplete(); + } + + public List<QueueEntry> getMessagesOnTheQueue(final long fromMessageId, final long toMessageId) + { + return getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageId(); + return messageId >= fromMessageId && messageId <= toMessageId; + } + + public boolean filterComplete() + { + return false; + } + }); + } + + public QueueEntry getMessageOnTheQueue(final long messageId) + { + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + private boolean _complete; + + public boolean accept(QueueEntry entry) + { + _complete = entry.getMessage().getMessageId() == messageId; + return _complete; + } + + public boolean filterComplete() + { + return _complete; + } + }); + return entries.isEmpty() ? null : entries.get(0); + } + + public List<QueueEntry> getMessagesOnTheQueue(QueueEntryFilter filter) + { + ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance() && !filter.filterComplete()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && filter.accept(node)) + { + entryList.add(node); + } + } + return entryList; + + } + + public void moveMessagesToAnotherQueue(final long fromMessageId, + final long toMessageId, + String queueName, + StoreContext storeContext) + { + + AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + MessageStore store = getVirtualHost().getMessageStore(); + + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageId(); + return (messageId >= fromMessageId) + && (messageId <= toMessageId) + && entry.acquire(); + } + + public boolean filterComplete() + { + return false; + } + }); + + try + { + store.beginTran(storeContext); + + // Move the messages in on the message store. + for (QueueEntry entry : entries) + { + AMQMessage message = entry.getMessage(); + + if (message.isPersistent() && toQueue.isDurable()) + { + store.enqueueMessage(storeContext, toQueue, message.getMessageId()); + } + // dequeue does not decrement the refence count + entry.dequeue(storeContext); + } + + // Commit and flush the move transcations. + try + { + store.commitTran(storeContext); + } + catch (AMQException e) + { + throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e); + } + } + catch (AMQException e) + { + try + { + store.abortTran(storeContext); + } + catch (AMQException rollbackEx) + { + _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx); + } + throw new RuntimeException(e); + } + + try + { + for (QueueEntry entry : entries) + { + toQueue.enqueue(storeContext, entry.getMessage()); + + } + } + catch (MessageCleanupException e) + { + throw new RuntimeException(e); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + public void copyMessagesToAnotherQueue(final long fromMessageId, + final long toMessageId, + String queueName, + final StoreContext storeContext) + { + AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + MessageStore store = getVirtualHost().getMessageStore(); + + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageId(); + if ((messageId >= fromMessageId) + && (messageId <= toMessageId)) + { + if (!entry.isDeleted()) + { + return entry.getMessage().incrementReference(); + } + } + + return false; + } + + public boolean filterComplete() + { + return false; + } + }); + + try + { + store.beginTran(storeContext); + + // Move the messages in on the message store. + for (QueueEntry entry : entries) + { + AMQMessage message = entry.getMessage(); + + if (message.isReferenced() && message.isPersistent() && toQueue.isDurable()) + { + store.enqueueMessage(storeContext, toQueue, message.getMessageId()); + } + } + + // Commit and flush the move transcations. + try + { + store.commitTran(storeContext); + } + catch (AMQException e) + { + throw new RuntimeException("Failed to commit transaction whilst moving messages on message store.", e); + } + } + catch (AMQException e) + { + try + { + store.abortTran(storeContext); + } + catch (AMQException rollbackEx) + { + _logger.error("Failed to rollback transaction when error occured moving messages", rollbackEx); + } + throw new RuntimeException(e); + } + + try + { + for (QueueEntry entry : entries) + { + if (entry.getMessage().isReferenced()) + { + toQueue.enqueue(storeContext, entry.getMessage()); + } + } + } + catch (MessageCleanupException e) + { + throw new RuntimeException(e); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + public void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext) + { + + try + { + QueueEntryIterator queueListIterator = _entries.iterator(); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + + final long messageId = node.getMessage().getMessageId(); + + if ((messageId >= fromMessageId) + && (messageId <= toMessageId) + && !node.isDeleted() + && node.acquire()) + { + node.discard(storeContext); + } + + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + // ------ Management functions + + public void deleteMessageFromTop(StoreContext storeContext) throws AMQException + { + QueueEntryIterator queueListIterator = _entries.iterator(); + boolean noDeletes = true; + + while (noDeletes && queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && node.acquire()) + { + node.discard(storeContext); + noDeletes = false; + } + + } + } + + public long clearQueue(StoreContext storeContext) throws AMQException + { + + QueueEntryIterator queueListIterator = _entries.iterator(); + long count = 0; + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && node.acquire()) + { + node.discard(storeContext); + count++; + } + + } + return count; + + } + + public void addQueueDeleteTask(final Task task) + { + _deleteTaskList.add(task); + } + + public int delete() throws AMQException + { + if (!_deleted.getAndSet(true)) + { + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + + while (subscriptionIter.advance()) + { + Subscription s = subscriptionIter.getNode().getSubscription(); + if (s != null) + { + s.queueDeleted(this); + } + } + + _bindings.deregister(); + _virtualHost.getQueueRegistry().unregisterQueue(_name); + + _managedObject.unregister(); + for (Task task : _deleteTaskList) + { + task.doTask(this); + } + + _deleteTaskList.clear(); + stop(); + } + return getMessageCount(); + + } + + public void stop() + { + if (!_stopped.getAndSet(true)) + { + ReferenceCountingExecutorService.getInstance().releaseExecutorService(); + } + } + + public void deliverAsync() + { + _stateChangeCount.incrementAndGet(); + + Runner runner = new Runner(); + + if (_asynchronousRunner.compareAndSet(null, runner)) + { + _asyncDelivery.execute(runner); + } + } + + public void deliverAsync(Subscription sub) + { + _asyncDelivery.execute(new SubFlushRunner(sub)); + } + + private class Runner implements ReadWriteRunnable + { + public void run() + { + try + { + processQueue(this); + } + catch (AMQException e) + { + _logger.error(e); + } + + } + + public boolean isRead() + { + return false; + } + + public boolean isWrite() + { + return true; + } + } + + private class SubFlushRunner implements ReadWriteRunnable + { + private final Subscription _sub; + + public SubFlushRunner(Subscription sub) + { + _sub = sub; + } + + public void run() + { + boolean complete = false; + try + { + complete = flushSubscription(_sub, new Long(MAX_ASYNC_DELIVERIES)); + + } + catch (AMQException e) + { + _logger.error(e); + } + if (!complete && !_sub.isSuspended()) + { + _asyncDelivery.execute(this); + } + + } + + public boolean isRead() + { + return false; + } + + public boolean isWrite() + { + return true; + } + } + + public void flushSubscription(Subscription sub) throws AMQException + { + flushSubscription(sub, Long.MAX_VALUE); + } + + public boolean flushSubscription(Subscription sub, Long iterations) throws AMQException + { + boolean atTail = false; + + while (!sub.isSuspended() && !atTail && iterations != 0) + { + try + { + sub.getSendLock(); + atTail = attemptDelivery(sub); + if (atTail && sub.isAutoClose()) + { + unregisterSubscription(sub); + + ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter(); + converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag()); + } + else if (!atTail) + { + iterations--; + } + } + finally + { + sub.releaseSendLock(); + } + } + + // if there's (potentially) more than one subscription the others will potentially not have been advanced to the + // next entry they are interested in yet. This would lead to holding on to references to expired messages, etc + // which would give us memory "leak". + + if (!isExclusiveSubscriber()) + { + advanceAllSubscriptions(); + } + return atTail; + } + + private boolean attemptDelivery(Subscription sub) throws AMQException + { + boolean atTail = false; + boolean advanced = false; + boolean subActive = sub.isActive(); + if (subActive) + { + QueueEntry node = moveSubscriptionToNextNode(sub); + if (!(node.isAcquired() || node.isDeleted())) + { + if (!sub.isSuspended()) + { + if (sub.hasInterest(node)) + { + if (!sub.wouldSuspend(node)) + { + if (!sub.isBrowser() && !node.acquire(sub)) + { + sub.restoreCredit(node); + } + else + { + deliverMessage(sub, node); + + if (sub.isBrowser()) + { + QueueEntry newNode = _entries.next(node); + + if (newNode != null) + { + advanced = true; + sub.setLastSeenEntry(node, newNode); + node = sub.getLastSeenEntry(); + } + } + } + + } + else // Not enough Credit for message and wouldSuspend + { + //QPID-1187 - Treat the subscription as suspended for this message + // and wait for the message to be removed to continue delivery. + subActive = false; + node.addStateChangeListener(new QueueEntryListener(sub, node)); + } + } + else + { + // this subscription is not interested in this node so we can skip over it + QueueEntry newNode = _entries.next(node); + if (newNode != null) + { + sub.setLastSeenEntry(node, newNode); + } + } + } + + } + atTail = (_entries.next(node) == null) && !advanced; + } + return atTail || !subActive; + } + + protected void advanceAllSubscriptions() throws AMQException + { + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + while (subscriberIter.advance()) + { + SubscriptionList.SubscriptionNode subNode = subscriberIter.getNode(); + Subscription sub = subNode.getSubscription(); + moveSubscriptionToNextNode(sub); + } + } + + private QueueEntry moveSubscriptionToNextNode(final Subscription sub) + throws AMQException + { + QueueEntry node = sub.getLastSeenEntry(); + + while (node != null && (node.isAcquired() || node.isDeleted() || node.expired())) + { + if (!node.isAcquired() && !node.isDeleted() && node.expired()) + { + if (node.acquire()) + { + final StoreContext reapingStoreContext = new StoreContext(); + node.discard(reapingStoreContext); + } + } + QueueEntry newNode = _entries.next(node); + if (newNode != null) + { + sub.setLastSeenEntry(node, newNode); + node = sub.getLastSeenEntry(); + } + else + { + break; + } + + } + return node; + } + + private void processQueue(Runnable runner) throws AMQException + { + long stateChangeCount; + long previousStateChangeCount = Long.MIN_VALUE; + boolean deliveryIncomplete = true; + + int extraLoops = 1; + Long iterations = new Long(MAX_ASYNC_DELIVERIES); + + _asynchronousRunner.compareAndSet(runner, null); + + while (iterations != 0 && ((previousStateChangeCount != (stateChangeCount = _stateChangeCount.get())) || deliveryIncomplete) && _asynchronousRunner.compareAndSet(null, runner)) + { + // we want to have one extra loop after every subscription has reached the point where it cannot move + // further, just in case the advance of one subscription in the last loop allows a different subscription to + // move forward in the next iteration + + if (previousStateChangeCount != stateChangeCount) + { + extraLoops = 1; + } + + previousStateChangeCount = stateChangeCount; + deliveryIncomplete = _subscriptionList.size() != 0; + boolean done = true; + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + //iterate over the subscribers and try to advance their pointer + while (subscriptionIter.advance()) + { + boolean closeConsumer = false; + Subscription sub = subscriptionIter.getNode().getSubscription(); + sub.getSendLock(); + try + { + if (sub != null) + { + + QueueEntry node = moveSubscriptionToNextNode(sub); + if (node != null) + { + done = attemptDelivery(sub); + } + } + if (done) + { + if (extraLoops == 0) + { + deliveryIncomplete = false; + if (sub.isAutoClose()) + { + unregisterSubscription(sub); + + ProtocolOutputConverter converter = sub.getChannel().getProtocolSession().getProtocolOutputConverter(); + converter.confirmConsumerAutoClose(sub.getChannel().getChannelId(), sub.getConsumerTag()); + } + } + else + { + extraLoops--; + } + } + else + { + iterations--; + extraLoops = 1; + } + } + finally + { + sub.releaseSendLock(); + } + } + _asynchronousRunner.set(null); + } + + // If deliveries == 0 then the limitting factor was the time-slicing rather than available messages or credit + // therefore we should schedule this runner again (unless someone beats us to it :-) ). + if (iterations == 0 && _asynchronousRunner.compareAndSet(null, runner)) + { + _asyncDelivery.execute(runner); + } + } + + @Override + public void checkMessageStatus() throws AMQException + { + + final StoreContext storeContext = new StoreContext(); + + QueueEntryIterator queueListIterator = _entries.iterator(); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && node.expired() && node.acquire()) + { + node.discard(storeContext); + } + else + { + _managedObject.checkForNotification(node.getMessage()); + } + } + + } + + public long getMinimumAlertRepeatGap() + { + return _minimumAlertRepeatGap; + } + + public void setMinimumAlertRepeatGap(long minimumAlertRepeatGap) + { + _minimumAlertRepeatGap = minimumAlertRepeatGap; + } + + public long getMaximumMessageAge() + { + return _maximumMessageAge; + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + _maximumMessageAge = maximumMessageAge; + if (maximumMessageAge == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_AGE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_AGE_ALERT); + } + } + + public long getMaximumMessageCount() + { + return _maximumMessageCount; + } + + public void setMaximumMessageCount(final long maximumMessageCount) + { + _maximumMessageCount = maximumMessageCount; + if (maximumMessageCount == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_COUNT_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_COUNT_ALERT); + } + + } + + public long getMaximumQueueDepth() + { + return _maximumQueueDepth; + } + + // Sets the queue depth, the max queue size + public void setMaximumQueueDepth(final long maximumQueueDepth) + { + _maximumQueueDepth = maximumQueueDepth; + if (maximumQueueDepth == 0L) + { + _notificationChecks.remove(NotificationCheck.QUEUE_DEPTH_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.QUEUE_DEPTH_ALERT); + } + + } + + public long getMaximumMessageSize() + { + return _maximumMessageSize; + } + + public void setMaximumMessageSize(final long maximumMessageSize) + { + _maximumMessageSize = maximumMessageSize; + if (maximumMessageSize == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_SIZE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_SIZE_ALERT); + } + } + + public Set<NotificationCheck> getNotificationChecks() + { + return _notificationChecks; + } + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + private final class QueueEntryListener implements QueueEntry.StateChangeListener + { + private final QueueEntry _entry; + private final Subscription _sub; + + public QueueEntryListener(final Subscription sub, final QueueEntry entry) + { + _entry = entry; + _sub = sub; + } + + public boolean equals(Object o) + { + return _entry == ((QueueEntryListener) o)._entry && _sub == ((QueueEntryListener) o)._sub; + } + + public int hashCode() + { + return System.identityHashCode(_entry) ^ System.identityHashCode(_sub); + } + + public void stateChanged(QueueEntry entry, QueueEntry.State oldSate, QueueEntry.State newState) + { + entry.removeStateChangeListener(this); + deliverAsync(_sub); + } + } + + public List<Long> getMessagesOnTheQueue(int num) + { + return getMessagesOnTheQueue(num, 0); + } + + public List<Long> getMessagesOnTheQueue(int num, int offset) + { + ArrayList<Long> ids = new ArrayList<Long>(num); + QueueEntryIterator it = _entries.iterator(); + for (int i = 0; i < offset; i++) + { + it.advance(); + } + + for (int i = 0; i < num && !it.atTail(); i++) + { + it.advance(); + ids.add(it.getNode().getMessage().getMessageId()); + } + return ids; + } + + public void configure(QueueConfiguration config) + { + if (config != null) + { + setMaximumMessageAge(config.getMaximumMessageAge()); + setMaximumQueueDepth(config.getMaximumQueueDepth()); + setMaximumMessageSize(config.getMaximumMessageSize()); + setMaximumMessageCount(config.getMaximumMessageCount()); + setMinimumAlertRepeatGap(config.getMinimumAlertRepeatGap()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java new file mode 100644 index 0000000000..a46c5ae2e8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java @@ -0,0 +1,178 @@ +package org.apache.qpid.server.queue; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +/* +* +* 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. +* +*/ +public class SimpleQueueEntryList implements QueueEntryList +{ + private final QueueEntryImpl _head; + + private volatile QueueEntryImpl _tail; + + static final AtomicReferenceFieldUpdater<SimpleQueueEntryList, QueueEntryImpl> + _tailUpdater = + AtomicReferenceFieldUpdater.newUpdater + (SimpleQueueEntryList.class, QueueEntryImpl.class, "_tail"); + + + private final AMQQueue _queue; + + static final AtomicReferenceFieldUpdater<QueueEntryImpl, QueueEntryImpl> + _nextUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, QueueEntryImpl.class, "_next"); + + + + + + public SimpleQueueEntryList(AMQQueue queue) + { + _queue = queue; + _head = new QueueEntryImpl(this); + _tail = _head; + } + + void advanceHead() + { + QueueEntryImpl head = _head.nextNode(); + while(head._next != null && head.isDeleted()) + { + + final QueueEntryImpl newhead = head.nextNode(); + if(newhead != null) + { + _nextUpdater.compareAndSet(_head,head, newhead); + } + head = _head.nextNode(); + } + } + + + public AMQQueue getQueue() + { + return _queue; + } + + + public QueueEntry add(AMQMessage message) + { + QueueEntryImpl node = new QueueEntryImpl(this, message); + for (;;) + { + QueueEntryImpl tail = _tail; + QueueEntryImpl next = tail.nextNode(); + if (tail == _tail) + { + if (next == null) + { + node.setEntryId(tail.getEntryId()+1); + if (_nextUpdater.compareAndSet(tail, null, node)) + { + _tailUpdater.compareAndSet(this, tail, node); + + return node; + } + } + else + { + _tailUpdater.compareAndSet(this,tail, next); + } + } + } + } + + public QueueEntry next(QueueEntry node) + { + return ((QueueEntryImpl)node).getNext(); + } + + + public class QueueEntryIteratorImpl implements QueueEntryIterator + { + + private QueueEntryImpl _lastNode; + + QueueEntryIteratorImpl(QueueEntryImpl startNode) + { + _lastNode = startNode; + } + + + public boolean atTail() + { + return _lastNode.nextNode() == null; + } + + public QueueEntry getNode() + { + + return _lastNode; + + } + + public boolean advance() + { + + if(!atTail()) + { + QueueEntryImpl nextNode = _lastNode.nextNode(); + while(nextNode.isDeleted() && nextNode.nextNode() != null) + { + nextNode = nextNode.nextNode(); + } + _lastNode = nextNode; + return true; + + } + else + { + return false; + } + + } + + } + + + public QueueEntryIterator iterator() + { + return new QueueEntryIteratorImpl(_head); + } + + + public QueueEntry getHead() + { + return _head; + } + + static class Factory implements QueueEntryListFactory + { + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + return new SimpleQueueEntryList(queue); + } + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java new file mode 100644 index 0000000000..9b91c71a1d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/TransientMessageData.java @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import java.util.LinkedList; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; + +/** + * Contains data that is only used in AMQMessage transiently, e.g. while the content + * body fragments are arriving. + * + * Having this data stored in a separate class means that the AMQMessage class avoids + * the small overhead of numerous guaranteed-null references. + * + * @author Apache Software Foundation + */ +public class TransientMessageData +{ + /** + * Stored temporarily until the header has been received at which point it is used when + * constructing the handle + */ + private MessagePublishInfo _messagePublishInfo; + + /** + * Also stored temporarily. + */ + private ContentHeaderBody _contentHeaderBody; + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + /** + * This is stored during routing, to know the queues to which this message should immediately be + * delivered. It is <b>cleared after delivery has been attempted</b>. Any persistent record of destinations is done + * by the message handle. + */ + private List<AMQQueue> _destinationQueues; + + public MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo) + { + _messagePublishInfo = messagePublishInfo; + } + + public List<AMQQueue> getDestinationQueues() + { + return _destinationQueues == null ? (List<AMQQueue>) Collections.EMPTY_LIST : _destinationQueues; + } + + public void setDestinationQueues(List<AMQQueue> destinationQueues) + { + _destinationQueues = destinationQueues; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + public long getBodyLengthReceived() + { + return _bodyLengthReceived; + } + + public void addBodyLength(int value) + { + _bodyLengthReceived += value; + } + + public boolean isAllContentReceived() throws AMQException + { + return _bodyLengthReceived == _contentHeaderBody.bodySize; + } + + public void addDestinationQueue(AMQQueue queue) + { + if(_destinationQueues == null) + { + _destinationQueues = new ArrayList<AMQQueue>(); + } + _destinationQueues.add(queue); + } + + public boolean isPersistent() + { + //todo remove literal values to a constant file such as AMQConstants in common + return _contentHeaderBody.properties instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.java new file mode 100644 index 0000000000..295cb266b9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/UnauthorizedAccessException.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.queue; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.RequiredDeliveryException; + +/** + * UnauthorizedAccessException is a {@link RequiredDeliveryException} that represents the failure case where a message + * is published with a user id different from the one used when creating the connection . + * The AMQP status code, 403, is always used to report this condition. + * + */ + +public class UnauthorizedAccessException extends RequiredDeliveryException +{ + public UnauthorizedAccessException(String msg, AMQMessage amqMessage) + { + super(msg, amqMessage); + } + + public AMQConstant getReplyCode() + { + return AMQConstant.ACCESS_REFUSED; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java new file mode 100644 index 0000000000..3ed8b0e55c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/WeakReferenceMessageHandle.java @@ -0,0 +1,219 @@ +/* + * + * 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.queue; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; + +/** + * @author Robert Greig (robert.j.greig@jpmorgan.com) + */ +public class WeakReferenceMessageHandle implements AMQMessageHandle +{ + private WeakReference<ContentHeaderBody> _contentHeaderBody; + + private WeakReference<MessagePublishInfo> _messagePublishInfo; + + private List<WeakReference<ContentChunk>> _contentBodies; + + private boolean _redelivered; + + private final MessageStore _messageStore; + + private final Long _messageId; + private long _arrivalTime; + + public WeakReferenceMessageHandle(final Long messageId, MessageStore messageStore) + { + _messageId = messageId; + _messageStore = messageStore; + } + + public ContentHeaderBody getContentHeaderBody(StoreContext context) throws AMQException + { + ContentHeaderBody chb = (_contentHeaderBody != null ? _contentHeaderBody.get() : null); + if (chb == null) + { + MessageMetaData mmd = loadMessageMetaData(context); + chb = mmd.getContentHeaderBody(); + } + return chb; + } + + public Long getMessageId() + { + return _messageId; + } + + private MessageMetaData loadMessageMetaData(StoreContext context) + throws AMQException + { + MessageMetaData mmd = _messageStore.getMessageMetaData(context, _messageId); + populateFromMessageMetaData(mmd); + return mmd; + } + + private void populateFromMessageMetaData(MessageMetaData mmd) + { + _arrivalTime = mmd.getArrivalTime(); + _contentHeaderBody = new WeakReference<ContentHeaderBody>(mmd.getContentHeaderBody()); + _messagePublishInfo = new WeakReference<MessagePublishInfo>(mmd.getMessagePublishInfo()); + } + + public int getBodyCount(StoreContext context) throws AMQException + { + if (_contentBodies == null) + { + MessageMetaData mmd = _messageStore.getMessageMetaData(context, _messageId); + int chunkCount = mmd.getContentChunkCount(); + _contentBodies = new ArrayList<WeakReference<ContentChunk>>(chunkCount); + for (int i = 0; i < chunkCount; i++) + { + _contentBodies.add(new WeakReference<ContentChunk>(null)); + } + } + return _contentBodies.size(); + } + + public long getBodySize(StoreContext context) throws AMQException + { + return getContentHeaderBody(context).bodySize; + } + + public ContentChunk getContentChunk(StoreContext context, int index) throws AMQException, IllegalArgumentException + { + if (index > _contentBodies.size() - 1) + { + throw new IllegalArgumentException("Index " + index + " out of valid range 0 to " + + (_contentBodies.size() - 1)); + } + WeakReference<ContentChunk> wr = _contentBodies.get(index); + ContentChunk cb = wr.get(); + if (cb == null) + { + cb = _messageStore.getContentBodyChunk(context, _messageId, index); + _contentBodies.set(index, new WeakReference<ContentChunk>(cb)); + } + return cb; + } + + /** + * Content bodies are set <i>before</i> the publish and header frames + * + * @param storeContext + * @param contentChunk + * @param isLastContentBody + * @throws AMQException + */ + public void addContentBodyFrame(StoreContext storeContext, ContentChunk contentChunk, boolean isLastContentBody) throws AMQException + { + if (_contentBodies == null && isLastContentBody) + { + _contentBodies = new ArrayList<WeakReference<ContentChunk>>(1); + } + else + { + if (_contentBodies == null) + { + _contentBodies = new LinkedList<WeakReference<ContentChunk>>(); + } + } + _contentBodies.add(new WeakReference<ContentChunk>(contentChunk)); + _messageStore.storeContentBodyChunk(storeContext, _messageId, _contentBodies.size() - 1, + contentChunk, isLastContentBody); + } + + public MessagePublishInfo getMessagePublishInfo(StoreContext context) throws AMQException + { + MessagePublishInfo bpb = (_messagePublishInfo != null ? _messagePublishInfo.get() : null); + if (bpb == null) + { + MessageMetaData mmd = loadMessageMetaData(context); + + bpb = mmd.getMessagePublishInfo(); + } + return bpb; + } + + public boolean isRedelivered() + { + return _redelivered; + } + + public void setRedelivered(boolean redelivered) + { + _redelivered = redelivered; + } + + public boolean isPersistent() + { + return true; + } + + /** + * This is called when all the content has been received. + * + * @param publishBody + * @param contentHeaderBody + * @throws AMQException + */ + public void setPublishAndContentHeaderBody(StoreContext storeContext, MessagePublishInfo publishBody, + ContentHeaderBody contentHeaderBody) + throws AMQException + { + // if there are no content bodies the list will be null so we must + // create en empty list here + if (contentHeaderBody.bodySize == 0) + { + _contentBodies = new LinkedList<WeakReference<ContentChunk>>(); + } + + final long arrivalTime = System.currentTimeMillis(); + + + MessageMetaData mmd = new MessageMetaData(publishBody, contentHeaderBody, _contentBodies.size(), arrivalTime); + + _messageStore.storeMessageMetaData(storeContext, _messageId, mmd); + + + populateFromMessageMetaData(mmd); + } + + public void removeMessage(StoreContext storeContext) throws AMQException + { + _messageStore.removeMessage(storeContext, _messageId); + } + + public long getArrivalTime() + { + return _arrivalTime; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java new file mode 100644 index 0000000000..22b4623ae1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -0,0 +1,290 @@ +/* + * + * 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.registry; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.mina.common.IoAcceptor; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +/** + * An abstract application registry that provides access to configuration information and handles the + * construction and caching of configurable objects. + * <p/> + * Subclasses should handle the construction of the "registered objects" such as the exchange registry. + */ +public abstract class ApplicationRegistry implements IApplicationRegistry +{ + protected static final Logger _logger = Logger.getLogger(ApplicationRegistry.class); + + private static Map<Integer, IApplicationRegistry> _instanceMap = new HashMap<Integer, IApplicationRegistry>(); + + private final Map<Class<?>, Object> _configuredObjects = new HashMap<Class<?>, Object>(); + + protected final ServerConfiguration _configuration; + + public static final int DEFAULT_INSTANCE = 1; + public static final String DEFAULT_APPLICATION_REGISTRY = "org.apache.qpid.server.util.NullApplicationRegistry"; + public static String _APPLICATION_REGISTRY = DEFAULT_APPLICATION_REGISTRY; + + protected final Map<InetSocketAddress, IoAcceptor> _acceptors = new HashMap<InetSocketAddress, IoAcceptor>(); + + protected ManagedObjectRegistry _managedObjectRegistry; + + protected AuthenticationManager _authenticationManager; + + protected VirtualHostRegistry _virtualHostRegistry; + + protected ACLManager _accessManager; + + protected PrincipalDatabaseManager _databaseManager; + + protected PluginManager _pluginManager; + + static + { + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService())); + } + + private static class ShutdownService implements Runnable + { + public void run() + { + removeAll(); + } + } + + public static void initialise(IApplicationRegistry instance) throws Exception + { + initialise(instance, DEFAULT_INSTANCE); + } + + public static void initialise(IApplicationRegistry instance, int instanceID) throws Exception + { + if (instance != null) + { + _logger.info("Initialising Application Registry:" + instanceID); + _instanceMap.put(instanceID, instance); + + try + { + instance.initialise(); + } + catch (Exception e) + { + _instanceMap.remove(instanceID); + throw e; + } + } + else + { + remove(instanceID); + } + } + + /** + * Method to cleanly shutdown specified registry running in this JVM + * + * @param instanceID the instance to shutdown + */ + + public static void remove(int instanceID) + { + try + { + IApplicationRegistry instance = _instanceMap.get(instanceID); + if (instance != null) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Shuting down ApplicationRegistry(" + instanceID + "):" + instance); + } + instance.close(); + } + } + catch (Exception e) + { + _logger.error("Error shutting down Application Registry(" + instanceID + "): " + e, e); + } + finally + { + _instanceMap.remove(instanceID); + } + } + + /** Method to cleanly shutdown all registries currently running in this JVM */ + public static void removeAll() + { + Object[] keys = _instanceMap.keySet().toArray(); + for (Object k : keys) + { + remove((Integer) k); + } + } + + protected ApplicationRegistry(ServerConfiguration configuration) + { + _configuration = configuration; + } + + public static IApplicationRegistry getInstance() + { + return getInstance(DEFAULT_INSTANCE); + } + + public static IApplicationRegistry getInstance(int instanceID) + { + synchronized (IApplicationRegistry.class) + { + IApplicationRegistry instance = _instanceMap.get(instanceID); + + if (instance == null) + { + try + { + _logger.info("Creating DEFAULT_APPLICATION_REGISTRY: " + _APPLICATION_REGISTRY + " : Instance:" + instanceID); + IApplicationRegistry registry = (IApplicationRegistry) Class.forName(_APPLICATION_REGISTRY).getConstructor((Class[]) null).newInstance((Object[]) null); + ApplicationRegistry.initialise(registry, instanceID); + _logger.info("Initialised Application Registry:" + instanceID); + return registry; + } + catch (Exception e) + { + _logger.error("Error configuring application: " + e, e); + //throw new AMQBrokerCreationException(instanceID, "Unable to create Application Registry instance " + instanceID); + throw new RuntimeException("Unable to create Application Registry", e); + } + } + else + { + return instance; + } + } + } + + public void close() throws Exception + { + if (_logger.isInfoEnabled()) + { + _logger.info("Shutting down ApplicationRegistry:"+this); + } + + //Stop incomming connections + unbind(); + + //Shutdown virtualhosts + for (VirtualHost virtualHost : getVirtualHostRegistry().getVirtualHosts()) + { + virtualHost.close(); + } + + // Replace above with this +// _virtualHostRegistry.close(); + +// _accessManager.close(); + +// _databaseManager.close(); + + _authenticationManager.close(); + +// _databaseManager.close(); + + // close the rmi registry(if any) started for management + if (_managedObjectRegistry != null) + { + _managedObjectRegistry.close(); + } + +// _pluginManager.close(); + } + + private void unbind() + { + synchronized (_acceptors) + { + for (InetSocketAddress bindAddress : _acceptors.keySet()) + { + IoAcceptor acceptor = _acceptors.get(bindAddress); + acceptor.unbind(bindAddress); + } + } + } + + public ServerConfiguration getConfiguration() + { + return _configuration; + } + + public void addAcceptor(InetSocketAddress bindAddress, IoAcceptor acceptor) + { + synchronized (_acceptors) + { + _acceptors.put(bindAddress, acceptor); + } + } + + public static void setDefaultApplicationRegistry(String clazz) + { + _APPLICATION_REGISTRY = clazz; + } + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + public ACLManager getAccessManager() throws ConfigurationException + { + return new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager); + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public PrincipalDatabaseManager getDatabaseManager() + { + return _databaseManager; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public PluginManager getPluginManager() + { + return _pluginManager; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java new file mode 100644 index 0000000000..39164883f9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java @@ -0,0 +1,87 @@ +/* + * + * 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.registry; + +import java.io.File; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.management.JMXManagedObjectRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.auth.database.ConfigurationFilePrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class ConfigurationFileApplicationRegistry extends ApplicationRegistry +{ + + public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException + { + super(new ServerConfiguration(configurationURL)); + } + + public void initialise() throws Exception + { + initialiseManagedObjectRegistry(); + + _virtualHostRegistry = new VirtualHostRegistry(); + + _pluginManager = new PluginManager(_configuration.getPluginDirectory()); + + _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager); + + _databaseManager = new ConfigurationFilePrincipalDatabaseManager(_configuration); + + _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null); + + _databaseManager.initialiseManagement(_configuration); + + _managedObjectRegistry.start(); + + initialiseVirtualHosts(); + + } + + private void initialiseVirtualHosts() throws Exception + { + for (String name : _configuration.getVirtualHosts()) + { + _virtualHostRegistry.registerVirtualHost(new VirtualHost(_configuration.getVirtualHostConfig(name))); + } + getVirtualHostRegistry().setDefaultVirtualHostName(_configuration.getDefaultVirtualHost()); + } + + private void initialiseManagedObjectRegistry() throws AMQException + { + if (_configuration.getManagementEnabled()) + { + _managedObjectRegistry = new JMXManagedObjectRegistry(); + } + else + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java new file mode 100644 index 0000000000..bbfda3addc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java @@ -0,0 +1,79 @@ +/* + * + * 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.registry; + +import java.util.Collection; +import java.net.InetSocketAddress; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.mina.common.IoAcceptor; + +public interface IApplicationRegistry +{ + /** + * Initialise the application registry. All initialisation must be done in this method so that any components + * that need access to the application registry itself for initialisation are able to use it. Attempting to + * initialise in the constructor will lead to failures since the registry reference will not have been set. + */ + void initialise() throws Exception; + + /** + * Shutdown this Registry + * @throws Exception - //fixme needs to be made more specific + */ + void close() throws Exception; + + /** + * Get the low level configuration. For use cases where the configured object approach is not required + * you can get the complete configuration information. + * @return a Commons Configuration instance + */ + ServerConfiguration getConfiguration(); + + ManagedObjectRegistry getManagedObjectRegistry(); + + PrincipalDatabaseManager getDatabaseManager(); + + AuthenticationManager getAuthenticationManager(); + + VirtualHostRegistry getVirtualHostRegistry(); + + ACLManager getAccessManager() throws ConfigurationException; + + PluginManager getPluginManager(); + + /** + * Register any acceptors for this registry + * @param bindAddress The address that the acceptor has been bound with + * @param acceptor The acceptor in use + */ + void addAcceptor(InetSocketAddress bindAddress, IoAcceptor acceptor); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java new file mode 100644 index 0000000000..6f7f66fad2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLManager.java @@ -0,0 +1,322 @@ +/* + * 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.security.access; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.SecurityConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.security.access.plugins.SimpleXML; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ACLManager +{ + private static final Logger _logger = Logger.getLogger(ACLManager.class); + private PluginManager _pluginManager; + private Map<String, ACLPluginFactory> _allSecurityPlugins = new HashMap<String, ACLPluginFactory>(); + private Map<String, ACLPlugin> _globalPlugins = new HashMap<String, ACLPlugin>(); + private Map<String, ACLPlugin> _hostPlugins = new HashMap<String, ACLPlugin>(); + + public ACLManager(SecurityConfiguration configuration, PluginManager manager) throws ConfigurationException + { + this(configuration, manager, null); + } + + public ACLManager(SecurityConfiguration configuration, PluginManager manager, ACLPluginFactory securityPlugin) throws ConfigurationException + { + _pluginManager = manager; + + if (manager == null) // No plugin manager, no plugins + { + return; + } + + _allSecurityPlugins = _pluginManager.getSecurityPlugins(); + if (securityPlugin != null) + { + _allSecurityPlugins.put(securityPlugin.getClass().getName(), securityPlugin); + } + + _globalPlugins = configurePlugins(configuration); + } + + + public void configureHostPlugins(SecurityConfiguration hostConfig) throws ConfigurationException + { + _hostPlugins = configurePlugins(hostConfig); + } + + public Map<String, ACLPlugin> configurePlugins(SecurityConfiguration hostConfig) throws ConfigurationException + { + Configuration securityConfig = hostConfig.getConfiguration(); + Map<String, ACLPlugin> plugins = new HashMap<String, ACLPlugin>(); + Iterator keys = securityConfig.getKeys(); + Collection<String> handledTags = new HashSet(); + while (keys.hasNext()) + { + // Splitting the string is necessary here because of the way that getKeys() returns only + // bottom level children + String tag = ((String) keys.next()).split("\\.", 2)[0]; + if (!handledTags.contains(tag)) + { + for (ACLPluginFactory plugin : _allSecurityPlugins.values()) + { + if (plugin.supportsTag(tag)) + { + _logger.warn("Plugin handling security section "+tag+" is "+plugin.getClass().getSimpleName()); + handledTags.add(tag); + plugins.put(plugin.getClass().getName(), plugin.newInstance(securityConfig)); + } + } + } + if (!handledTags.contains(tag)) + { + _logger.warn("No plugin handled security section "+tag); + } + } + return plugins; + } + + public static Logger getLogger() + { + return _logger; + } + + private abstract class AccessCheck + { + abstract AuthzResult allowed(ACLPlugin plugin); + } + + private boolean checkAllPlugins(AccessCheck checker) + { + AuthzResult result = AuthzResult.ABSTAIN; + HashMap<String, ACLPlugin> remainingPlugins = new HashMap<String, ACLPlugin>(); + remainingPlugins.putAll(_globalPlugins); + for (Entry<String, ACLPlugin> plugin : _hostPlugins.entrySet()) + { + result = checker.allowed(plugin.getValue()); + if (result == AuthzResult.DENIED) + { + // Something vetoed the access, we're done + return false; + } + else if (result == AuthzResult.ALLOWED) + { + // Remove plugin from global check list since + // host allow overrides global allow + remainingPlugins.remove(plugin.getKey()); + } + } + + for (ACLPlugin plugin : remainingPlugins.values()) + { + result = checker.allowed(plugin); + if (result == AuthzResult.DENIED) + { + return false; + } + } + return true; + } + + public boolean authoriseBind(final AMQProtocolSession session, final Exchange exch, final AMQQueue queue, + final AMQShortString routingKey) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseBind(session, exch, queue, routingKey); + } + + }); + } + + public boolean authoriseConnect(final AMQProtocolSession session, final VirtualHost virtualHost) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseConnect(session, virtualHost); + } + + }); + } + + public boolean authoriseConsume(final AMQProtocolSession session, final boolean noAck, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseConsume(session, noAck, queue); + } + + }); + } + + public boolean authoriseConsume(final AMQProtocolSession session, final boolean exclusive, final boolean noAck, + final boolean noLocal, final boolean nowait, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseConsume(session, exclusive, noAck, noLocal, nowait, queue); + } + + }); + } + + public boolean authoriseCreateExchange(final AMQProtocolSession session, final boolean autoDelete, + final boolean durable, final AMQShortString exchangeName, final boolean internal, final boolean nowait, + final boolean passive, final AMQShortString exchangeType) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseCreateExchange(session, autoDelete, durable, exchangeName, internal, nowait, + passive, exchangeType); + } + + }); + } + + public boolean authoriseCreateQueue(final AMQProtocolSession session, final boolean autoDelete, + final boolean durable, final boolean exclusive, final boolean nowait, final boolean passive, + final AMQShortString queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseCreateQueue(session, autoDelete, durable, exclusive, nowait, passive, queue); + } + + }); + } + + public boolean authoriseDelete(final AMQProtocolSession session, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseDelete(session, queue); + } + + }); + } + + public boolean authoriseDelete(final AMQProtocolSession session, final Exchange exchange) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseDelete(session, exchange); + } + + }); + } + + public boolean authorisePublish(final AMQProtocolSession session, final boolean immediate, final boolean mandatory, + final AMQShortString routingKey, final Exchange e) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authorisePublish(session, immediate, mandatory, routingKey, e); + } + + }); + } + + public boolean authorisePurge(final AMQProtocolSession session, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authorisePurge(session, queue); + } + + }); + } + + public boolean authoriseUnbind(final AMQProtocolSession session, final Exchange exch, + final AMQShortString routingKey, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + + @Override + AuthzResult allowed(ACLPlugin plugin) + { + return plugin.authoriseUnbind(session, exch, routingKey, queue); + } + + }); + } + + public void addHostPlugin(ACLPlugin aclPlugin) + { + _hostPlugins.put(aclPlugin.getClass().getName(), aclPlugin); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java new file mode 100644 index 0000000000..032184ec39 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPlugin.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.security.access; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public interface ACLPlugin +{ + public enum AuthzResult + { + ALLOWED, + DENIED, + ABSTAIN + } + + void setConfiguration(Configuration config) throws ConfigurationException; + + // These return true if the plugin thinks the action should be allowed, and false if not. + + AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue, AMQShortString routingKey); + + AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable, + AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType); + + AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable, boolean exclusive, + boolean nowait, boolean passive, AMQShortString queue); + + AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost); + + AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue); + + AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal, + boolean nowait, AMQQueue queue); + + AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue); + + AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange); + + AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory, + AMQShortString routingKey, Exchange e); + + AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue); + + AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey, AMQQueue queue); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java new file mode 100644 index 0000000000..256f093477 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ACLPluginFactory.java @@ -0,0 +1,33 @@ +/* + * + * 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.security.access; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +public interface ACLPluginFactory +{ + + public boolean supportsTag(String name); + + public ACLPlugin newInstance(Configuration config) throws ConfigurationException; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java new file mode 100644 index 0000000000..d722da4ae0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessResult.java @@ -0,0 +1,65 @@ +/* + * 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.security.access; + +public class AccessResult +{ + public enum AccessStatus + { + GRANTED, REFUSED + } + + private String _authorizer; + private AccessStatus _status; + + public AccessResult(ACLPlugin authorizer, AccessStatus status) + { + _status = status; + _authorizer = authorizer.getClass().getSimpleName(); + } + + public void setAuthorizer(ACLPlugin authorizer) + { + _authorizer += authorizer.getClass().getSimpleName(); + } + + public String getAuthorizer() + { + return _authorizer; + } + + public void setStatus(AccessStatus status) + { + _status = status; + } + + public AccessStatus getStatus() + { + return _status; + } + + public void addAuthorizer(ACLPlugin accessManager) + { + _authorizer = accessManager.getClass().getSimpleName() + "->" + _authorizer; + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java new file mode 100644 index 0000000000..1b79a5a0e0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AccessRights.java @@ -0,0 +1,63 @@ +/* + * 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.security.access; + +public class AccessRights +{ + public enum Rights + { + ANY, + READ, + WRITE, + READWRITE + } + + Rights _right; + + public AccessRights(Rights right) + { + _right = right; + } + + public boolean allows(Rights rights) + { + switch (_right) + { + case ANY: + return (rights.equals(Rights.WRITE) + || rights.equals(Rights.READ) + || rights.equals(Rights.READWRITE) + || rights.equals(Rights.ANY)); + case READ: + return rights.equals(Rights.READ) || rights.equals(Rights.ANY); + case WRITE: + return rights.equals(Rights.WRITE) || rights.equals(Rights.ANY); + case READWRITE: + return true; + } + return false; + } + + public Rights getRights() + { + return _right; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java new file mode 100644 index 0000000000..f51cf24caa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Accessable.java @@ -0,0 +1,27 @@ +/* + * 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.security.access; + +public interface Accessable +{ + void setAccessableName(String name); + String getAccessableName(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java new file mode 100644 index 0000000000..9527120f30 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/AuthorizationManager.java @@ -0,0 +1,6 @@ +package org.apache.qpid.server.security.access; + +public class AuthorizationManager +{ + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java new file mode 100644 index 0000000000..b65b0cdc6c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java @@ -0,0 +1,38 @@ +/* + * 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.security.access; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +public enum Permission +{ + CONSUME, + PUBLISH, + CREATEQUEUE, + CREATEEXCHANGE, + ACCESS, + BIND, + UNBIND, + DELETE, + PURGE +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java new file mode 100755 index 0000000000..f852514444 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/PrincipalPermissions.java @@ -0,0 +1,612 @@ +/* + * + * 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.security.access; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.QueueBindBody; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.ExchangeDeclareBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.exchange.Exchange; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class PrincipalPermissions +{ + + private static final Object CONSUME_QUEUES_KEY = new Object(); + private static final Object CONSUME_TEMPORARY_KEY = new Object(); + private static final Object CONSUME_OWN_QUEUES_ONLY_KEY = new Object(); + + private static final Object CREATE_QUEUES_KEY = new Object(); + private static final Object CREATE_EXCHANGES_KEY = new Object(); + + private static final Object CREATE_QUEUE_TEMPORARY_KEY = new Object(); + private static final Object CREATE_QUEUE_QUEUES_KEY = new Object(); + private static final Object CREATE_QUEUE_EXCHANGES_KEY = new Object(); + + private static final Object CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY = new Object(); + private static final Object CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY = new Object(); + + private static final int PUBLISH_EXCHANGES_KEY = 0; + + private Map _permissions; + + private String _user; + + + public PrincipalPermissions(String user) + { + _user = user; + _permissions = new ConcurrentHashMap(); + } + + /** + * + * @param permission the type of permission to check + * + * @param parameters vararg depending on what permission was passed in + * ACCESS: none + * BIND: none + * CONSUME: AMQShortString queueName, Boolean temporary, Boolean ownQueueOnly + * CREATEQUEUE: Boolean temporary, AMQShortString queueName, AMQShortString exchangeName, AMQShortString routingKey + * CREATEEXCHANGE: AMQShortString exchangeName, AMQShortString Class + * DELETE: none + * PUBLISH: Exchange exchange, AMQShortString routingKey + * PURGE: none + * UNBIND: none + */ + public void grant(Permission permission, Object... parameters) + { + switch (permission) + { + case ACCESS: + break; // This is a no-op as the existence of this PrincipalPermission object is scoped per VHost for ACCESS + case BIND: + break; // All the details are currently included in the create setup. + case CONSUME: // Parameters : AMQShortString queueName, Boolean Temporary, Boolean ownQueueOnly + Map consumeRights = (Map) _permissions.get(permission); + + if (consumeRights == null) + { + consumeRights = new ConcurrentHashMap(); + _permissions.put(permission, consumeRights); + } + + //if we have parametsre + if (parameters.length > 0) + { + AMQShortString queueName = (AMQShortString) parameters[0]; + Boolean temporary = (Boolean) parameters[1]; + Boolean ownQueueOnly = (Boolean) parameters[2]; + + if (temporary) + { + consumeRights.put(CONSUME_TEMPORARY_KEY, true); + } + else + { + consumeRights.put(CONSUME_TEMPORARY_KEY, false); + } + + if (ownQueueOnly) + { + consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, true); + } + else + { + consumeRights.put(CONSUME_OWN_QUEUES_ONLY_KEY, false); + } + + + LinkedList queues = (LinkedList) consumeRights.get(CONSUME_QUEUES_KEY); + if (queues == null) + { + queues = new LinkedList(); + consumeRights.put(CONSUME_QUEUES_KEY, queues); + } + + if (queueName != null) + { + queues.add(queueName); + } + } + + + break; + case CREATEQUEUE: // Parameters : Boolean temporary, AMQShortString queueName + // , AMQShortString exchangeName , AMQShortString routingKey + + Map createRights = (Map) _permissions.get(permission); + + if (createRights == null) + { + createRights = new ConcurrentHashMap(); + _permissions.put(permission, createRights); + + } + + //The existence of the empty map mean permission to all. + if (parameters.length == 0) + { + return; + } + + Boolean temporary = (Boolean) parameters[0]; + + AMQShortString queueName = parameters.length > 1 ? (AMQShortString) parameters[1] : null; + AMQShortString exchangeName = parameters.length > 2 ? (AMQShortString) parameters[2] : null; + //Set the routingkey to the specified value or the queueName if present + AMQShortString routingKey = (parameters.length > 3 && null != parameters[3]) ? (AMQShortString) parameters[3] : queueName; + + // Get the queues map + Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY); + + if (create_queues == null) + { + create_queues = new ConcurrentHashMap(); + createRights.put(CREATE_QUEUES_KEY, create_queues); + } + + //Allow all temp queues to be created + create_queues.put(CREATE_QUEUE_TEMPORARY_KEY, temporary); + + //Create empty list of queues + Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY); + + if (create_queues_queues == null) + { + create_queues_queues = new ConcurrentHashMap(); + create_queues.put(CREATE_QUEUE_QUEUES_KEY, create_queues_queues); + } + + // We are granting CREATE rights to all temporary queues only + if (parameters.length == 1) + { + return; + } + + // if we have a queueName then we need to store any associated exchange / rk bindings + if (queueName != null) + { + Map queue = (Map) create_queues_queues.get(queueName); + if (queue == null) + { + queue = new ConcurrentHashMap(); + create_queues_queues.put(queueName, queue); + } + + if (exchangeName != null) + { + queue.put(exchangeName, routingKey); + } + + //If no exchange is specified then the presence of the queueName in the map says any exchange is ok + } + + // Store the exchange that we are being granted rights to. This will be used as part of binding + + //Lookup the list of exchanges + Map create_queues_exchanges = (Map) create_queues.get(CREATE_QUEUE_EXCHANGES_KEY); + + if (create_queues_exchanges == null) + { + create_queues_exchanges = new ConcurrentHashMap(); + create_queues.put(CREATE_QUEUE_EXCHANGES_KEY, create_queues_exchanges); + } + + //if we have an exchange + if (exchangeName != null) + { + //Retrieve the list of permitted exchanges. + Map exchanges = (Map) create_queues_exchanges.get(exchangeName); + + if (exchanges == null) + { + exchanges = new ConcurrentHashMap(); + create_queues_exchanges.put(exchangeName, exchanges); + } + + //Store the temporary setting CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY + exchanges.put(CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY, temporary); + + //Store the binding details of queue/rk for this exchange. + if (queueName != null) + { + //Retrieve the list of permitted routingKeys. + Map rKeys = (Map) exchanges.get(exchangeName); + + if (rKeys == null) + { + rKeys = new ConcurrentHashMap(); + exchanges.put(CREATE_QUEUE_EXCHANGES_ROUTINGKEYS_KEY, rKeys); + } + + rKeys.put(queueName, routingKey); + } + } + break; + case CREATEEXCHANGE: + // Parameters AMQShortString exchangeName , AMQShortString Class + Map rights = (Map) _permissions.get(permission); + if (rights == null) + { + rights = new ConcurrentHashMap(); + _permissions.put(permission, rights); + } + + Map create_exchanges = (Map) rights.get(CREATE_EXCHANGES_KEY); + if (create_exchanges == null) + { + create_exchanges = new ConcurrentHashMap(); + rights.put(CREATE_EXCHANGES_KEY, create_exchanges); + } + + //Should perhaps error if parameters[0] is null; + AMQShortString name = parameters.length > 0 ? (AMQShortString) parameters[0] : null; + AMQShortString className = parameters.length > 1 ? (AMQShortString) parameters[1] : new AMQShortString("direct"); + + //Store the exchangeName / class mapping if the mapping is null + rights.put(name, className); + break; + case DELETE: + break; + + case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey + Map publishRights = (Map) _permissions.get(permission); + + if (publishRights == null) + { + publishRights = new ConcurrentHashMap(); + _permissions.put(permission, publishRights); + } + + if (parameters == null || parameters.length == 0) + { + //If we have no parameters then allow publish to all destinations + // this is signified by having a null value for publish_exchanges + } + else + { + Map publish_exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY); + + if (publish_exchanges == null) + { + publish_exchanges = new ConcurrentHashMap(); + publishRights.put(PUBLISH_EXCHANGES_KEY, publish_exchanges); + } + + + HashSet routingKeys = (HashSet) publish_exchanges.get(parameters[0]); + + // Check to see if we have a routing key + if (parameters.length == 2) + { + if (routingKeys == null) + { + routingKeys = new HashSet<AMQShortString>(); + } + //Add routing key to permitted publish destinations + routingKeys.add(parameters[1]); + } + + // Add the updated routingkey list or null if all values allowed + publish_exchanges.put(parameters[0], routingKeys); + } + break; + case PURGE: + break; + case UNBIND: + break; + } + + } + + /** + * + * @param permission the type of permission to check + * + * @param parameters vararg depending on what permission was passed in + * ACCESS: none + * BIND: QueueBindBody bindmethod, Exchange exchange, AMQQueue queue, AMQShortString routingKey + * CONSUME: AMQQueue queue + * CREATEQUEUE: Boolean autodelete, AMQShortString name + * CREATEEXCHANGE: AMQShortString exchangeName + * DELETE: none + * PUBLISH: Exchange exchange, AMQShortString routingKey + * PURGE: none + * UNBIND: none + */ + public AuthzResult authorise(Permission permission, Object... parameters) + { + + switch (permission) + { + case ACCESS: + return AuthzResult.ALLOWED; // This is here for completeness but the SimpleXML ACLManager never calls it. + // The existence of this user specific PP can be validated in the map SimpleXML maintains. + case BIND: // Parameters : QueueBindMethod , Exchange , AMQQueue, AMQShortString routingKey + + Exchange exchange = (Exchange) parameters[1]; + + AMQQueue bind_queueName = (AMQQueue) parameters[2]; + AMQShortString routingKey = (AMQShortString) parameters[3]; + + //Get all Create Rights for this user + Map bindCreateRights = (Map) _permissions.get(Permission.CREATEQUEUE); + + //Look up the Queue Creation Rights + Map bind_create_queues = (Map) bindCreateRights.get(CREATE_QUEUES_KEY); + + //Lookup the list of queues + Map bind_create_queues_queues = (Map) bindCreateRights.get(CREATE_QUEUE_QUEUES_KEY); + + // Check and see if we have a queue white list to check + if (bind_create_queues_queues != null) + { + //There a white list for queues + Map exchangeDetails = (Map) bind_create_queues_queues.get(bind_queueName); + + if (exchangeDetails == null) //Then all queue can be bound to all exchanges. + { + return AuthzResult.ALLOWED; + } + + // Check to see if we have a white list of routingkeys to check + Map rkeys = (Map) exchangeDetails.get(exchange.getName()); + + // if keys is null then any rkey is allowed on this exchange + if (rkeys == null) + { + // There is no routingkey white list + return AuthzResult.ALLOWED; + } + else + { + // We have routingKeys so a match must be found to allowed binding + Iterator keys = rkeys.keySet().iterator(); + + boolean matched = false; + while (keys.hasNext() && !matched) + { + AMQShortString rkey = (AMQShortString) keys.next(); + if (rkey.endsWith("*")) + { + matched = routingKey.startsWith(rkey.subSequence(0, rkey.length() - 1).toString()); + } + else + { + matched = routingKey.equals(rkey); + } + } + + + return (matched) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + + + } + else + { + //There a is no white list for queues + + // So can allow all queues to be bound + // but we should first check and see if we have a temp queue and validate that we are allowed + // to bind temp queues. + + //Check to see if we have a temporary queue + if (bind_queueName.isAutoDelete()) + { + // Check and see if we have an exchange white list. + Map bind_exchanges = (Map) bind_create_queues.get(CREATE_QUEUE_EXCHANGES_KEY); + + // If the exchange exists then we must check to see if temporary queues are allowed here + if (bind_exchanges != null) + { + // Check to see if the requested exchange is allowed. + Map exchangeDetails = (Map) bind_exchanges.get(exchange.getName()); + + return ((Boolean) exchangeDetails.get(CREATE_QUEUE_EXCHANGES_TEMPORARY_KEY)) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + + //no white list so all allowed, drop through to return true below. + } + + // not a temporary queue and no white list so all allowed. + return AuthzResult.ALLOWED; + } + + case CREATEQUEUE:// Parameters : boolean autodelete, AMQShortString name + + Map createRights = (Map) _permissions.get(permission); + + // If there are no create rights then deny request + if (createRights == null) + { + return AuthzResult.DENIED; + } + + //Look up the Queue Creation Rights + Map create_queues = (Map) createRights.get(CREATE_QUEUES_KEY); + + //Lookup the list of queues allowed to be created + Map create_queues_queues = (Map) create_queues.get(CREATE_QUEUE_QUEUES_KEY); + + + AMQShortString queueName = (AMQShortString) parameters[1]; + Boolean autoDelete = (Boolean) parameters[0]; + + if (autoDelete)// we have a temporary queue + { + return ((Boolean) create_queues.get(CREATE_QUEUE_TEMPORARY_KEY)) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + else + { + // If there is a white list then check + if (create_queues_queues == null || create_queues_queues.containsKey(queueName)) + { + return AuthzResult.ALLOWED; + } + else + { + return AuthzResult.DENIED; + } + + } + case CREATEEXCHANGE: + Map rights = (Map) _permissions.get(permission); + + AMQShortString exchangeName = (AMQShortString) parameters[0]; + + // If the exchange list is doesn't exist then all is allowed else + // check the valid exchanges + if (rights == null || rights.containsKey(exchangeName)) + { + return AuthzResult.ALLOWED; + } + else + { + return AuthzResult.DENIED; + } + case CONSUME: // Parameters : AMQQueue + + if (parameters.length == 1 && parameters[0] instanceof AMQQueue) + { + AMQQueue queue = ((AMQQueue) parameters[0]); + Map queuePermissions = (Map) _permissions.get(permission); + + List queues = (List) queuePermissions.get(CONSUME_QUEUES_KEY); + + Boolean temporayQueues = (Boolean) queuePermissions.get(CONSUME_TEMPORARY_KEY); + Boolean ownQueuesOnly = (Boolean) queuePermissions.get(CONSUME_OWN_QUEUES_ONLY_KEY); + + // If user is allowed to publish to temporary queues and this is a temp queue then allow it. + if (temporayQueues) + { + if (queue.isAutoDelete()) + // This will allow consumption from any temporary queue including ones not owned by this user. + // Of course the exclusivity will not be broken. + { + // if not limited to ownQueuesOnly then ok else check queue Owner. + return (!ownQueuesOnly || queue.getOwner().equals(_user)) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + else + { + return AuthzResult.DENIED; + } + } + + // if queues are white listed then ensure it is ok + if (queues != null) + { + // if no queues are listed then ALL are ok othereise it must be specified. + if (ownQueuesOnly) + { + if (queue.getOwner().equals(_user)) + { + return (queues.size() == 0 || queues.contains(queue.getName())) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + else + { + return AuthzResult.DENIED; + } + } + + // If we are + return (queues.size() == 0 || queues.contains(queue.getName())) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + } + + // Can't authenticate without the right parameters + return AuthzResult.DENIED; + case DELETE: + break; + + case PUBLISH: // Parameters : Exchange exchange, AMQShortString routingKey + Map publishRights = (Map) _permissions.get(permission); + + if (publishRights == null) + { + return AuthzResult.DENIED; + } + + Map exchanges = (Map) publishRights.get(PUBLISH_EXCHANGES_KEY); + + // Having no exchanges listed gives full publish rights to all exchanges + if (exchanges == null) + { + return AuthzResult.ALLOWED; + } + // Otherwise exchange must be listed in the white list + + // If the map doesn't have the exchange then it isn't allowed + if (!exchanges.containsKey(((Exchange) parameters[0]).getName())) + { + return AuthzResult.DENIED; + } + else + { + + // Get valid routing keys + HashSet routingKeys = (HashSet) exchanges.get(((Exchange)parameters[0]).getName()); + + // Having no routingKeys in the map then all are allowed. + if (routingKeys == null) + { + return AuthzResult.ALLOWED; + } + else + { + // We have routingKeys so a match must be found to allowed binding + Iterator keys = routingKeys.iterator(); + + + AMQShortString publishRKey = (AMQShortString)parameters[1]; + + boolean matched = false; + while (keys.hasNext() && !matched) + { + AMQShortString rkey = (AMQShortString) keys.next(); + + if (rkey.endsWith("*")) + { + matched = publishRKey.startsWith(rkey.subSequence(0, rkey.length() - 1)); + } + else + { + matched = publishRKey.equals(rkey); + } + } + return (matched) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + } + } + case PURGE: + break; + case UNBIND: + break; + + } + + return AuthzResult.DENIED; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java new file mode 100644 index 0000000000..13151a66b8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/VirtualHostAccess.java @@ -0,0 +1,68 @@ +/* + * 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.security.access; + +public class VirtualHostAccess +{ + private String _vhost; + private AccessRights _rights; + + public VirtualHostAccess(String vhostaccess) + { + //format <vhost>(<rights>) + int hostend = vhostaccess.indexOf('('); + + if (hostend == -1) + { + throw new IllegalArgumentException("VirtualHostAccess format string contains no access _rights"); + } + + _vhost = vhostaccess.substring(0, hostend); + + String rights = vhostaccess.substring(hostend); + + if (rights.indexOf('r') != -1) + { + if (rights.indexOf('w') != -1) + { + _rights = new AccessRights(AccessRights.Rights.READWRITE); + } + else + { + _rights = new AccessRights(AccessRights.Rights.READ); + } + } + else if (rights.indexOf('w') != -1) + { + _rights = new AccessRights(AccessRights.Rights.WRITE); + } + } + + public AccessRights getAccessRights() + { + return _rights; + } + + public String getVirtualHost() + { + return _vhost; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java new file mode 100644 index 0000000000..121f571abe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBean.java @@ -0,0 +1,501 @@ +/* + * 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.security.access.management; + +import org.apache.qpid.server.management.MBeanDescription; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanInvocationHandlerImpl; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.access.management.UserManagement; +import org.apache.log4j.Logger; +import org.apache.commons.configuration.ConfigurationException; + +import javax.management.JMException; +import javax.management.remote.JMXPrincipal; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.auth.Subject; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.FileOutputStream; +import java.util.Properties; +import java.util.List; +import java.util.Enumeration; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import java.security.Principal; +import java.security.AccessControlContext; +import java.security.AccessController; + +/** MBean class for AMQUserManagementMBean. It implements all the management features exposed for managing users. */ +@MBeanDescription("User Management Interface") +public class AMQUserManagementMBean extends AMQManagedObject implements UserManagement +{ + + private static final Logger _logger = Logger.getLogger(AMQUserManagementMBean.class); + + private PrincipalDatabase _principalDatabase; + private Properties _accessRights; + private File _accessFile; + + private ReentrantLock _accessRightsUpdate = new ReentrantLock(); + + // Setup for the TabularType + static TabularType _userlistDataType; // Datatype for representing User Lists + + static CompositeType _userDataType; // Composite type for representing User + static String[] _userItemNames = {"Username", "read", "write", "admin"}; + + static + { + String[] userItemDesc = {"Broker Login username", "Management Console Read Permission", + "Management Console Write Permission", "Management Console Admin Permission"}; + + OpenType[] userItemTypes = new OpenType[4]; // User item types. + userItemTypes[0] = SimpleType.STRING; // For Username + userItemTypes[1] = SimpleType.BOOLEAN; // For Rights - Read + userItemTypes[2] = SimpleType.BOOLEAN; // For Rights - Write + userItemTypes[3] = SimpleType.BOOLEAN; // For Rights - Admin + String[] userDataIndex = {_userItemNames[0]}; + + try + { + _userDataType = + new CompositeType("User", "User Data", _userItemNames, userItemDesc, userItemTypes); + + _userlistDataType = new TabularType("Users", "List of users", _userDataType, userDataIndex); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing users incorrect."); + _userlistDataType = null; + } + } + + + public AMQUserManagementMBean() throws JMException + { + super(UserManagement.class, UserManagement.TYPE, UserManagement.VERSION); + } + + public String getObjectInstanceName() + { + return UserManagement.TYPE; + } + + public boolean setPassword(String username, char[] password) + { + try + { + //delegate password changes to the Principal Database + return _principalDatabase.updatePassword(new UsernamePrincipal(username), password); + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to set password of non-existant user'" + username + "'"); + return false; + } + } + + public boolean setRights(String username, boolean read, boolean write, boolean admin) + { + + Object oldRights = null; + if ((oldRights =_accessRights.get(username)) == null) + { + // If the user doesn't exist in the access rights file check that they at least have an account. + if (_principalDatabase.getUser(username) == null) + { + return false; + } + } + + try + { + _accessRightsUpdate.lock(); + + // Update the access rights + if (admin) + { + _accessRights.put(username, MBeanInvocationHandlerImpl.ADMIN); + } + else + { + if (read | write) + { + if (read) + { + _accessRights.put(username, MBeanInvocationHandlerImpl.READONLY); + } + if (write) + { + _accessRights.put(username, MBeanInvocationHandlerImpl.READWRITE); + } + } + else + { + _accessRights.remove(username); + } + } + + //save the rights file + try + { + saveAccessFile(); + } + catch (IOException e) + { + _logger.warn("Problem occured saving '" + _accessFile + "', the access right changes will not be preserved: " + e); + + //the rights file was not successfully saved, restore user rights to previous value + _logger.warn("Reverting attempted rights update for user'" + username + "'"); + if (oldRights != null) + { + _accessRights.put(username, oldRights); + } + else + { + _accessRights.remove(username); + } + + return false; + } + } + finally + { + if (_accessRightsUpdate.isHeldByCurrentThread()) + { + _accessRightsUpdate.unlock(); + } + } + + return true; + } + + public boolean createUser(String username, char[] password, boolean read, boolean write, boolean admin) + { + if (_principalDatabase.createPrincipal(new UsernamePrincipal(username), password)) + { + if (!setRights(username, read, write, admin)) + { + //unable to set rights for user, remove account + try + { + _principalDatabase.deletePrincipal(new UsernamePrincipal(username)); + } + catch (AccountNotFoundException e) + { + //ignore + } + return false; + } + else + { + return true; + } + } + + return false; + } + + public boolean deleteUser(String username) + { + try + { + if (_principalDatabase.deletePrincipal(new UsernamePrincipal(username))) + { + try + { + _accessRightsUpdate.lock(); + + _accessRights.remove(username); + + try + { + saveAccessFile(); + } + catch (IOException e) + { + _logger.warn("Problem occured saving '" + _accessFile + "', the access right changes will not be preserved: " + e); + return false; + } + } + finally + { + if (_accessRightsUpdate.isHeldByCurrentThread()) + { + _accessRightsUpdate.unlock(); + } + } + } + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to delete user (" + username + ") that doesn't exist"); + return false; + } + + return true; + } + + public boolean reloadData() + { + try + { + loadAccessFile(); + _principalDatabase.reload(); + } + catch (ConfigurationException e) + { + _logger.warn("Reload failed due to:" + e); + return false; + } + catch (IOException e) + { + _logger.warn("Reload failed due to:" + e); + return false; + } + // Reload successful + return true; + } + + + @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.") + public TabularData viewUsers() + { + // Table of users + // Username(string), Access rights Read,Write,Admin(bool,bool,bool) + + if (_userlistDataType == null) + { + _logger.warn("TabluarData not setup correctly"); + return null; + } + + List<Principal> users = _principalDatabase.getUsers(); + + TabularDataSupport userList = new TabularDataSupport(_userlistDataType); + + try + { + // Create the tabular list of message header contents + for (Principal user : users) + { + // Create header attributes list + + String rights = (String) _accessRights.get(user.getName()); + + Boolean read = false; + Boolean write = false; + Boolean admin = false; + + if (rights != null) + { + read = rights.equals(MBeanInvocationHandlerImpl.READONLY) + || rights.equals(MBeanInvocationHandlerImpl.READWRITE); + write = rights.equals(MBeanInvocationHandlerImpl.READWRITE); + admin = rights.equals(MBeanInvocationHandlerImpl.ADMIN); + } + + Object[] itemData = {user.getName(), read, write, admin}; + CompositeData messageData = new CompositeDataSupport(_userDataType, _userItemNames, itemData); + userList.put(messageData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create user list due to :" + e); + return null; + } + + return userList; + } + + /*** Broker Methods **/ + + /** + * setPrincipalDatabase + * + * @param database set The Database to use for user lookup + */ + public void setPrincipalDatabase(PrincipalDatabase database) + { + _principalDatabase = database; + } + + /** + * setAccessFile + * + * @param accessFile the file to use for updating. + * + * @throws java.io.IOException If the file cannot be accessed + * @throws org.apache.commons.configuration.ConfigurationException + * if checks on the file fail. + */ + public void setAccessFile(String accessFile) throws IOException, ConfigurationException + { + if (accessFile != null) + { + _accessFile = new File(accessFile); + if (!_accessFile.exists()) + { + throw new ConfigurationException("'" + _accessFile + "' does not exist"); + } + + if (!_accessFile.canRead()) + { + throw new ConfigurationException("Cannot read '" + _accessFile + "'."); + } + + if (!_accessFile.canWrite()) + { + _logger.warn("Unable to write to access rights file '" + _accessFile + "', changes will not be preserved."); + } + + loadAccessFile(); + } + else + { + _logger.warn("Access rights file specified is null. Access rights not changed."); + } + } + + private void loadAccessFile() throws IOException, ConfigurationException + { + if(_accessFile == null) + { + _logger.error("No jmx access rights file has been specified."); + return; + } + + if(_accessFile.exists()) + { + try + { + _accessRightsUpdate.lock(); + + Properties accessRights = new Properties(); + accessRights.load(new FileInputStream(_accessFile)); + checkAccessRights(accessRights); + setAccessRights(accessRights); + } + finally + { + if (_accessRightsUpdate.isHeldByCurrentThread()) + { + _accessRightsUpdate.unlock(); + } + } + } + else + { + _logger.error("Specified jmxaccess rights file '" + _accessFile + "' does not exist."); + } + } + + private void checkAccessRights(Properties accessRights) + { + Enumeration values = accessRights.propertyNames(); + + while (values.hasMoreElements()) + { + String user = (String) values.nextElement(); + + if (_principalDatabase.getUser(user) == null) + { + _logger.warn("Access rights contains user '" + user + "' but there is no authentication data for that user"); + } + } + } + + private void saveAccessFile() throws IOException + { + try + { + _accessRightsUpdate.lock(); + + // Create temporary file + File tmp = File.createTempFile(_accessFile.getName(), ".tmp"); + + FileOutputStream output = new FileOutputStream(tmp); + _accessRights.store(output, "Generated by AMQUserManagementMBean Console : Last edited by user:" + getCurrentJMXUser()); + output.close(); + + // Rename new file to main file + tmp.renameTo(_accessFile); + + // delete tmp + tmp.delete(); + } + finally + { + if (_accessRightsUpdate.isHeldByCurrentThread()) + { + _accessRightsUpdate.unlock(); + } + } + + } + + private String getCurrentJMXUser() + { + AccessControlContext acc = AccessController.getContext(); + + Subject subject = Subject.getSubject(acc); + if (subject == null) + { + return "Unknown user, authentication Subject was null"; + } + + // Retrieve JMXPrincipal from Subject + Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); + if (principals == null || principals.isEmpty()) + { + return "Unknown user principals were null"; + } + + Principal principal = principals.iterator().next(); + return principal.getName(); + } + + /** + * user=read user=write user=readwrite user=admin + * + * @param accessRights The properties list of access rights to process + */ + private void setAccessRights(Properties accessRights) + { + _logger.debug("Setting Access Rights:" + accessRights); + _accessRights = accessRights; + MBeanInvocationHandlerImpl.setAccessRights(_accessRights); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java new file mode 100644 index 0000000000..9fcdd4cd17 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/management/UserManagement.java @@ -0,0 +1,121 @@ +/* + * 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.security.access.management; + +import org.apache.qpid.server.management.MBeanOperation; +import org.apache.qpid.server.management.MBeanOperationParameter; +import org.apache.qpid.server.management.MBeanAttribute; +import org.apache.qpid.AMQException; + +import javax.management.openmbean.TabularData; +import javax.management.openmbean.CompositeData; +import javax.management.JMException; +import javax.management.MBeanOperationInfo; +import java.io.IOException; + +public interface UserManagement +{ + + String TYPE = "UserManagement"; + int VERSION = 2; + + //********** Operations *****************// + /** + * set password for user + * + * @param username The username to create + * @param password The password for the user + * + * @return The result of the operation + */ + @MBeanOperation(name = "setPassword", description = "Set password for user.", + impact = MBeanOperationInfo.ACTION) + boolean setPassword(@MBeanOperationParameter(name = "username", description = "Username")String username, + @MBeanOperationParameter(name = "password", description = "Password")char[] password); + + /** + * set rights for users with given details + * + * @param username The username to create + * @param read The set of permission to give the new user + * @param write The set of permission to give the new user + * @param admin The set of permission to give the new user + * + * @return The result of the operation + */ + @MBeanOperation(name = "setRights", description = "Set access rights for user.", + impact = MBeanOperationInfo.ACTION) + boolean setRights(@MBeanOperationParameter(name = "username", description = "Username")String username, + @MBeanOperationParameter(name = "read", description = "Administration read")boolean read, + @MBeanOperationParameter(name = "readAndWrite", description = "Administration write")boolean write, + @MBeanOperationParameter(name = "admin", description = "Administration rights")boolean admin); + + /** + * Create users with given details + * + * @param username The username to create + * @param password The password for the user + * @param read The set of permission to give the new user + * @param write The set of permission to give the new user + * @param admin The set of permission to give the new user + * + * @return The result of the operation + */ + @MBeanOperation(name = "createUser", description = "Create new user from system.", + impact = MBeanOperationInfo.ACTION) + boolean createUser(@MBeanOperationParameter(name = "username", description = "Username")String username, + @MBeanOperationParameter(name = "password", description = "Password")char[] password, + @MBeanOperationParameter(name = "read", description = "Administration read")boolean read, + @MBeanOperationParameter(name = "readAndWrite", description = "Administration write")boolean write, + @MBeanOperationParameter(name = "admin", description = "Administration rights")boolean admin); + + /** + * View users returns all the users that are currently available to the system. + * + * @param username The user to delete + * + * @return The result of the operation + */ + @MBeanOperation(name = "deleteUser", description = "Delete user from system.", + impact = MBeanOperationInfo.ACTION) + boolean deleteUser(@MBeanOperationParameter(name = "username", description = "Username")String username); + + + /** + * Reload the date from disk + * + * @return The result of the operation + */ + @MBeanOperation(name = "reloadData", description = "Reload the authentication file from disk.", + impact = MBeanOperationInfo.ACTION) + boolean reloadData(); + + /** + * View users returns all the users that are currently available to the system. + * + * @return a table of users data (Username, read, write, admin) + */ + @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.", + impact = MBeanOperationInfo.INFO) + TabularData viewUsers(); + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java new file mode 100644 index 0000000000..682135bc25 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AbstractACLPlugin.java @@ -0,0 +1,99 @@ +/* + * + * 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.security.access.plugins; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * This ACLPlugin abstains from all votes. Useful if your plugin only cares about a few operations. + */ +public abstract class AbstractACLPlugin implements ACLPlugin +{ + + private static final AuthzResult DEFAULT_ANSWER = AuthzResult.ABSTAIN; + + public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue, + AMQShortString routingKey) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal, + boolean nowait, AMQQueue queue) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable, + AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType) + { + // TODO Auto-generated method stub + return null; + } + + public AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable, + boolean exclusive, boolean nowait, boolean passive, AMQShortString queue) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory, + AMQShortString routingKey, Exchange e) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue) + { + return DEFAULT_ANSWER; + } + + public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey, + AMQQueue queue) + { + return DEFAULT_ANSWER; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java new file mode 100644 index 0000000000..4af178574b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java @@ -0,0 +1,54 @@ +/* + * 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.security.access.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; + +public class AllowAll extends BasicACLPlugin +{ + + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return false; + } + + public ACLPlugin newInstance(Configuration config) + { + return new AllowAll(); + } + }; + + public String getPluginName() + { + return this.getClass().getSimpleName(); + } + + @Override + protected AuthzResult getResult() + { + // Always allow + return AuthzResult.ALLOWED; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java new file mode 100644 index 0000000000..f7e537b02b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicACLPlugin.java @@ -0,0 +1,129 @@ +/* + * 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.security.access.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public abstract class BasicACLPlugin implements ACLPlugin +{ + + // Returns true or false if the plugin should authorise or deny the request + protected abstract AuthzResult getResult(); + + @Override + public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, + AMQQueue queue, AMQShortString routingKey) + { + return getResult(); + } + + @Override + public AuthzResult authoriseConnect(AMQProtocolSession session, + VirtualHost virtualHost) + { + return getResult(); + } + + @Override + public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, + AMQQueue queue) + { + return getResult(); + } + + @Override + public AuthzResult authoriseConsume(AMQProtocolSession session, + boolean exclusive, boolean noAck, boolean noLocal, boolean nowait, + AMQQueue queue) + { + return getResult(); + } + + @Override + public AuthzResult authoriseCreateExchange(AMQProtocolSession session, + boolean autoDelete, boolean durable, AMQShortString exchangeName, + boolean internal, boolean nowait, boolean passive, + AMQShortString exchangeType) + { + return getResult(); + } + + @Override + public AuthzResult authoriseCreateQueue(AMQProtocolSession session, + boolean autoDelete, boolean durable, boolean exclusive, + boolean nowait, boolean passive, AMQShortString queue) + { + return getResult(); + } + + @Override + public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue) + { + return getResult(); + } + + @Override + public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange) + { + return getResult(); + } + + @Override + public AuthzResult authorisePublish(AMQProtocolSession session, + boolean immediate, boolean mandatory, AMQShortString routingKey, + Exchange e) + { + return getResult(); + } + + @Override + public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue) + { + return getResult(); + } + + @Override + public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, + AMQShortString routingKey, AMQQueue queue) + { + return getResult(); + } + + @Override + public void setConfiguration(Configuration config) + { + // no-op + } + + public boolean supportsTag(String name) + { + // This plugin doesn't support any tags + return false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java new file mode 100644 index 0000000000..26a76c9af1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.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.security.access.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; +import org.apache.qpid.server.security.access.AccessResult; +import org.apache.qpid.server.security.access.Permission; + +public class DenyAll extends BasicACLPlugin +{ + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return false; + } + + public ACLPlugin newInstance(Configuration config) + { + return new DenyAll(); + } + }; + + public AccessResult authorise(AMQProtocolSession session, + Permission permission, AMQMethodBody body, Object... parameters) + throws AMQConnectionException + { + + if (ACLManager.getLogger().isInfoEnabled()) + { + ACLManager.getLogger().info( + "Denying user:" + session.getAuthorizedID()); + } + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, + "DenyAll Plugin"); + } + + public String getPluginName() + { + return getClass().getSimpleName(); + } + + @Override + protected AuthzResult getResult() + { + // Always deny + return AuthzResult.DENIED; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java new file mode 100644 index 0000000000..fc1bc048d4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccessPlugin.java @@ -0,0 +1,71 @@ +/* + * 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.security.access.plugins; + +import java.util.Collection; +import java.util.HashSet; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; + +/** + * + * Used to suppress warnings in legacy config files that have things in <security> which aren't handled by a plugin directly. + * + */ +public class LegacyAccessPlugin extends BasicACLPlugin +{ + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + private Collection maskedTags = new HashSet<String>(); + { + maskedTags.add("principal-databases"); + maskedTags.add("access"); + maskedTags.add("msg-auth"); + maskedTags.add("false"); + maskedTags.add("jmx"); + } + + public boolean supportsTag(String name) + { + return maskedTags .contains(name); + } + + public ACLPlugin newInstance(Configuration config) + { + return new LegacyAccessPlugin(); + } + }; + + public String getPluginName() + { + return getClass().getSimpleName(); + } + + @Override + protected AuthzResult getResult() + { + // Always abstain + return AuthzResult.ABSTAIN; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java new file mode 100644 index 0000000000..2cc0c530de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/SimpleXML.java @@ -0,0 +1,432 @@ +/* + * 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.security.access.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicConsumeBody; +import org.apache.qpid.framing.BasicPublishBody; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; +import org.apache.qpid.server.security.access.AccessResult; +import org.apache.qpid.server.security.access.Permission; +import org.apache.qpid.server.security.access.PrincipalPermissions; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This uses the default + */ +public class SimpleXML implements ACLPlugin +{ + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return name.startsWith("access_control_list"); + } + + public ACLPlugin newInstance(Configuration config) + { + SimpleXML plugin = new SimpleXML(); + plugin.setConfiguration(config); + return plugin; + } + }; + + private Map<String, PrincipalPermissions> _users; + private final AccessResult GRANTED = new AccessResult(this, AccessResult.AccessStatus.GRANTED); + + public SimpleXML() + { + _users = new ConcurrentHashMap<String, PrincipalPermissions>(); + } + + public void setConfiguration(Configuration config) + { + processConfig(config); + } + + private void processConfig(Configuration config) + { + processPublish(config); + + processConsume(config); + + processCreate(config); + } + + /** + * Publish format takes Exchange + Routing Key Pairs + * + * @param config + * XML Configuration + */ + private void processPublish(Configuration config) + { + Configuration publishConfig = config.subset("access_control_list.publish"); + + // Process users that have full publish permission + String[] users = publishConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.PUBLISH, user); + } + + // Process exchange limited users + int exchangeCount = 0; + Configuration exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + // Get Exchange Name + AMQShortString exchangeName = new AMQShortString(exchangeConfig.getString("name")); + + // Get Routing Keys + int keyCount = 0; + Configuration routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")"); + + while (!routingkeyConfig.isEmpty()) + { + // Get RoutingKey Value + AMQShortString routingKeyValue = new AMQShortString(routingkeyConfig.getString("value")); + + // Apply Exchange + RoutingKey permissions to Users + users = routingkeyConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.PUBLISH, user, exchangeName, routingKeyValue); + } + + // Apply permissions to Groups + + // Check for more configs + keyCount++; + routingkeyConfig = exchangeConfig.subset("routing_keys.routing_key(" + keyCount + ")"); + } + + // Apply Exchange wide permissions to Users + users = exchangeConfig.getStringArray("exchange(" + exchangeCount + ").users.user"); + + for (String user : users) + { + grant(Permission.PUBLISH, user, exchangeName); + } + + // Apply permissions to Groups + exchangeCount++; + exchangeConfig = publishConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + } + + private void grant(Permission permission, String user, Object... parameters) + { + PrincipalPermissions permissions = _users.get(user); + + if (permissions == null) + { + permissions = new PrincipalPermissions(user); + } + + _users.put(user, permissions); + permissions.grant(permission, parameters); + } + + private void processConsume(Configuration config) + { + Configuration consumeConfig = config.subset("access_control_list.consume"); + + // Process queue limited users + int queueCount = 0; + Configuration queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")"); + + while (!queueConfig.isEmpty()) + { + // Get queue Name + AMQShortString queueName = new AMQShortString(queueConfig.getString("name")); + // if there is no name then there may be a temporary element + boolean temporary = queueConfig.containsKey("temporary"); + boolean ownQueues = queueConfig.containsKey("own_queues"); + + // Process permissions for this queue + String[] users = queueConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.CONSUME, user, queueName, temporary, ownQueues); + } + + // See if we have another config + queueCount++; + queueConfig = consumeConfig.subset("queues.queue(" + queueCount + ")"); + } + + // Process users that have full consume permission + String[] users = consumeConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.CONSUME, user); + } + } + + private void processCreate(Configuration config) + { + Configuration createConfig = config.subset("access_control_list.create"); + + // Process create permissions for queue creation + int queueCount = 0; + Configuration queueConfig = createConfig.subset("queues.queue(" + queueCount + ")"); + + while (!queueConfig.isEmpty()) + { + // Get queue Name + AMQShortString queueName = new AMQShortString(queueConfig.getString("name")); + + // if there is no name then there may be a temporary element + boolean temporary = queueConfig.containsKey("temporary"); + + int exchangeCount = 0; + Configuration exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + + AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name")); + AMQShortString routingKey = new AMQShortString(exchangeConfig.getString("routing_key")); + + // Process permissions for this queue + String[] users = exchangeConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.CREATEEXCHANGE, user, exchange); + grant(Permission.CREATEQUEUE, user, temporary, (queueName.equals("") ? null : queueName), (exchange + .equals("") ? null : exchange), (routingKey.equals("") ? null : routingKey)); + } + + // See if we have another config + exchangeCount++; + exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + + // Process users that are not bound to an exchange + String[] users = queueConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.CREATEQUEUE, user, temporary, queueName); + } + + // See if we have another config + queueCount++; + queueConfig = createConfig.subset("queues.queue(" + queueCount + ")"); + } + + // Process create permissions for exchange creation + int exchangeCount = 0; + Configuration exchangeConfig = createConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + + while (!exchangeConfig.isEmpty()) + { + AMQShortString exchange = new AMQShortString(exchangeConfig.getString("name")); + AMQShortString clazz = new AMQShortString(exchangeConfig.getString("class")); + + // Process permissions for this queue + String[] users = exchangeConfig.getStringArray("users.user"); + for (String user : users) + { + grant(Permission.CREATEEXCHANGE, user, exchange, clazz); + } + + // See if we have another config + exchangeCount++; + exchangeConfig = queueConfig.subset("exchanges.exchange(" + exchangeCount + ")"); + } + + // Process users that have full create permission + String[] users = createConfig.getStringArray("users.user"); + + for (String user : users) + { + grant(Permission.CREATEEXCHANGE, user); + grant(Permission.CREATEQUEUE, user); + } + + } + + public String getPluginName() + { + return "Simple"; + } + + public AuthzResult authoriseBind(AMQProtocolSession session, Exchange exch, AMQQueue queue, AMQShortString routingKey) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.BIND, null, exch, queue, routingKey); + } + } + + public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.ACCESS); + } + } + + public AuthzResult authoriseConsume(AMQProtocolSession session, boolean noAck, AMQQueue queue) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.CONSUME, queue); + } + } + + public AuthzResult authoriseConsume(AMQProtocolSession session, boolean exclusive, boolean noAck, boolean noLocal, + boolean nowait, AMQQueue queue) + { + return authoriseConsume(session, noAck, queue); + } + + public AuthzResult authoriseCreateExchange(AMQProtocolSession session, boolean autoDelete, boolean durable, + AMQShortString exchangeName, boolean internal, boolean nowait, boolean passive, AMQShortString exchangeType) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.CREATEEXCHANGE, exchangeName); + } + } + + public AuthzResult authoriseCreateQueue(AMQProtocolSession session, boolean autoDelete, boolean durable, boolean exclusive, + boolean nowait, boolean passive, AMQShortString queue) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.CREATEQUEUE, autoDelete, queue); + } + } + + public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.DELETE); + } + } + + public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.DELETE); + } + } + + public AuthzResult authorisePublish(AMQProtocolSession session, boolean immediate, boolean mandatory, + AMQShortString routingKey, Exchange e) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.PUBLISH, e, routingKey); + } + } + + public AuthzResult authorisePurge(AMQProtocolSession session, AMQQueue queue) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.PURGE); + } + } + + public AuthzResult authoriseUnbind(AMQProtocolSession session, Exchange exch, AMQShortString routingKey, AMQQueue queue) + { + PrincipalPermissions principalPermissions = _users.get(session.getAuthorizedID().getName()); + if (principalPermissions == null) + { + return AuthzResult.DENIED; + } + else + { + return principalPermissions.authorise(Permission.UNBIND); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.java new file mode 100644 index 0000000000..a1a399e5bf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallFactory.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.security.access.plugins.network; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; + +public class FirewallFactory implements ACLPluginFactory +{ + + @Override + public ACLPlugin newInstance(Configuration config) throws ConfigurationException + { + FirewallPlugin plugin = new FirewallPlugin(); + plugin.setConfiguration(config); + return plugin; + } + + @Override + public boolean supportsTag(String name) + { + return name.equals("firewall"); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java new file mode 100644 index 0000000000..85026121ab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/network/FirewallPlugin.java @@ -0,0 +1,264 @@ +/* + * + * 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.security.access.plugins.network; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; +import org.apache.qpid.server.security.access.plugins.AbstractACLPlugin; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.util.NetMatcher; + +public class FirewallPlugin extends AbstractACLPlugin +{ + + public class FirewallPluginException extends Exception {} + + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return name.startsWith("firewall"); + } + + public ACLPlugin newInstance(Configuration config) throws ConfigurationException + { + FirewallPlugin plugin = new FirewallPlugin(); + plugin.setConfiguration(config); + return plugin; + } + }; + + public class FirewallRule + { + + private static final long DNS_TIMEOUT = 30000; + private AuthzResult _access; + private NetMatcher _network; + private Pattern[] _hostnamePatterns; + + public FirewallRule(String access, List networks, List hostnames) + { + _access = (access.equals("allow")) ? AuthzResult.ALLOWED : AuthzResult.DENIED; + + if (networks != null && networks.size() > 0) + { + String[] networkStrings = objListToStringArray(networks); + _network = new NetMatcher(networkStrings); + } + + if (hostnames != null && hostnames.size() > 0) + { + int i = 0; + _hostnamePatterns = new Pattern[hostnames.size()]; + for (String hostname : objListToStringArray(hostnames)) + { + _hostnamePatterns[i++] = Pattern.compile(hostname); + } + } + + } + + private String[] objListToStringArray(List objList) + { + String[] networkStrings = new String[objList.size()]; + int i = 0; + for (Object network : objList) + { + networkStrings[i++] = (String) network; + } + return networkStrings; + } + + public boolean match(InetAddress remote) throws FirewallPluginException + { + if (_hostnamePatterns != null) + { + String hostname = getHostname(remote); + if (hostname == null) + { + throw new FirewallPluginException(); + } + for (Pattern pattern : _hostnamePatterns) + { + if (pattern.matcher(hostname).matches()) + { + return true; + } + } + return false; + } + else + { + return _network.matchInetNetwork(remote); + } + } + + /** + * @param remote the InetAddress to look up + * @return the hostname, null if not found or takes longer than 30s to find + */ + private String getHostname(final InetAddress remote) + { + final String[] hostname = new String[]{null}; + final AtomicBoolean done = new AtomicBoolean(false); + // Spawn thread + Thread thread = new Thread(new Runnable() + { + public void run() + { + hostname[0] = remote.getCanonicalHostName(); + done.getAndSet(true); + synchronized (done) + { + done.notifyAll(); + } + } + }); + + thread.run(); + long endTime = System.currentTimeMillis() + DNS_TIMEOUT; + + while (System.currentTimeMillis() < endTime && !done.get()) + { + try + { + synchronized (done) + { + done.wait(endTime - System.currentTimeMillis()); + } + } + catch (InterruptedException e) + { + // Check the time and if necessary sleep for a bit longer + } + } + return hostname[0]; + } + + public AuthzResult getAccess() + { + return _access; + } + + } + + private AuthzResult _default = AuthzResult.ABSTAIN; + private FirewallRule[] _rules; + + @Override + public AuthzResult authoriseConnect(AMQProtocolSession session, VirtualHost virtualHost) + { + if (!(session instanceof AMQMinaProtocolSession)) + { + return AuthzResult.ABSTAIN; // We only deal with tcp sessions, which + // mean MINA right now + } + + InetAddress addr = getInetAdressFromMinaSession((AMQMinaProtocolSession) session); + + if (addr == null) + { + return AuthzResult.ABSTAIN; // Not an Inet socket on the other end + } + + boolean match = false; + for (FirewallRule rule : _rules) + { + try + { + match = rule.match(addr); + } + catch (FirewallPluginException e) + { + return AuthzResult.DENIED; + } + if (match) + { + return rule.getAccess(); + } + } + return _default; + + } + + private InetAddress getInetAdressFromMinaSession(AMQMinaProtocolSession session) + { + SocketAddress remote = session.getIOSession().getRemoteAddress(); + if (remote instanceof InetSocketAddress) + { + return ((InetSocketAddress) remote).getAddress(); + } + else + { + return null; + } + } + + @Override + public void setConfiguration(Configuration config) throws ConfigurationException + { + // Get default action + String defaultAction = config.getString("[@default-action]"); + if (defaultAction == null) + { + _default = AuthzResult.ABSTAIN; + } + else if (defaultAction.toLowerCase().equals("allow")) + { + _default = AuthzResult.ALLOWED; + } + else + { + _default = AuthzResult.DENIED; + } + CompositeConfiguration finalConfig = new CompositeConfiguration(config); + + List subFiles = config.getList("firewall.xml[@fileName]"); + for (Object subFile : subFiles) + { + finalConfig.addConfiguration(new XMLConfiguration((String) subFile)); + } + + // all rules must have an access attribute + int numRules = finalConfig.getList("rule[@access]").size(); + _rules = new FirewallRule[numRules]; + for (int i = 0; i < numRules; i++) + { + FirewallRule rule = new FirewallRule(finalConfig.getString("rule(" + i + ")[@access]"), finalConfig.getList("rule(" + + i + ")[@network]"), finalConfig.getList("rule(" + i + ")[@hostname]")); + _rules[i] = rule; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java new file mode 100644 index 0000000000..3f846b9dd0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java @@ -0,0 +1,63 @@ +/* + * + * 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.security.auth; + +import javax.security.sasl.SaslException; + +public class AuthenticationResult +{ + public enum AuthenticationStatus + { + SUCCESS, CONTINUE, ERROR + } + + public AuthenticationStatus status; + public byte[] challenge; + + private Exception cause; + + public AuthenticationResult(AuthenticationStatus status) + { + this(null, status, null); + } + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status) + { + this(challenge, status, null); + } + + public AuthenticationResult(AuthenticationStatus error, Exception cause) + { + this(null, error, cause); + } + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status, Exception cause) + { + this.status = status; + this.challenge = challenge; + this.cause = cause; + } + + public Exception getCause() + { + return cause; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..3c211746e3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java @@ -0,0 +1,541 @@ +/* + * 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.security.auth.database; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.access.management.AMQUserManagementMBean; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: username:password username1:password1 ... usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in plain text. + */ +public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase +{ + private static final Logger _logger = Logger.getLogger(Base64MD5PasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + AMQUserManagementMBean _mbean; + public static final String DEFAULT_ENCODING = "utf-8"; + private Map<String, HashedUser> _users = new HashMap<String, HashedUser>(); + private ReentrantLock _userUpdate = new ReentrantLock(); + + public Base64MD5PasswordFilePrincipalDatabase() + { + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for MD5 Password file. + */ + + // Accept Plain incomming and hash it for comparison to the file. + CRAMMD5HashedInitialiser cram = new CRAMMD5HashedInitialiser(); + cram.initialise(this); + _saslServers.put(cram.getMechanismName(), cram); + + //fixme The PDs should setup a PD Mangement MBean +// try +// { +// _mbean = new AMQUserManagementMBean(); +// _mbean.setPrincipalDatabase(this); +// } +// catch (JMException e) +// { +// _logger.warn("User management disabled as unable to create MBean:" + e); +// } + } + + public void setPasswordFile(String passwordFile) throws IOException + { + File f = new File(passwordFile); + _logger.info("PasswordFilePrincipalDatabase using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws AccountNotFoundException If the Principal cannont be found in this Database + */ + public void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + + char[] pwd = lookupPassword(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray; + try + { + MD5byteArray = HashedUser.getMD5(byteArray); + } + catch (Exception e1) + { + _logger.warn("Unable to hash password for user '" + principal + "' for comparison"); + return false; + } + + char[] hashedPassword = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + hashedPassword[index++] = (char) c; + } + + return compareCharArray(pwd, hashedPassword); + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + HashedUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + try + { + _userUpdate.lock(); + char[] orig = user.getPassword(); + user.setPassword(password,false); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.error("Unable to save password file, password change for user'" + + principal + "' will revert at restart"); + //revert the password change + user.setPassword(orig,true); + return false; + } + return true; + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + catch (Exception e) + { + return false; + } + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_users.get(principal.getName()) != null) + { + return false; + } + + HashedUser user; + try + { + user = new HashedUser(principal.getName(), password); + } + catch (Exception e1) + { + _logger.warn("Unable to create new user '" + principal.getName() + "'"); + return false; + } + + + try + { + _userUpdate.lock(); + _users.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _users.remove(user.getName()); + return false; + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + HashedUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.warn("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _users.remove(user.getName()); + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + + return true; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(_users.values()); + } + + public Principal getUser(String username) + { + if (_users.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + /** + * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it + * creates strings of passwords. It should be modified to create only char arrays which get nulled out. + * + * @param name The principal name to lookup + * + * @return a char[] for use in SASL. + */ + private char[] lookupPassword(String name) + { + HashedUser user = _users.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + _users.clear(); + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + continue; + } + + HashedUser user = new HashedUser(result); + _logger.info("Created user:" + user); + _users.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + private void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + File tmp = File.createTempFile(_passwordFile.getName(), ".tmp"); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + HashedUser user = _users.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + try + { + byte[] encodedPassword = user.getEncodedPassword(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + + user.saved(); + } + catch (Exception e) + { + _logger.warn("Unable to encode new password reverting to old password."); + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + } + } + } + + for (HashedUser user : _users.values()) + { + if (user.isModified()) + { + byte[] encodedPassword; + try + { + encodedPassword = user.getEncodedPassword(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + user.saved(); + } + catch (Exception e) + { + _logger.warn("Unable to get Encoded password for user'" + user.getName() + "' password not saved"); + } + } + } + } + finally + { + if (reader != null) + { + reader.close(); + } + + if (writer != null) + { + writer.close(); + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + _passwordFile.renameTo(old); + tmp.renameTo(_passwordFile); + tmp.delete(); + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + public void reload() throws IOException + { + loadPasswordFile(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java new file mode 100644 index 0000000000..e0d4c49af1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.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.security.auth.database; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import org.apache.log4j.Logger; + +import org.apache.qpid.configuration.PropertyUtils; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.access.management.AMQUserManagementMBean; +import org.apache.qpid.AMQException; + +import javax.management.JMException; + +public class ConfigurationFilePrincipalDatabaseManager implements PrincipalDatabaseManager +{ + private static final Logger _logger = Logger.getLogger(ConfigurationFilePrincipalDatabaseManager.class); + + Map<String, PrincipalDatabase> _databases; + + public ConfigurationFilePrincipalDatabaseManager(ServerConfiguration _configuration) throws Exception + { + _logger.info("Initialising PrincipleDatabase authentication manager"); + _databases = initialisePrincipalDatabases(_configuration); + } + + private Map<String, PrincipalDatabase> initialisePrincipalDatabases(ServerConfiguration _configuration) throws Exception + { + List<String> databaseNames = _configuration.getPrincipalDatabaseNames(); + List<String> databaseClasses = _configuration.getPrincipalDatabaseClass(); + Map<String, PrincipalDatabase> databases = new HashMap<String, PrincipalDatabase>(); + + if (databaseNames.size() == 0) + { + _logger.warn("No Principal databases specified. Broker running with NO AUTHENTICATION"); + } + + for (int i = 0; i < databaseNames.size(); i++) + { + Object o; + try + { + o = Class.forName(databaseClasses.get(i)).newInstance(); + } + catch (Exception e) + { + throw new Exception("Error initialising principal database: " + e, e); + } + + if (!(o instanceof PrincipalDatabase)) + { + throw new Exception("Principal databases must implement the PrincipalDatabase interface"); + } + + initialisePrincipalDatabase((PrincipalDatabase) o, _configuration, i); + + String name = databaseNames.get(i); + if ((name == null) || (name.length() == 0)) + { + throw new Exception("Principal database names must have length greater than or equal to one character"); + } + + PrincipalDatabase pd = databases.get(name); + if (pd != null) + { + throw new Exception("Duplicate principal database name not permitted"); + } + + _logger.info("Initialised principal database '" + name + "' successfully"); + databases.put(name, (PrincipalDatabase) o); + } + + return databases; + } + + private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, ServerConfiguration _configuration, int index) + throws FileNotFoundException, ConfigurationException + { + List<String> argumentNames = _configuration.getPrincipalDatabaseAttributeNames(index); + List<String> argumentValues = _configuration.getPrincipalDatabaseAttributeValues(index); + for (int i = 0; i < argumentNames.size(); i++) + { + String argName = argumentNames.get(i); + if ((argName == null) || (argName.length() == 0)) + { + throw new ConfigurationException("Argument names must have length >= 1 character"); + } + + if (Character.isLowerCase(argName.charAt(0))) + { + argName = Character.toUpperCase(argName.charAt(0)) + argName.substring(1); + } + + String methodName = "set" + argName; + Method method = null; + try + { + method = principalDatabase.getClass().getMethod(methodName, String.class); + } + catch (Exception e) + { + // do nothing.. as on error method will be null + } + + if (method == null) + { + throw new ConfigurationException("No method " + methodName + " found in class " + + principalDatabase.getClass() + + " hence unable to configure principal database. The method must be public and " + + "have a single String argument with a void return type"); + } + + try + { + method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i))); + } + catch (Exception ite) + { + if (ite instanceof ConfigurationException) + { + throw(ConfigurationException) ite; + } + else + { + throw new ConfigurationException(ite.getMessage(), ite); + } + } + } + } + + public Map<String, PrincipalDatabase> getDatabases() + { + return _databases; + } + + public void initialiseManagement(ServerConfiguration config) throws ConfigurationException + { + try + { + AMQUserManagementMBean _mbean = new AMQUserManagementMBean(); + + List<String> principalDBs = config.getManagementPrincipalDBs(); + + if (principalDBs.size() == 0) + { + throw new ConfigurationException("No principal-database specified for jmx security"); + } + + String databaseName = principalDBs.get(0); + + PrincipalDatabase database = getDatabases().get(databaseName); + + if (database == null) + { + throw new ConfigurationException("Principal-database '" + databaseName + "' not found"); + } + + _mbean.setPrincipalDatabase(database); + + List<String> jmxaccesslist = config.getManagementAccessList(); + + if (jmxaccesslist.size() == 0) + { + throw new ConfigurationException("No access control files specified for jmx security"); + } + + String jmxaccesssFile = null; + + try + { + jmxaccesssFile = PropertyUtils.replaceProperties(jmxaccesslist.get(0)); + } + catch (PropertyException e) + { + throw new ConfigurationException("Unable to parse access control filename '" + jmxaccesssFile + "'"); + } + + try + { + _mbean.setAccessFile(jmxaccesssFile); + } + catch (IOException e) + { + _logger.warn("Unable to load access file:" + jmxaccesssFile); + } + + try + { + _mbean.register(); + } + catch (AMQException e) + { + _logger.warn("Unable to register user management MBean"); + } + } + catch (JMException e) + { + _logger.warn("User management disabled as unable to create MBean:" + e); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java new file mode 100644 index 0000000000..3690e7f92a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java @@ -0,0 +1,169 @@ +/* +* + * 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.security.auth.database; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; + +public class HashedUser implements Principal +{ + private static final Logger _logger = Logger.getLogger(HashedUser.class); + + String _name; + char[] _password; + byte[] _encodedPassword = null; + private boolean _modified = false; + private boolean _deleted = false; + + HashedUser(String[] data) throws UnsupportedEncodingException + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + byte[] encoded_password = data[1].getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + + Base64 b64 = new Base64(); + byte[] decoded = b64.decode(encoded_password); + + _encodedPassword = encoded_password; + + _password = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + _password[index++] = (char) c; + } + } + + public HashedUser(String name, char[] password) throws UnsupportedEncodingException, NoSuchAlgorithmException + { + _name = name; + setPassword(password,false); + } + + public static byte[] getMD5(byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException + { + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + return md.digest(); + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + char[] getPassword() + { + return _password; + } + + void setPassword(char[] password, boolean alreadyHashed) throws UnsupportedEncodingException, NoSuchAlgorithmException + { + if(alreadyHashed){ + _password = password; + } + else + { + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray = getMD5(byteArray); + + _password = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + _password[index++] = (char) c; + } + } + + _modified = true; + _encodedPassword = null; + } + + byte[] getEncodedPassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException + { + if (_encodedPassword == null) + { + encodePassword(); + } + return _encodedPassword; + } + + private void encodePassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException + { + byte[] byteArray = new byte[_password.length]; + int index = 0; + for (char c : _password) + { + byteArray[index++] = (byte) c; + } + _encodedPassword = (new Base64()).encode(byteArray); + } + + public boolean isModified() + { + return _modified; + } + + public boolean isDeleted() + { + return _deleted; + } + + public void delete() + { + _deleted = true; + } + + public void saved() + { + _modified = false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..5e4678a63b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java @@ -0,0 +1,491 @@ +/* + * 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.security.auth.database; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: username:password username1:password1 ... usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in plain text. + */ +public class PlainPasswordFilePrincipalDatabase implements PrincipalDatabase +{ + public static final String DEFAULT_ENCODING = "utf-8"; + + private static final Logger _logger = Logger.getLogger(PlainPasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + private Map<String, PlainUser> _users = new HashMap<String, PlainUser>(); + private ReentrantLock _userUpdate = new ReentrantLock(); + + public PlainPasswordFilePrincipalDatabase() + { + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for Plain Password file. + */ + + // Accept AMQPlain incomming and compare it to the file. + AmqPlainInitialiser amqplain = new AmqPlainInitialiser(); + amqplain.initialise(this); + + // Accept Plain incomming and compare it to the file. + PlainInitialiser plain = new PlainInitialiser(); + plain.initialise(this); + + // Accept MD5 incomming and Hash file value for comparison + CRAMMD5Initialiser cram = new CRAMMD5Initialiser(); + cram.initialise(this); + + _saslServers.put(amqplain.getMechanismName(), amqplain); + _saslServers.put(plain.getMechanismName(), plain); + _saslServers.put(cram.getMechanismName(), cram); + } + + public void setPasswordFile(String passwordFile) throws IOException + { + File f = new File(passwordFile); + _logger.info("PlainPasswordFile using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws AccountNotFoundException If the Principal cannot be found in this Database + */ + public void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + char[] pwd = lookupPassword(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The plaintext password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + return compareCharArray(pwd, password); + + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + PlainUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + try + { + _userUpdate.lock(); + char[] orig = user.getPassword(); + user.setPassword(password); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.error("Unable to save password file, password change for user '" + principal + "' discarded"); + //revert the password change + user.setPassword(orig); + return false; + } + return true; + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + catch (Exception e) + { + return false; + } + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_users.get(principal.getName()) != null) + { + return false; + } + + PlainUser user = new PlainUser(principal.getName(), password); + + try + { + _userUpdate.lock(); + _users.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _users.remove(user.getName()); + _logger.warn("Unable to create user '" + user.getName()); + return false; + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + PlainUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.error("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _users.remove(user.getName()); + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + + return true; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(_users.values()); + } + + public Principal getUser(String username) + { + if (_users.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + + /** + * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it + * creates strings of passwords. It should be modified to create only char arrays which get nulled out. + * + * @param name The principal name to lookup + * + * @return a char[] for use in SASL. + */ + private char[] lookupPassword(String name) + { + PlainUser user = _users.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + _users.clear(); + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + continue; + } + + PlainUser user = new PlainUser(result); + _logger.info("Created user:" + user); + _users.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + private void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + File tmp = File.createTempFile(_passwordFile.getName(), ".tmp"); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + PlainUser user = _users.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + byte[] password = user.getPasswordBytes(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(password); + writer.println(); + + user.saved(); + } + } + } + + for (PlainUser user : _users.values()) + { + if (user.isModified()) + { + byte[] password; + password = user.getPasswordBytes(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(password); + writer.println(); + user.saved(); + } + } + } + finally + { + if (reader != null) + { + reader.close(); + } + + if (writer != null) + { + writer.close(); + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + _passwordFile.renameTo(old); + tmp.renameTo(_passwordFile); + tmp.delete(); + } + } + finally + { + if (_userUpdate.isHeldByCurrentThread()) + { + _userUpdate.unlock(); + } + } + } + + public void reload() throws IOException + { + loadPasswordFile(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java new file mode 100644 index 0000000000..46a78a55aa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java @@ -0,0 +1,106 @@ +/* +* + * 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.security.auth.database; + +import org.apache.log4j.Logger; + +import java.security.Principal; + +public class PlainUser implements Principal +{ + private String _name; + private char[] _password; + private boolean _modified = false; + private boolean _deleted = false; + + PlainUser(String[] data) + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + _password = data[1].toCharArray(); + + } + + public PlainUser(String name, char[] password) + { + _name = name; + _password = password; + _modified = true; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + char[] getPassword() + { + return _password; + } + + byte[] getPasswordBytes() + { + byte[] byteArray = new byte[_password.length]; + int index = 0; + for (char c : _password) + { + byteArray[index++] = (byte) c; + } + return byteArray; + } + + void setPassword(char[] password) + { + _password = password; + _modified = true; + } + + public boolean isModified() + { + return _modified; + } + + public boolean isDeleted() + { + return _deleted; + } + + public void delete() + { + _deleted = true; + } + + public void saved() + { + _modified = false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java new file mode 100644 index 0000000000..ef37e043a6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java @@ -0,0 +1,105 @@ +/* + * + * 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.security.auth.database; + +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Map; +import java.util.List; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +/** Represents a "user database" which is really a way of storing principals (i.e. usernames) and passwords. */ +public interface PrincipalDatabase +{ + /** + * Set the password for a given principal in the specified callback. This is used for certain SASL providers. The + * user database implementation should look up the password in any way it chooses and set it in the callback by + * calling its setPassword method. + * + * @param principal the principal + * @param callback the password callback that wants to receive the password + * + * @throws AccountNotFoundException if the account for specified principal could not be found + * @throws IOException if there was an error looking up the principal + */ + void setPassword(Principal principal, PasswordCallback callback) + throws IOException, AccountNotFoundException; + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * @param principal The principal to authenticate + * @param password The password to check + * @return true if password is correct + * @throws AccountNotFoundException if the principal cannot be found + */ + boolean verifyPassword(String principal, char[] password) + throws AccountNotFoundException; + + /** + * Update(Change) the password for the given principal + * @param principal Who's password is to be changed + * @param password The new password to use + * @return True if change was successful + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean updatePassword(Principal principal, char[] password) + throws AccountNotFoundException; + + /** + * Create a new principal in the database + * @param principal The principal to create + * @param password The password to set for the principal + * @return True on a successful creation + */ + boolean createPrincipal(Principal principal, char[] password); + + /** + * Delete a principal + * @param principal The principal to delete + * @return True on a successful creation + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean deletePrincipal(Principal principal) + throws AccountNotFoundException; + + /** + * Get the principal from the database with the given username + * @param username of the principal to lookup + * @return The Principal object for the given username or null if not found. + */ + Principal getUser(String username); + + /** + * Reload the database to its ensure contents are up to date + * @throws IOException If there was an error reloading the database + */ + void reload() throws IOException; + + public Map<String, AuthenticationProviderInitialiser> getMechanisms(); + + + List<Principal> getUsers(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java new file mode 100644 index 0000000000..f9882f8810 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.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.security.auth.database; + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import java.util.Map; + +public interface PrincipalDatabaseManager +{ + public Map<String, PrincipalDatabase> getDatabases(); + + public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java new file mode 100644 index 0000000000..ff8851306f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java @@ -0,0 +1,169 @@ +/* + * 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.security.auth.database; + +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.util.Properties; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.security.Principal; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +public class PropertiesPrincipalDatabase implements PrincipalDatabase +{ + private Properties _users; + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + public PropertiesPrincipalDatabase(Properties users) + { + _users = users; + + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for Properties Principal Database. + */ + + // Accept MD5 incomming and use plain comparison with the file + PlainInitialiser cram = new PlainInitialiser(); + cram.initialise(this); + // Accept Plain incomming and hash it for comparison to the file. + CRAMMD5Initialiser plain = new CRAMMD5Initialiser(); + plain.initialise(this, CRAMMD5Initialiser.HashDirection.INCOMMING); + + _saslServers.put(plain.getMechanismName(), cram); + _saslServers.put(cram.getMechanismName(), plain); + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, AccountNotFoundException + { + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + + + + final String pwd = _users.getProperty(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd.toCharArray()); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + //fixme this is not correct as toCharArray is not safe based on the type of string. + char[] pwd = _users.getProperty(principal).toCharArray(); + + return compareCharArray(pwd, password); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + return false; // updates denied + } + + public boolean createPrincipal(Principal principal, char[] password) + { + return false; // updates denied + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + return false; // updates denied + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + private char[] convertPassword(String password) throws UnsupportedEncodingException + { + byte[] passwdBytes = password.getBytes("utf-8"); + + char[] passwd = new char[passwdBytes.length]; + + int index = 0; + + for (byte b : passwdBytes) + { + passwd[index++] = (char) b; + } + + return passwd; + } + + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(); //todo + } + + public Principal getUser(String username) + { + if (_users.getProperty(username) != null) + { + return new UsernamePrincipal(username); + } + else + { + return null; + } + } + + public void reload() throws IOException + { + //No file to update from, so do nothing. + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java new file mode 100644 index 0000000000..4efe381a8b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java @@ -0,0 +1,50 @@ +/* + * 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.security.auth.database; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.ServerConfiguration; + +import java.util.Map; +import java.util.Properties; +import java.util.HashMap; + +public class PropertiesPrincipalDatabaseManager implements PrincipalDatabaseManager +{ + + Map<String, PrincipalDatabase> _databases = new HashMap<String, PrincipalDatabase>(); + + public PropertiesPrincipalDatabaseManager(String name, Properties users) + { + _databases.put(name, new PropertiesPrincipalDatabase(users)); + } + + public Map<String, PrincipalDatabase> getDatabases() + { + return _databases; + } + + @Override + public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException + { + //todo + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java new file mode 100644 index 0000000000..d1803124a7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java @@ -0,0 +1,38 @@ +/* + * + * 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.security.auth.manager; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public interface AuthenticationManager +{ + String getMechanisms(); + + SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException; + + AuthenticationResult authenticate(SaslServer server, byte[] response); + + void close(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java new file mode 100644 index 0000000000..98c060599a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java @@ -0,0 +1,237 @@ +/* + * 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.security.auth.manager; + +import org.apache.log4j.Logger; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.JCAProvider; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; +import java.security.Security; + +public class PrincipalDatabaseAuthenticationManager implements AuthenticationManager +{ + private static final Logger _logger = Logger.getLogger(PrincipalDatabaseAuthenticationManager.class); + + /** The list of mechanisms, in the order in which they are configured (i.e. preferred order) */ + private String _mechanisms; + + /** Maps from the mechanism to the callback handler to use for handling those requests */ + private Map<String, CallbackHandler> _callbackHandlerMap = new HashMap<String, CallbackHandler>(); + + /** + * Maps from the mechanism to the properties used to initialise the server. See the method Sasl.createSaslServer for + * details of the use of these properties. This map is populated during initialisation of each provider. + */ + private Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>(); + + private AuthenticationManager _default = null; + /** The name for the required SASL Server mechanisms */ + public static final String PROVIDER_NAME= "AMQSASLProvider-Server"; + + public PrincipalDatabaseAuthenticationManager(String name, VirtualHostConfiguration hostConfig) throws Exception + { + _logger.info("Initialising " + (name == null ? "Default" : "'" + name + "'") + + " PrincipleDatabase authentication manager."); + + // Fixme This should be done per Vhost but allowing global hack isn't right but ... + // required as authentication is done before Vhost selection + + Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>(); + + + if (name == null || hostConfig == null) + { + initialiseAuthenticationMechanisms(providerMap, ApplicationRegistry.getInstance().getDatabaseManager().getDatabases()); + } + else + { + String databaseName = hostConfig.getAuthenticationDatabase(); + + if (databaseName == null) + { + + _default = ApplicationRegistry.getInstance().getAuthenticationManager(); + return; + } + else + { + PrincipalDatabase database = ApplicationRegistry.getInstance().getDatabaseManager().getDatabases().get(databaseName); + + if (database == null) + { + throw new ConfigurationException("Requested database:" + databaseName + " was not found"); + } + + initialiseAuthenticationMechanisms(providerMap, database); + } + } + + if (providerMap.size() > 0) + { + // Ensure we are used before the defaults + if (Security.insertProviderAt(new JCAProvider(PROVIDER_NAME, providerMap), 1) == -1) + { + _logger.error("Unable to load custom SASL providers. Qpid custom SASL authenticators unavailable."); + } + else + { + _logger.info("Additional SASL providers successfully registered."); + } + + } + else + { + _logger.warn("No additional SASL providers registered."); + } + + } + + + private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, Map<String, PrincipalDatabase> databases) throws Exception + { + if (databases.size() > 1) + { + _logger.warn("More than one principle database provided currently authentication mechanism will override each other."); + } + + for (Map.Entry<String, PrincipalDatabase> entry : databases.entrySet()) + { + // fixme As the database now provide the mechanisms they support, they will ... + // overwrite each other in the map. There should only be one database per vhost. + // But currently we must have authentication before vhost definition. + initialiseAuthenticationMechanisms(providerMap, entry.getValue()); + } + } + + private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, PrincipalDatabase database) throws Exception + { + if (database == null || database.getMechanisms().size() == 0) + { + _logger.warn("No Database or no mechanisms to initialise authentication"); + return; + } + + for (Map.Entry<String, AuthenticationProviderInitialiser> mechanism : database.getMechanisms().entrySet()) + { + initialiseAuthenticationMechanism(mechanism.getKey(), mechanism.getValue(), providerMap); + } + } + + private void initialiseAuthenticationMechanism(String mechanism, AuthenticationProviderInitialiser initialiser, + Map<String, Class<? extends SaslServerFactory>> providerMap) + throws Exception + { + if (_mechanisms == null) + { + _mechanisms = mechanism; + } + else + { + // simple append should be fine since the number of mechanisms is small and this is a one time initialisation + _mechanisms = _mechanisms + " " + mechanism; + } + _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler()); + _serverCreationProperties.put(mechanism, initialiser.getProperties()); + Class<? extends SaslServerFactory> factory = initialiser.getServerFactoryClassForJCARegistration(); + if (factory != null) + { + providerMap.put(mechanism, factory); + } + _logger.info("Initialised " + mechanism + " SASL provider successfully"); + } + + public String getMechanisms() + { + if (_default != null) + { + // Use the default AuthenticationManager if present + return _default.getMechanisms(); + } + else + { + return _mechanisms; + } + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + if (_default != null) + { + // Use the default AuthenticationManager if present + return _default.createSaslServer(mechanism, localFQDN); + } + else + { + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism), + _callbackHandlerMap.get(mechanism)); + } + + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + // Use the default AuthenticationManager if present + if (_default != null) + { + return _default.authenticate(server, response); + } + + + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + public void close() + { + Security.removeProvider(PROVIDER_NAME); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java new file mode 100644 index 0000000000..77040e896c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java @@ -0,0 +1,119 @@ +/* + * + * 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.security.auth.rmi; + +import java.util.Collections; + +import javax.management.remote.JMXAuthenticator; +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public class RMIPasswordAuthenticator implements JMXAuthenticator +{ + static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details"; + static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]"; + static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password"; + static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null"; + static final String INVALID_CREDENTIALS = "Invalid user details supplied"; + static final String CREDENTIALS_REQUIRED = "User details are required. " + + "Please ensure you are using an up to date management console to connect."; + + private PrincipalDatabase _db = null; + + public RMIPasswordAuthenticator() + { + } + + public void setPrincipalDatabase(PrincipalDatabase pd) + { + this._db = pd; + } + + public Subject authenticate(Object credentials) throws SecurityException + { + // Verify that credential's are of type String[]. + if (!(credentials instanceof String[])) + { + if (credentials == null) + { + throw new SecurityException(CREDENTIALS_REQUIRED); + } + else + { + throw new SecurityException(SHOULD_BE_STRING_ARRAY); + } + } + + // Verify that required number of credential's. + final String[] userCredentials = (String[]) credentials; + if (userCredentials.length != 2) + { + throw new SecurityException(SHOULD_HAVE_2_ELEMENTS); + } + + String username = (String) userCredentials[0]; + String password = (String) userCredentials[1]; + + // Verify that all required credential's are actually present. + if (username == null || password == null) + { + throw new SecurityException(SHOULD_BE_NON_NULL); + } + + // Verify that a PD has been set. + if (_db == null) + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + + boolean authenticated = false; + + // Perform authentication + try + { + if (_db.verifyPassword(username, password.toCharArray())) + { + authenticated = true; + } + } + catch (AccountNotFoundException e) + { + throw new SecurityException(INVALID_CREDENTIALS); + } + + if (authenticated) + { + //credential's check out, return the appropriate JAAS Subject + return new Subject(true, + Collections.singleton(new JMXPrincipal(username)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + else + { + throw new SecurityException(INVALID_CREDENTIALS); + } + } + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java new file mode 100644 index 0000000000..89e545d6f5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.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.security.auth.sasl; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public interface AuthenticationProviderInitialiser +{ + /** + * @return the mechanism's name. This will be used in the list of mechanism's advertised to the + * client. + */ + String getMechanismName(); + + /** + * Initialise the authentication provider. + * @param baseConfigPath the path in the config file that points to any config options for this provider. Each + * provider can have its own set of configuration options + * @param configuration the Apache Commons Configuration instance used to configure this provider + * @param principalDatabases the set of principal databases that are available + * @throws Exception needs refined Exception is too broad. + */ + void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception; + + /** + * Initialise the authentication provider. + * @param db The principal database to initialise with + */ + void initialise(PrincipalDatabase db); + + + /** + * @return the callback handler that should be used to process authentication requests for this mechanism. This will + * be called after initialise and will be stored by the authentication manager. The callback handler <b>must</b> be + * fully threadsafe. + */ + CallbackHandler getCallbackHandler(); + + /** + * Get the properties that must be passed in to the Sasl.createSaslServer method. + * @return the properties, which may be null + */ + Map<String, ?> getProperties(); + + /** + * Get the class that is the server factory. This is used for the JCA registration. + * @return null if no JCA registration is required, otherwise return the class + * that will be used in JCA registration + */ + Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java new file mode 100644 index 0000000000..d6a09d8217 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.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.security.auth.sasl; + +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +import javax.security.sasl.SaslServerFactory; + +public final class JCAProvider extends Provider +{ + public JCAProvider(String name, Map<String, Class<? extends SaslServerFactory>> providerMap) + { + super(name, 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + } + + private void register(Map<String, Class<? extends SaslServerFactory>> providerMap) + { + for (Map.Entry<String, Class<? extends SaslServerFactory>> me : + providerMap.entrySet()) + { + put("SaslServerFactory." + me.getKey(), me.getValue().getName()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java new file mode 100644 index 0000000000..dd0bd096c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java @@ -0,0 +1,123 @@ +/* + * + * 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.security.auth.sasl; + +import java.io.IOException; +import java.security.Principal; +import java.util.Map; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.AuthorizeCallback; + +import org.apache.commons.configuration.Configuration; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser +{ + protected static final Logger _logger = Logger.getLogger(UsernamePasswordInitialiser.class); + + private ServerCallbackHandler _callbackHandler; + + private class ServerCallbackHandler implements CallbackHandler + { + private final PrincipalDatabase _principalDatabase; + + protected ServerCallbackHandler(PrincipalDatabase database) + { + _principalDatabase = database; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + Principal username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = new UsernamePrincipal(((NameCallback) callback).getDefaultName()); + } + else if (callback instanceof PasswordCallback) + { + try + { + _principalDatabase.setPassword(username, (PasswordCallback) callback); + } + catch (AccountNotFoundException e) + { + // very annoyingly the callback handler does not throw anything more appropriate than + // IOException + IOException ioe = new IOException("Error looking up user " + e); + ioe.initCause(e); + throw ioe; + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + public void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception + { + String principalDatabaseName = configuration.getString(baseConfigPath + ".principal-database"); + PrincipalDatabase db = principalDatabases.get(principalDatabaseName); + + initialise(db); + } + + public void initialise(PrincipalDatabase db) + { + if (db == null) + { + throw new NullPointerException("Cannot initialise with a null Principal database."); + } + + _callbackHandler = new ServerCallbackHandler(db); + } + + public CallbackHandler getCallbackHandler() + { + return _callbackHandler; + } + + public Map<String, ?> getProperties() + { + // there are no properties required for the CRAM-MD5 implementation + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java new file mode 100644 index 0000000000..d7c8383690 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.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.security.auth.sasl; + +import java.security.Principal; + +/** A principal that is just a wrapper for a simple username. */ +public class UsernamePrincipal implements Principal +{ + private String _name; + + public UsernamePrincipal(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java new file mode 100644 index 0000000000..7acc6322d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * 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.security.auth.sasl.amqplain; + +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AmqPlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return AmqPlainSaslServerFactory.class; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java new file mode 100644 index 0000000000..9f56b8521a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java @@ -0,0 +1,132 @@ +/* + * + * 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.security.auth.sasl.amqplain; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; + +public class AmqPlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public AmqPlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + final FieldTable ft = FieldTableFactory.newFieldTable(ByteBuffer.wrap(response), response.length); + String username = (String) ft.getString("LOGIN"); + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", username); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + String pwd = (String) ft.getString("PASSWORD"); + AuthorizeCallback authzCb = new AuthorizeCallback(username, username); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + String storedPwd = new String(passwordCb.getPassword()); + if (storedPwd.equals(pwd)) + { + _complete = true; + } + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (AMQFrameDecodingException e) + { + throw new SaslException("Unable to decode response: " + e, e); + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java new file mode 100644 index 0000000000..67d20136bf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java @@ -0,0 +1,60 @@ +/* + * + * 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.security.auth.sasl.amqplain; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class AmqPlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AmqPlainSaslServer.MECHANISM.equals(mechanism)) + { + return new AmqPlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslServer.MECHANISM}; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java new file mode 100644 index 0000000000..97f9a4e91a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java @@ -0,0 +1,50 @@ +/* + * + * 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.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class CRAMMD5HashedInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return CRAMMD5HashedSaslServer.MECHANISM; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return CRAMMD5HashedServerFactory.class; + } + + public void initialise(PrincipalDatabase passwordFile) + { + super.initialise(passwordFile); + } + + public Map<String, ?> getProperties() + { + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java new file mode 100644 index 0000000000..f6cab084ea --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java @@ -0,0 +1,105 @@ +/* + * 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.security.auth.sasl.crammd5; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslServerFactory; +import javax.security.auth.callback.CallbackHandler; +import java.util.Enumeration; +import java.util.Map; + +public class CRAMMD5HashedSaslServer implements SaslServer +{ + public static final String MECHANISM = "CRAM-MD5-HASHED"; + + private SaslServer _realServer; + + public CRAMMD5HashedSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + Enumeration factories = Sasl.getSaslServerFactories(); + + while (factories.hasMoreElements()) + { + SaslServerFactory factory = (SaslServerFactory) factories.nextElement(); + + if (factory instanceof CRAMMD5HashedServerFactory) + { + continue; + } + + String[] mechs = factory.getMechanismNames(props); + + for (String mech : mechs) + { + if (mech.equals("CRAM-MD5")) + { + _realServer = factory.createSaslServer("CRAM-MD5", protocol, serverName, props, cbh); + return; + } + } + } + + throw new RuntimeException("No default SaslServer found for mechanism:" + "CRAM-MD5"); + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + return _realServer.evaluateResponse(response); + } + + public boolean isComplete() + { + return _realServer.isComplete(); + } + + public String getAuthorizationID() + { + return _realServer.getAuthorizationID(); + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return _realServer.unwrap(incoming, offset, len); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return _realServer.wrap(outgoing, offset, len); + } + + public Object getNegotiatedProperty(String propName) + { + return _realServer.getNegotiatedProperty(propName); + } + + public void dispose() throws SaslException + { + _realServer.dispose(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java new file mode 100644 index 0000000000..5298b5cc63 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.crammd5; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5HashedServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + if (mechanism.equals(CRAMMD5HashedSaslServer.MECHANISM)) + { + return new CRAMMD5HashedSaslServer(mechanism, protocol, serverName, props, cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + } + + return new String[]{CRAMMD5HashedSaslServer.MECHANISM}; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java new file mode 100644 index 0000000000..264832888d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java @@ -0,0 +1,71 @@ +/* + * + * 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.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5Initialiser extends UsernamePasswordInitialiser +{ + private HashDirection _hashDirection; + + public enum HashDirection + { + INCOMMING, PASSWORD_FILE + } + + + public String getMechanismName() + { + return "CRAM-MD5"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + // since the CRAM-MD5 provider is registered as part of the JDK, we do not + // return the factory class here since we do not need to register it ourselves. + if (_hashDirection == HashDirection.PASSWORD_FILE) + { + return null; + } + else + { + //fixme we need a server that will correctly has the incomming plain text for comparison to file. + _logger.warn("we need a server that will correctly convert the incomming plain text for comparison to file."); + return null; + } + } + + public void initialise(PrincipalDatabase passwordFile) + { + initialise(passwordFile, HashDirection.PASSWORD_FILE); + } + + public void initialise(PrincipalDatabase passwordFile, HashDirection direction) + { + super.initialise(passwordFile); + + _hashDirection = direction; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java new file mode 100644 index 0000000000..1d16cd8755 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * 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.security.auth.sasl.plain; + +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "PLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return PlainSaslServerFactory.class; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java new file mode 100644 index 0000000000..45fb9a4e42 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java @@ -0,0 +1,151 @@ +/* + * + * 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.security.auth.sasl.plain; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public class PlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "PLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public PlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + int authzidNullPosition = findNullPosition(response, 0); + if (authzidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authzid null terminator not found"); + } + int authcidNullPosition = findNullPosition(response, authzidNullPosition + 1); + if (authcidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authcid null terminator not found"); + } + + // we do not currently support authcid in any meaningful way + // String authcid = new String(response, 0, authzidNullPosition, "utf8"); + String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - 1, "utf8"); + + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", authzid); + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + int passwordLen = response.length - authcidNullPosition - 1; + String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8"); + AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + String storedPwd = new String(passwordCb.getPassword()); + if (storedPwd.equals(pwd)) + { + _complete = true; + } + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + private int findNullPosition(byte[] response, int startPosition) + { + int position = startPosition; + while (position < response.length) + { + if (response[position] == (byte) 0) + { + return position; + } + position++; + } + return -1; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java new file mode 100644 index 0000000000..f0dd9eeb6d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java @@ -0,0 +1,60 @@ +/* + * + * 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.security.auth.sasl.plain; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class PlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{PlainSaslServer.MECHANISM}; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java new file mode 100644 index 0000000000..f427cc7206 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java @@ -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. + * + */ +package org.apache.qpid.server.state; + +/** + * States used in the AMQ protocol. Used by the finite state machine to determine + * valid responses. + */ +public enum AMQState +{ + CONNECTION_NOT_STARTED, + CONNECTION_NOT_AUTH, + CONNECTION_NOT_TUNED, + CONNECTION_NOT_OPENED, + CONNECTION_OPEN, + CONNECTION_CLOSING, + CONNECTION_CLOSED +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java new file mode 100644 index 0000000000..c5b3099f58 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java @@ -0,0 +1,263 @@ +/* + * + * 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.state; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.handler.BasicAckMethodHandler; +import org.apache.qpid.server.handler.BasicCancelMethodHandler; +import org.apache.qpid.server.handler.BasicConsumeMethodHandler; +import org.apache.qpid.server.handler.BasicGetMethodHandler; +import org.apache.qpid.server.handler.BasicPublishMethodHandler; +import org.apache.qpid.server.handler.BasicQosHandler; +import org.apache.qpid.server.handler.BasicRecoverMethodHandler; +import org.apache.qpid.server.handler.BasicRejectMethodHandler; +import org.apache.qpid.server.handler.ChannelCloseHandler; +import org.apache.qpid.server.handler.ChannelCloseOkHandler; +import org.apache.qpid.server.handler.ChannelFlowHandler; +import org.apache.qpid.server.handler.ChannelOpenHandler; +import org.apache.qpid.server.handler.ConnectionCloseMethodHandler; +import org.apache.qpid.server.handler.ConnectionCloseOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionOpenMethodHandler; +import org.apache.qpid.server.handler.ConnectionSecureOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionStartOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionTuneOkMethodHandler; +import org.apache.qpid.server.handler.ExchangeBoundHandler; +import org.apache.qpid.server.handler.ExchangeDeclareHandler; +import org.apache.qpid.server.handler.ExchangeDeleteHandler; +import org.apache.qpid.server.handler.QueueBindHandler; +import org.apache.qpid.server.handler.QueueDeclareHandler; +import org.apache.qpid.server.handler.QueueDeleteHandler; +import org.apache.qpid.server.handler.QueuePurgeHandler; +import org.apache.qpid.server.handler.TxCommitHandler; +import org.apache.qpid.server.handler.TxRollbackHandler; +import org.apache.qpid.server.handler.TxSelectHandler; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +/** + * The state manager is responsible for managing the state of the protocol session. <p/> For each AMQProtocolHandler + * there is a separate state manager. + */ +public class AMQStateManager implements AMQMethodListener +{ + private static final Logger _logger = Logger.getLogger(AMQStateManager.class); + + private final VirtualHostRegistry _virtualHostRegistry; + private final AMQProtocolSession _protocolSession; + /** The current state */ + private AMQState _currentState; + + /** + * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. The class must be a subclass of + * AMQFrame. + */ +/* private final EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>> _state2HandlersMap = + new EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>>( + AMQState.class); + */ + + + private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>(); + + public AMQStateManager(VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession) + { + + _virtualHostRegistry = virtualHostRegistry; + _protocolSession = protocolSession; + _currentState = AMQState.CONNECTION_NOT_STARTED; + + } + + /* + protected void registerListeners() + { + Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> frame2handlerMap; + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionOpenBody.class, ConnectionOpenMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); + + // + // ConnectionOpen handlers + // + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + ChannelOpenHandler.getInstance(); + ChannelCloseHandler.getInstance(); + ChannelCloseOkHandler.getInstance(); + ConnectionCloseMethodHandler.getInstance(); + ConnectionCloseOkMethodHandler.getInstance(); + ConnectionTuneOkMethodHandler.getInstance(); + ConnectionSecureOkMethodHandler.getInstance(); + ConnectionStartOkMethodHandler.getInstance(); + ExchangeDeclareHandler.getInstance(); + ExchangeDeleteHandler.getInstance(); + ExchangeBoundHandler.getInstance(); + BasicAckMethodHandler.getInstance(); + BasicRecoverMethodHandler.getInstance(); + BasicConsumeMethodHandler.getInstance(); + BasicGetMethodHandler.getInstance(); + BasicCancelMethodHandler.getInstance(); + BasicPublishMethodHandler.getInstance(); + BasicQosHandler.getInstance(); + QueueBindHandler.getInstance(); + QueueDeclareHandler.getInstance(); + QueueDeleteHandler.getInstance(); + QueuePurgeHandler.getInstance(); + ChannelFlowHandler.getInstance(); + TxSelectHandler.getInstance(); + TxCommitHandler.getInstance(); + TxRollbackHandler.getInstance(); + BasicRejectMethodHandler.getInstance(); + + _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + + _state2HandlersMap.put(AMQState.CONNECTION_CLOSING, frame2handlerMap); + + } */ + + public AMQState getCurrentState() + { + return _currentState; + } + + public void changeState(AMQState newState) throws AMQException + { + _logger.debug("State changing to " + newState + " from old state " + _currentState); + final AMQState oldState = _currentState; + _currentState = newState; + + for (StateListener l : _stateListeners) + { + l.stateChanged(oldState, newState); + } + } + + public void error(Exception e) + { + _logger.error("State manager received error notification[Current State:" + _currentState + "]: " + e, e); + for (StateListener l : _stateListeners) + { + l.error(e); + } + } + + public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException + { + MethodDispatcher dispatcher = _protocolSession.getMethodDispatcher(); + + final int channelId = evt.getChannelId(); + B body = evt.getMethod(); + + if(channelId != 0 && _protocolSession.getChannel(channelId)== null) + { + + if(! ((body instanceof ChannelOpenBody) + || (body instanceof ChannelCloseOkBody) + || (body instanceof ChannelCloseBody))) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "channel is closed"); + } + + } + + return body.execute(dispatcher, channelId); + + } + + private <B extends AMQMethodBody> void checkChannel(AMQMethodEvent<B> evt, AMQProtocolSession protocolSession) + throws AMQException + { + if ((evt.getChannelId() != 0) && !(evt.getMethod() instanceof ChannelOpenBody) + && (protocolSession.getChannel(evt.getChannelId()) == null) + && !protocolSession.channelAwaitingClosure(evt.getChannelId())) + { + throw evt.getMethod().getChannelNotFoundException(evt.getChannelId()); + } + } + +/* + protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState currentState, + B frame) + // throws IllegalStateTransitionException + { + final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> classToHandlerMap = + _state2HandlersMap.get(currentState); + + final StateAwareMethodListener<B> handler = + (classToHandlerMap == null) ? null : (StateAwareMethodListener<B>) classToHandlerMap.get(frame.getClass()); + + if (handler == null) + { + _logger.debug("No state transition handler defined for receiving frame " + frame); + + return null; + } + else + { + return handler; + } + } +*/ + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + public AMQProtocolSession getProtocolSession() + { + return _protocolSession; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..cec67a8a6d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.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.state; + +import org.apache.qpid.AMQException; + +/** + * @todo Not an AMQP exception as no status code. + * + * @todo Not used! Delete. + */ +public class IllegalStateTransitionException extends AMQException +{ + private AMQState _originalState; + + private Class _frame; + + public IllegalStateTransitionException(AMQState originalState, Class frame) + { + super("No valid state transition defined for receiving frame " + frame + " from state " + originalState); + _originalState = originalState; + _frame = frame; + } + + public AMQState getOriginalState() + { + return _originalState; + } + + public Class getFrameClass() + { + return _frame; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..3c11bb8a9c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.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.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQMethodEvent; + +/** + * A frame listener that is informed of the protocol state when invoked and has + * the opportunity to update state. + * + */ +public interface StateAwareMethodListener<B extends AMQMethodBody> +{ + void methodReceived(AMQStateManager stateManager, B evt, int channelId) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java new file mode 100644 index 0000000000..00fc09867b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java new file mode 100644 index 0000000000..e7f9c777c9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java @@ -0,0 +1,1464 @@ +/* +* +* 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; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.queue.QueueRegistry; + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; + +import java.io.File; +import java.io.ByteArrayInputStream; +import java.sql.DriverManager; +import java.sql.Driver; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Blob; +import java.sql.Types; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; + + +public class DerbyMessageStore implements MessageStore +{ + + private static final Logger _logger = Logger.getLogger(DerbyMessageStore.class); + + private static final String ENVIRONMENT_PATH_PROPERTY = "environment-path"; + + + private static final String SQL_DRIVER_NAME = "org.apache.derby.jdbc.EmbeddedDriver"; + + private static final String DB_VERSION_TABLE_NAME = "QPID_DB_VERSION"; + + private static final String EXCHANGE_TABLE_NAME = "QPID_EXCHANGE"; + private static final String QUEUE_TABLE_NAME = "QPID_QUEUE"; + private static final String BINDINGS_TABLE_NAME = "QPID_BINDINGS"; + private static final String QUEUE_ENTRY_TABLE_NAME = "QPID_QUEUE_ENTRY"; + private static final String MESSAGE_META_DATA_TABLE_NAME = "QPID_MESSAGE_META_DATA"; + private static final String MESSAGE_CONTENT_TABLE_NAME = "QPID_MESSAGE_CONTENT"; + + private static final int DB_VERSION = 1; + + + + private VirtualHost _virtualHost; + private static Class<Driver> DRIVER_CLASS; + + private final AtomicLong _messageId = new AtomicLong(1); + private AtomicBoolean _closed = new AtomicBoolean(false); + + private String _connectionURL; + + + + private static final String CREATE_DB_VERSION_TABLE = "CREATE TABLE "+DB_VERSION_TABLE_NAME+" ( version int not null )"; + private static final String INSERT_INTO_DB_VERSION = "INSERT INTO "+DB_VERSION_TABLE_NAME+" ( version ) VALUES ( ? )"; + private static final String CREATE_EXCHANGE_TABLE = "CREATE TABLE "+EXCHANGE_TABLE_NAME+" ( name varchar(255) not null, type varchar(255) not null, autodelete SMALLINT not null, PRIMARY KEY ( name ) )"; + private static final String CREATE_QUEUE_TABLE = "CREATE TABLE "+QUEUE_TABLE_NAME+" ( name varchar(255) not null, owner varchar(255), PRIMARY KEY ( name ) )"; + private static final String CREATE_BINDINGS_TABLE = "CREATE TABLE "+BINDINGS_TABLE_NAME+" ( exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255) not null, arguments blob , PRIMARY KEY ( exchange_name, queue_name, binding_key ) )"; + private static final String CREATE_QUEUE_ENTRY_TABLE = "CREATE TABLE "+QUEUE_ENTRY_TABLE_NAME+" ( queue_name varchar(255) not null, message_id bigint not null, PRIMARY KEY (queue_name, message_id) )"; + private static final String CREATE_MESSAGE_META_DATA_TABLE = "CREATE TABLE "+MESSAGE_META_DATA_TABLE_NAME+" ( message_id bigint not null, exchange_name varchar(255) not null, routing_key varchar(255), flag_mandatory smallint not null, flag_immediate smallint not null, content_header blob, chunk_count int not null, PRIMARY KEY ( message_id ) )"; + private static final String CREATE_MESSAGE_CONTENT_TABLE = "CREATE TABLE "+MESSAGE_CONTENT_TABLE_NAME+" ( message_id bigint not null, chunk_id int not null, content_chunk blob , PRIMARY KEY (message_id, chunk_id) )"; + private static final String SELECT_FROM_QUEUE = "SELECT name, owner FROM " + QUEUE_TABLE_NAME; + private static final String SELECT_FROM_EXCHANGE = "SELECT name, type, autodelete FROM " + EXCHANGE_TABLE_NAME; + private static final String SELECT_FROM_BINDINGS = + "SELECT queue_name, binding_key, arguments FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ?"; + private static final String DELETE_FROM_MESSAGE_META_DATA = "DELETE FROM " + MESSAGE_META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String DELETE_FROM_MESSAGE_CONTENT = "DELETE FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ?"; + private static final String INSERT_INTO_EXCHANGE = "INSERT INTO " + EXCHANGE_TABLE_NAME + " ( name, type, autodelete ) VALUES ( ?, ?, ? )"; + private static final String DELETE_FROM_EXCHANGE = "DELETE FROM " + EXCHANGE_TABLE_NAME + " WHERE name = ?"; + private static final String INSERT_INTO_BINDINGS = "INSERT INTO " + BINDINGS_TABLE_NAME + " ( exchange_name, queue_name, binding_key, arguments ) values ( ?, ?, ?, ? )"; + private static final String DELETE_FROM_BINDINGS = "DELETE FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ? AND queue_name = ? AND binding_key = ?"; + private static final String INSERT_INTO_QUEUE = "INSERT INTO " + QUEUE_TABLE_NAME + " (name, owner) VALUES (?, ?)"; + private static final String DELETE_FROM_QUEUE = "DELETE FROM " + QUEUE_TABLE_NAME + " WHERE name = ?"; + private static final String INSERT_INTO_QUEUE_ENTRY = "INSERT INTO " + QUEUE_ENTRY_TABLE_NAME + " (queue_name, message_id) values (?,?)"; + private static final String DELETE_FROM_QUEUE_ENTRY = "DELETE FROM " + QUEUE_ENTRY_TABLE_NAME + " WHERE queue_name = ? AND message_id =?"; + private static final String INSERT_INTO_MESSAGE_CONTENT = "INSERT INTO " + MESSAGE_CONTENT_TABLE_NAME + "( message_id, chunk_id, content_chunk ) values (?, ?, ?)"; + private static final String INSERT_INTO_MESSAGE_META_DATA = "INSERT INTO " + MESSAGE_META_DATA_TABLE_NAME + "( message_id , exchange_name , routing_key , flag_mandatory , flag_immediate , content_header , chunk_count ) values (?, ?, ?, ?, ?, ?, ?)"; + private static final String SELECT_FROM_MESSAGE_META_DATA = + "SELECT exchange_name , routing_key , flag_mandatory , flag_immediate , content_header , chunk_count FROM " + MESSAGE_META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String SELECT_FROM_MESSAGE_CONTENT = + "SELECT content_chunk FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ? and chunk_id = ?"; + private static final String SELECT_FROM_QUEUE_ENTRY = "SELECT queue_name, message_id FROM " + QUEUE_ENTRY_TABLE_NAME; + private static final String TABLE_EXISTANCE_QUERY = "SELECT 1 FROM SYS.SYSTABLES WHERE TABLENAME = ?"; + + + private enum State + { + INITIAL, + CONFIGURING, + RECOVERING, + STARTED, + CLOSING, + CLOSED + } + + private State _state = State.INITIAL; + + + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + { + stateTransition(State.INITIAL, State.CONFIGURING); + + initialiseDriver(); + + _virtualHost = virtualHost; + + _logger.info("Configuring Derby message store for virtual host " + virtualHost.getName()); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + final String databasePath = config.getStoreConfiguration().getString(base + "." + ENVIRONMENT_PATH_PROPERTY, "derbyDB"); + + File environmentPath = new File(databasePath); + 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."); + } + } + + createOrOpenDatabase(databasePath); + + // this recovers durable queues and persistent messages + + recover(); + + stateTransition(State.RECOVERING, State.STARTED); + + } + + private static synchronized void initialiseDriver() throws ClassNotFoundException + { + if(DRIVER_CLASS == null) + { + DRIVER_CLASS = (Class<Driver>) Class.forName(SQL_DRIVER_NAME); + } + } + + private void createOrOpenDatabase(final String environmentPath) throws SQLException + { + _connectionURL = "jdbc:derby:" + environmentPath + "/" + _virtualHost.getName() + ";create=true"; + + Connection conn = newConnection(); + + createVersionTable(conn); + createExchangeTable(conn); + createQueueTable(conn); + createBindingsTable(conn); + createQueueEntryTable(conn); + createMessageMetaDataTable(conn); + createMessageContentTable(conn); + + conn.close(); + } + + + + private void createVersionTable(final Connection conn) throws SQLException + { + if(!tableExists(DB_VERSION_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + + stmt.execute(CREATE_DB_VERSION_TABLE); + stmt.close(); + + PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_DB_VERSION); + pstmt.setInt(1, DB_VERSION); + pstmt.execute(); + pstmt.close(); + } + + } + + + private void createExchangeTable(final Connection conn) throws SQLException + { + if(!tableExists(EXCHANGE_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + + stmt.execute(CREATE_EXCHANGE_TABLE); + stmt.close(); + } + } + + private void createQueueTable(final Connection conn) throws SQLException + { + if(!tableExists(QUEUE_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + stmt.execute(CREATE_QUEUE_TABLE); + stmt.close(); + } + } + + private void createBindingsTable(final Connection conn) throws SQLException + { + if(!tableExists(BINDINGS_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + stmt.execute(CREATE_BINDINGS_TABLE); + + stmt.close(); + } + + } + + private void createQueueEntryTable(final Connection conn) throws SQLException + { + if(!tableExists(QUEUE_ENTRY_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + stmt.execute(CREATE_QUEUE_ENTRY_TABLE); + + stmt.close(); + } + + } + + private void createMessageMetaDataTable(final Connection conn) throws SQLException + { + if(!tableExists(MESSAGE_META_DATA_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + stmt.execute(CREATE_MESSAGE_META_DATA_TABLE); + + stmt.close(); + } + + } + + + private void createMessageContentTable(final Connection conn) throws SQLException + { + if(!tableExists(MESSAGE_CONTENT_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + stmt.execute(CREATE_MESSAGE_CONTENT_TABLE); + + stmt.close(); + } + + } + + + + private boolean tableExists(final String tableName, final Connection conn) throws SQLException + { + PreparedStatement stmt = conn.prepareStatement(TABLE_EXISTANCE_QUERY); + stmt.setString(1, tableName); + ResultSet rs = stmt.executeQuery(); + boolean exists = rs.next(); + rs.close(); + stmt.close(); + return exists; + } + + public void recover() throws AMQException + { + stateTransition(State.CONFIGURING, State.RECOVERING); + + _logger.info("Recovering persistent state..."); + StoreContext context = new StoreContext(); + + try + { + Map<AMQShortString, AMQQueue> queues = loadQueues(); + + recoverExchanges(); + + try + { + + beginTran(context); + + deliverMessages(context, queues); + _logger.info("Persistent state recovered successfully"); + commitTran(context); + + } + finally + { + if(inTran(context)) + { + abortTran(context); + } + } + } + catch (SQLException e) + { + + throw new AMQException("Error recovering persistent state: " + e, e); + } + + } + + private Map<AMQShortString, AMQQueue> loadQueues() throws SQLException, AMQException + { + Connection conn = newConnection(); + + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE); + Map<AMQShortString, AMQQueue> queueMap = new HashMap<AMQShortString, AMQQueue>(); + while(rs.next()) + { + String queueName = rs.getString(1); + String owner = rs.getString(2); + AMQShortString queueNameShortString = new AMQShortString(queueName); + AMQQueue q = AMQQueueFactory.createAMQQueueImpl(queueNameShortString, true, owner == null ? null : new AMQShortString(owner), false, _virtualHost, + null); + _virtualHost.getQueueRegistry().registerQueue(q); + queueMap.put(queueNameShortString,q); + + } + return queueMap; + } + + private void recoverExchanges() throws AMQException, SQLException + { + for (Exchange exchange : loadExchanges()) + { + recoverExchange(exchange); + } + } + + + private List<Exchange> loadExchanges() throws AMQException, SQLException + { + + List<Exchange> exchanges = new ArrayList<Exchange>(); + Connection conn = null; + try + { + conn = newConnection(); + + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(SELECT_FROM_EXCHANGE); + + Exchange exchange; + while(rs.next()) + { + String exchangeName = rs.getString(1); + String type = rs.getString(2); + boolean autoDelete = rs.getShort(3) != 0; + + exchange = _virtualHost.getExchangeFactory().createExchange(new AMQShortString(exchangeName), new AMQShortString(type), true, autoDelete, 0); + _virtualHost.getExchangeRegistry().registerExchange(exchange); + exchanges.add(exchange); + + } + return exchanges; + + } + finally + { + if(conn != null) + { + conn.close(); + } + } + + } + + private void recoverExchange(Exchange exchange) throws AMQException, SQLException + { + _logger.info("Recovering durable exchange " + exchange.getName() + " of type " + exchange.getType() + "..."); + + QueueRegistry queueRegistry = _virtualHost.getQueueRegistry(); + + Connection conn = null; + try + { + conn = newConnection(); + + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_BINDINGS); + stmt.setString(1, exchange.getName().toString()); + + ResultSet rs = stmt.executeQuery(); + + + while(rs.next()) + { + String queueName = rs.getString(1); + String bindingKey = rs.getString(2); + Blob arguments = rs.getBlob(3); + + + AMQQueue queue = queueRegistry.getQueue(new AMQShortString(queueName)); + if (queue == null) + { + _logger.error("Unkown queue: " + queueName + " cannot be bound to exchange: " + + exchange.getName()); + } + else + { + _logger.info("Restoring binding: (Exchange: " + exchange.getName() + ", Queue: " + queueName + + ", Routing Key: " + bindingKey + ", Arguments: " + arguments + + ")"); + + FieldTable argumentsFT = null; + if(arguments != null) + { + byte[] argumentBytes = arguments.getBytes(0, (int) arguments.length()); + ByteBuffer buf = ByteBuffer.wrap(argumentBytes); + argumentsFT = new FieldTable(buf,arguments.length()); + } + + queue.bind(exchange, bindingKey == null ? null : new AMQShortString(bindingKey), argumentsFT); + + } + } + } + finally + { + if(conn != null) + { + conn.close(); + } + } + } + + public void close() throws Exception + { + _closed.getAndSet(true); + } + + public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException + { + + boolean localTx = getOrCreateTransaction(storeContext); + + Connection conn = getConnection(storeContext); + ConnectionWrapper wrapper = (ConnectionWrapper) storeContext.getPayload(); + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Message Id: " + messageId + " Removing"); + } + + // first we need to look up the header to get the chunk count + MessageMetaData mmd = getMessageMetaData(storeContext, messageId); + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_META_DATA); + stmt.setLong(1,messageId); + wrapper.setRequiresCommit(); + int results = stmt.executeUpdate(); + + if (results == 0) + { + if (localTx) + { + abortTran(storeContext); + } + + throw new AMQException("Message metadata not found for message id " + messageId); + } + stmt.close(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Deleted metadata for message " + messageId); + } + + stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + results = stmt.executeUpdate(); + + if(results != mmd.getContentChunkCount()) + { + if (localTx) + { + abortTran(storeContext); + } + throw new AMQException("Unexpected number of content chunks when deleting message. Expected " + mmd.getContentChunkCount() + " but found " + results); + + } + + if (localTx) + { + commitTran(storeContext); + } + } + catch (SQLException e) + { + if ((conn != null) && localTx) + { + abortTran(storeContext); + } + + throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e); + } + + } + + public void createExchange(Exchange exchange) throws AMQException + { + if (_state != State.RECOVERING) + { + try + { + Connection conn = null; + + try + { + conn = newConnection(); + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_EXCHANGE); + stmt.setString(1, exchange.getName().toString()); + stmt.setString(2, exchange.getType().toString()); + stmt.setShort(3, exchange.isAutoDelete() ? (short) 1 : (short) 0); + stmt.execute(); + stmt.close(); + conn.commit(); + + } + finally + { + if(conn != null) + { + conn.close(); + } + } + } + catch (SQLException e) + { + throw new AMQException("Error writing Exchange with name " + exchange.getName() + " to database: " + e, e); + } + } + + } + + public void removeExchange(Exchange exchange) throws AMQException + { + Connection conn = null; + + try + { + conn = newConnection(); + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_EXCHANGE); + stmt.setString(1, exchange.getName().toString()); + int results = stmt.executeUpdate(); + if(results == 0) + { + throw new AMQException("Exchange " + exchange.getName() + " not found"); + } + else + { + conn.commit(); + stmt.close(); + } + } + catch (SQLException e) + { + throw new AMQException("Error writing deleting with name " + exchange.getName() + " from database: " + e, e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + throws AMQException + { + if (_state != State.RECOVERING) + { + Connection conn = null; + + + try + { + conn = newConnection(); + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_BINDINGS); + stmt.setString(1, exchange.getName().toString() ); + stmt.setString(2, queue.getName().toString()); + stmt.setString(3, routingKey == null ? null : routingKey.toString()); + if(args != null) + { + /* This would be the Java 6 way of setting a Blob + Blob blobArgs = conn.createBlob(); + blobArgs.setBytes(0, args.getDataAsBytes()); + stmt.setBlob(4, blobArgs); + */ + byte[] bytes = args.getDataAsBytes(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + stmt.setBinaryStream(4, bis, bytes.length); + } + else + { + stmt.setNull(4, Types.BLOB); + } + + stmt.executeUpdate(); + conn.commit(); + stmt.close(); + } + catch (SQLException e) + { + throw new AMQException("Error writing binding for AMQQueue with name " + queue.getName() + " to exchange " + + exchange.getName() + " to database: " + e, e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + + } + + + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + throws AMQException + { + Connection conn = null; + + + try + { + conn = newConnection(); + // exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255), arguments blob + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_BINDINGS); + stmt.setString(1, exchange.getName().toString() ); + stmt.setString(2, queue.getName().toString()); + stmt.setString(3, routingKey == null ? null : routingKey.toString()); + + + if(stmt.executeUpdate() != 1) + { + throw new AMQException("Queue binding for queue with name " + queue.getName() + " to exchange " + + exchange.getName() + " not found"); + } + conn.commit(); + stmt.close(); + } + catch (SQLException e) + { + throw new AMQException("Error removing binding for AMQQueue with name " + queue.getName() + " to exchange " + + exchange.getName() + " in database: " + e, e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + + + } + + public void createQueue(AMQQueue queue) throws AMQException + { + createQueue(queue, null); + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException + { + _logger.debug("public void createQueue(AMQQueue queue = " + queue + "): called"); + + if (_state != State.RECOVERING) + { + try + { + Connection conn = newConnection(); + + PreparedStatement stmt = + conn.prepareStatement(INSERT_INTO_QUEUE); + + stmt.setString(1, queue.getName().toString()); + stmt.setString(2, queue.getOwner() == null ? null : queue.getOwner().toString()); + + stmt.execute(); + + stmt.close(); + + conn.commit(); + + conn.close(); + } + catch (SQLException e) + { + throw new AMQException("Error writing AMQQueue with name " + queue.getName() + " to database: " + e, e); + } + } + } + + private Connection newConnection() throws SQLException + { + final Connection connection = DriverManager.getConnection(_connectionURL); + return connection; + } + + public void removeQueue(final AMQQueue queue) throws AMQException + { + AMQShortString name = queue.getName(); + _logger.debug("public void removeQueue(AMQShortString name = " + name + "): called"); + Connection conn = null; + + + try + { + conn = newConnection(); + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE); + stmt.setString(1, name.toString()); + int results = stmt.executeUpdate(); + + + if (results == 0) + { + throw new AMQException("Queue " + name + " not found"); + } + + conn.commit(); + stmt.close(); + } + catch (SQLException e) + { + throw new AMQException("Error writing deleting with name " + name + " from database: " + e, e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + + + } + + public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + AMQShortString name = queue.getName(); + + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + try + { + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_QUEUE_ENTRY); + stmt.setString(1,name.toString()); + stmt.setLong(2,messageId); + stmt.executeUpdate(); + connWrapper.requiresCommit(); + + if(localTx) + { + commitTran(context); + } + + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueuing message " + messageId + " on queue " + name + "[Connection" + conn + "]"); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + _logger.error("Failed to enqueue: " + e, e); + throw new AMQException("Error writing enqueued message with id " + messageId + " for queue " + name + + " to database", e); + } + + } + + public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + AMQShortString name = queue.getName(); + + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE_ENTRY); + stmt.setString(1,name.toString()); + stmt.setLong(2,messageId); + int results = stmt.executeUpdate(); + + connWrapper.requiresCommit(); + + if(results != 1) + { + throw new AMQException("Unable to find message with id " + messageId + " on queue " + name); + } + + if(localTx) + { + commitTran(context); + } + + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeuing message " + messageId + " on queue " + name + "[Connection" + conn + "]"); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + _logger.error("Failed to dequeue: " + e, e); + throw new AMQException("Error deleting enqueued message with id " + messageId + " for queue " + name + + " from database", e); + } + + } + + private static final class ConnectionWrapper + { + private final Connection _connection; + private boolean _requiresCommit; + + public ConnectionWrapper(Connection conn) + { + _connection = conn; + } + + public void setRequiresCommit() + { + _requiresCommit = true; + } + + public boolean requiresCommit() + { + return _requiresCommit; + } + + public Connection getConnection() + { + return _connection; + } + } + + public void beginTran(StoreContext context) throws AMQException + { + if (context.getPayload() != null) + { + throw new AMQException("Fatal internal error: transactional context is not empty at beginTran: " + + context.getPayload()); + } + else + { + try + { + Connection conn = newConnection(); + + + context.setPayload(new ConnectionWrapper(conn)); + } + catch (SQLException e) + { + throw new AMQException("Error starting transaction: " + e, e); + } + } + } + + public void commitTran(StoreContext context) throws AMQException + { + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + if (connWrapper == null) + { + throw new AMQException("Fatal internal error: transactional context is empty at commitTran"); + } + + try + { + Connection conn = connWrapper.getConnection(); + if(connWrapper.requiresCommit()) + { + conn.commit(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("commit tran completed"); + } + + } + conn.close(); + } + catch (SQLException e) + { + throw new AMQException("Error commit tx: " + e, e); + } + finally + { + context.setPayload(null); + } + } + + public void abortTran(StoreContext context) throws AMQException + { + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + if (connWrapper == null) + { + throw new AMQException("Fatal internal error: transactional context is empty at abortTran"); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("abort tran called: " + connWrapper.getConnection()); + } + + try + { + Connection conn = connWrapper.getConnection(); + if(connWrapper.requiresCommit()) + { + conn.rollback(); + } + + conn.close(); + } + catch (SQLException e) + { + throw new AMQException("Error aborting transaction: " + e, e); + } + finally + { + context.setPayload(null); + } + } + + public boolean inTran(StoreContext context) + { + return context.getPayload() != null; + } + + public Long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storeContentBodyChunk(StoreContext context, + Long messageId, + int index, + ContentChunk contentBody, + boolean lastContentBody) throws AMQException + { + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + try + { + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + stmt.setInt(2, index); + byte[] chunkData = new byte[contentBody.getSize()]; + contentBody.getData().duplicate().get(chunkData); + /* this would be the Java 6 way of doing things + Blob dataAsBlob = conn.createBlob(); + dataAsBlob.setBytes(1L, chunkData); + stmt.setBlob(3, dataAsBlob); + */ + ByteArrayInputStream bis = new ByteArrayInputStream(chunkData); + stmt.setBinaryStream(3, bis, chunkData.length); + stmt.executeUpdate(); + connWrapper.requiresCommit(); + + if(localTx) + { + commitTran(context); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + + throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e); + } + + } + + public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData mmd) + throws AMQException + { + + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + ConnectionWrapper connWrapper = (ConnectionWrapper) context.getPayload(); + + try + { + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_META_DATA); + stmt.setLong(1,messageId); + stmt.setString(2, mmd.getMessagePublishInfo().getExchange().toString()); + stmt.setString(3, mmd.getMessagePublishInfo().getRoutingKey().toString()); + stmt.setShort(4, mmd.getMessagePublishInfo().isMandatory() ? (short) 1 : (short) 0); + stmt.setShort(5, mmd.getMessagePublishInfo().isImmediate() ? (short) 1 : (short) 0); + + ContentHeaderBody headerBody = mmd.getContentHeaderBody(); + final int bodySize = headerBody.getSize(); + byte[] underlying = new byte[bodySize]; + ByteBuffer buf = ByteBuffer.wrap(underlying); + headerBody.writePayload(buf); +/* + Blob dataAsBlob = conn.createBlob(); + dataAsBlob.setBytes(1L, underlying); + stmt.setBlob(6, dataAsBlob); +*/ + ByteArrayInputStream bis = new ByteArrayInputStream(underlying); + stmt.setBinaryStream(6,bis,underlying.length); + + stmt.setInt(7, mmd.getContentChunkCount()); + + stmt.executeUpdate(); + connWrapper.requiresCommit(); + + if(localTx) + { + commitTran(context); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + + throw new AMQException("Error writing AMQMessage with id " + messageId + " to database: " + e, e); + } + + + } + + public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException + { + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + + + try + { + + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_META_DATA); + stmt.setLong(1,messageId); + ResultSet rs = stmt.executeQuery(); + + if(rs.next()) + { + final AMQShortString exchange = new AMQShortString(rs.getString(1)); + final AMQShortString routingKey = rs.getString(2) == null ? null : new AMQShortString(rs.getString(2)); + final boolean mandatory = (rs.getShort(3) != (short)0); + final boolean immediate = (rs.getShort(4) != (short)0); + MessagePublishInfo info = 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; + } + } ; + + Blob dataAsBlob = rs.getBlob(5); + + byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length()); + ByteBuffer buf = ByteBuffer.wrap(dataAsBytes); + + ContentHeaderBody chb = ContentHeaderBody.createFromBuffer(buf, dataAsBytes.length); + + if(localTx) + { + commitTran(context); + } + + return new MessageMetaData(info, chb, rs.getInt(6)); + + } + else + { + if(localTx) + { + abortTran(context); + } + throw new AMQException("Metadata not found for message with id " + messageId); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + + throw new AMQException("Error reading AMQMessage with id " + messageId + " from database: " + e, e); + } + + + } + + public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException + { + boolean localTx = getOrCreateTransaction(context); + Connection conn = getConnection(context); + + + try + { + + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + stmt.setInt(2, index); + ResultSet rs = stmt.executeQuery(); + + if(rs.next()) + { + Blob dataAsBlob = rs.getBlob(1); + + final int size = (int) dataAsBlob.length(); + byte[] dataAsBytes = dataAsBlob.getBytes(1, size); + final ByteBuffer buf = ByteBuffer.wrap(dataAsBytes); + + ContentChunk cb = new ContentChunk() + { + + public int getSize() + { + return size; + } + + public ByteBuffer getData() + { + return buf; + } + + public void reduceToFit() + { + + } + }; + + if(localTx) + { + commitTran(context); + } + + return cb; + + } + else + { + if(localTx) + { + abortTran(context); + } + throw new AMQException("Message not found for message with id " + messageId); + } + } + catch (SQLException e) + { + if(localTx) + { + abortTran(context); + } + + throw new AMQException("Error reading AMQMessage with id " + messageId + " from database: " + e, e); + } + + + + } + + public boolean isPersistent() + { + return true; + } + + private void checkNotClosed() throws MessageStoreClosedException + { + if (_closed.get()) + { + throw new MessageStoreClosedException(); + } + } + + + private static final class ProcessAction + { + private final AMQQueue _queue; + private final StoreContext _context; + private final AMQMessage _message; + + public ProcessAction(AMQQueue queue, StoreContext context, AMQMessage message) + { + _queue = queue; + _context = context; + _message = message; + } + + public void process() throws AMQException + { + _queue.enqueue(_context, _message); + + } + + } + + + private void deliverMessages(final StoreContext context, Map<AMQShortString, AMQQueue> queues) + throws SQLException, AMQException + { + Map<Long, AMQMessage> msgMap = new HashMap<Long,AMQMessage>(); + List<ProcessAction> actions = new ArrayList<ProcessAction>(); + + Map<AMQShortString, Integer> queueRecoveries = new TreeMap<AMQShortString, Integer>(); + + final boolean inLocaltran = inTran(context); + Connection conn = null; + try + { + + if(inLocaltran) + { + conn = getConnection(context); + } + else + { + conn = newConnection(); + } + + + MessageHandleFactory messageHandleFactory = new MessageHandleFactory(); + long maxId = 1; + + TransactionalContext txnContext = new NonTransactionalContext(this, new StoreContext(), null, null); + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE_ENTRY); + + + while (rs.next()) + { + + + + AMQShortString queueName = new AMQShortString(rs.getString(1)); + + + AMQQueue queue = queues.get(queueName); + if (queue == null) + { + queue = AMQQueueFactory.createAMQQueueImpl(queueName, false, null, false, _virtualHost, null); + + _virtualHost.getQueueRegistry().registerQueue(queue); + queues.put(queueName, queue); + } + + long messageId = rs.getLong(2); + maxId = Math.max(maxId, messageId); + AMQMessage message = msgMap.get(messageId); + + if(message != null) + { + message.incrementReference(); + } + else + { + message = new AMQMessage(messageId, this, messageHandleFactory, txnContext); + msgMap.put(messageId,message); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("On recovery, delivering " + message.getMessageId() + " to " + queue.getName()); + } + + if (_logger.isInfoEnabled()) + { + Integer count = queueRecoveries.get(queueName); + if (count == null) + { + count = 0; + } + + queueRecoveries.put(queueName, ++count); + + } + + actions.add(new ProcessAction(queue, context, message)); + + } + + for(ProcessAction action : actions) + { + action.process(); + } + + _messageId.set(maxId + 1); + } + catch (SQLException e) + { + _logger.error("Error: " + e, e); + throw e; + } + finally + { + if (inLocaltran && conn != null) + { + conn.close(); + } + } + + if (_logger.isInfoEnabled()) + { + _logger.info("Recovered message counts: " + queueRecoveries); + } + } + + private Connection getConnection(final StoreContext context) + { + return ((ConnectionWrapper)context.getPayload()).getConnection(); + } + + private boolean getOrCreateTransaction(StoreContext context) throws AMQException + { + + ConnectionWrapper tx = (ConnectionWrapper) context.getPayload(); + if (tx == null) + { + beginTran(context); + return true; + } + + return false; + } + + private synchronized void stateTransition(State requiredState, State newState) throws AMQException + { + if (_state != requiredState) + { + throw new AMQException("Cannot transition to the state: " + newState + "; need to be in state: " + requiredState + + "; currently in state: " + _state); + } + + _state = newState; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java new file mode 100644 index 0000000000..4691f02952 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java @@ -0,0 +1,235 @@ +/* + * + * 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; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** A simple message store that stores the messages in a threadsafe structure in memory. */ +public class MemoryMessageStore implements MessageStore +{ + private static final Logger _log = Logger.getLogger(MemoryMessageStore.class); + + private static final int DEFAULT_HASHTABLE_CAPACITY = 50000; + + private static final String HASHTABLE_CAPACITY_CONFIG = "hashtable-capacity"; + + protected ConcurrentMap<Long, MessageMetaData> _metaDataMap; + + protected ConcurrentMap<Long, List<ContentChunk>> _contentBodyMap; + + private final AtomicLong _messageId = new AtomicLong(1); + private AtomicBoolean _closed = new AtomicBoolean(false); + + public void configure() + { + _log.info("Using capacity " + DEFAULT_HASHTABLE_CAPACITY + " for hash tables"); + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(DEFAULT_HASHTABLE_CAPACITY); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>(DEFAULT_HASHTABLE_CAPACITY); + } + + public void configure(String base, VirtualHostConfiguration config) + { + int hashtableCapacity = config.getStoreConfiguration().getInt(base + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY); + _log.info("Using capacity " + hashtableCapacity + " for hash tables"); + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(hashtableCapacity); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>(hashtableCapacity); + } + + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + { + configure(base, config); + } + + public void close() throws Exception + { + _closed.getAndSet(true); + if (_metaDataMap != null) + { + _metaDataMap.clear(); + _metaDataMap = null; + } + if (_contentBodyMap != null) + { + _contentBodyMap.clear(); + _contentBodyMap = null; + } + } + + public void removeMessage(StoreContext context, Long messageId) throws AMQException + { + checkNotClosed(); + if (_log.isDebugEnabled()) + { + _log.debug("Removing message with id " + messageId); + } + _metaDataMap.remove(messageId); + _contentBodyMap.remove(messageId); + } + + public void createExchange(Exchange exchange) throws AMQException + { + + } + + public void removeExchange(Exchange exchange) throws AMQException + { + + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + + } + + + public void createQueue(AMQQueue queue) throws AMQException + { + // Not requred to do anything + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException + { + // Not required to do anything + } + + public void removeQueue(final AMQQueue queue) throws AMQException + { + // Not required to do anything + } + + public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + // Not required to do anything + } + + public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + // Not required to do anything + } + + public void beginTran(StoreContext context) throws AMQException + { + // Not required to do anything + } + + public void commitTran(StoreContext context) throws AMQException + { + // Not required to do anything + } + + public void abortTran(StoreContext context) throws AMQException + { + // Not required to do anything + } + + public boolean inTran(StoreContext context) + { + return false; + } + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public Long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) + throws AMQException + { + checkNotClosed(); + List<ContentChunk> bodyList = _contentBodyMap.get(messageId); + + if (bodyList == null && lastContentBody) + { + _contentBodyMap.put(messageId, Collections.singletonList(contentBody)); + } + else + { + if (bodyList == null) + { + bodyList = new ArrayList<ContentChunk>(); + _contentBodyMap.put(messageId, bodyList); + } + + bodyList.add(index, contentBody); + } + } + + public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) + throws AMQException + { + checkNotClosed(); + _metaDataMap.put(messageId, messageMetaData); + } + + public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException + { + checkNotClosed(); + return _metaDataMap.get(messageId); + } + + public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException + { + checkNotClosed(); + List<ContentChunk> bodyList = _contentBodyMap.get(messageId); + return bodyList.get(index); + } + + public boolean isPersistent() + { + return false; + } + + private void checkNotClosed() throws MessageStoreClosedException + { + if (_closed.get()) + { + throw new MessageStoreClosedException(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java new file mode 100644 index 0000000000..5a1b54b298 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java @@ -0,0 +1,277 @@ +/* + * + * 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; + +import org.apache.commons.configuration.Configuration; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * MessageStore defines the interface to a storage area, which can be used to preserve the state of messages, queues + * and exchanges in a transactional manner. + * + * <p/>All message store, remove, enqueue and dequeue operations are carried out against a {@link StoreContext} which + * encapsulates the transactional context they are performed in. Many such operations can be carried out in a single + * transaction. + * + * <p/>The storage and removal of queues and exchanges, are not carried out in a transactional context. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <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> + */ +public interface MessageStore +{ + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * + * @param virtualHost The virtual host using by this store + * @param base The base element identifier from which all configuration items are relative. For example, if + * the base element is "store", the all elements used by concrete classes will be "store.foo" etc. + * @param hostConfig The apache commons configuration object. + * + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration hostConfig) throws Exception; + + /** + * Called to close and cleanup any resources used by the message store. + * + * @throws Exception If the close fails. + */ + void close() throws Exception; + + /** + * Removes the specified message from the store in the given transactional store context. + * + * @param storeContext The transactional context to remove the message in. + * @param messageId Identifies the message to remove. + * + * @throws AMQException If the operation fails for any reason. + */ + void removeMessage(StoreContext storeContext, Long messageId) throws AMQException; + + /** + * Makes the specified exchange persistent. + * + * @param exchange The exchange to persist. + * + * @throws AMQException If the operation fails for any reason. + */ + void createExchange(Exchange exchange) throws AMQException; + + /** + * Removes the specified persistent exchange. + * + * @param exchange The exchange to remove. + * + * @throws AMQException If the operation fails for any reason. + */ + void removeExchange(Exchange exchange) throws AMQException; + + /** + * Binds the specified queue to an exchange with a routing key. + * + * @param exchange The exchange to bind to. + * @param routingKey The routing key to bind by. + * @param queue The queue to bind. + * @param args Additional parameters. + * + * @throws AMQException If the operation fails for any reason. + */ + void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + /** + * Unbinds the specified from an exchange under a particular routing key. + * + * @param exchange The exchange to unbind from. + * @param routingKey The routing key to unbind. + * @param queue The queue to unbind. + * @param args Additonal parameters. + * + * @throws AMQException If the operation fails for any reason. + */ + void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + /** + * Makes the specified queue persistent. + * + * @param queue The queue to store. + * + * @throws AMQException If the operation fails for any reason. + */ + void createQueue(AMQQueue queue) throws AMQException; + + /** + * Makes the specified queue persistent. + * + * @param queue The queue to store. + * + * @param arguments The additional arguments to the binding + * @throws AMQException If the operation fails for any reason. + */ + void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException; + + /** + * Removes the specified queue from the persistent store. + * + * @param queue The queue to remove. + * @throws AMQException If the operation fails for any reason. + */ + void removeQueue(final AMQQueue queue) throws AMQException; + + /** + * Places a message onto a specified queue, in a given transactional context. + * + * @param context The transactional context for the operation. + * @param queue The queue to place the message on. + * @param messageId The message to enqueue. + * @throws AMQException If the operation fails for any reason. + */ + void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException; + + /** + * Extracts a message from a specified queue, in a given transactional context. + * + * @param context The transactional context for the operation. + * @param queue The queue to place the message on. + * @param messageId The message to dequeue. + * @throws AMQException If the operation fails for any reason, or if the specified message does not exist. + */ + void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException; + + /** + * Begins a transactional context. + * + * @param context The transactional context to begin. + * + * @throws AMQException If the operation fails for any reason. + */ + void beginTran(StoreContext context) throws AMQException; + + /** + * Commits all operations performed within a given transactional context. + * + * @param context The transactional context to commit all operations for. + * + * @throws AMQException If the operation fails for any reason. + */ + void commitTran(StoreContext context) throws AMQException; + + /** + * Abandons all operations performed within a given transactional context. + * + * @param context The transactional context to abandon. + * + * @throws AMQException If the operation fails for any reason. + */ + void abortTran(StoreContext context) throws AMQException; + + /** + * Tests a transactional context to see if it has been begun but not yet committed or aborted. + * + * @param context The transactional context to test. + * + * @return <tt>true</tt> if the transactional context is live, <tt>false</tt> otherwise. + */ + boolean inTran(StoreContext context); + + /** + * Return a valid, currently unused message id. + * + * @return A fresh message id. + */ + Long getNewMessageId(); + + /** + * Stores a chunk of message data. + * + * @param context The transactional context for the operation. + * @param messageId The message to store the data for. + * @param index The index of the data chunk. + * @param contentBody The content of the data chunk. + * @param lastContentBody Flag to indicate that this is the last such chunk for the message. + * + * @throws AMQException If the operation fails for any reason, or if the specified message does not exist. + */ + void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, + boolean lastContentBody) throws AMQException; + + /** + * Stores message meta-data. + * + * @param context The transactional context for the operation. + * @param messageId The message to store the data for. + * @param messageMetaData The message meta data to store. + * + * @throws AMQException If the operation fails for any reason, or if the specified message does not exist. + */ + void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException; + + /** + * Retrieves message meta-data. + * + * @param context The transactional context for the operation. + * @param messageId The message to get the meta-data for. + * + * @return The message meta data. + * + * @throws AMQException If the operation fails for any reason, or if the specified message does not exist. + */ + MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException; + + /** + * Retrieves a chunk of message data. + * + * @param context The transactional context for the operation. + * @param messageId The message to get the data chunk for. + * @param index The offset index of the data chunk within the message. + * + * @return A chunk of message data. + * + * @throws AMQException If the operation fails for any reason, or if the specified message does not exist. + */ + ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException; + + /** + * Is this store capable of persisting the data + * + * @return true if this store is capable of persisting data + */ + boolean isPersistent(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java new file mode 100644 index 0000000000..3d1538c7eb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java @@ -0,0 +1,36 @@ +package org.apache.qpid.server.store; + +import org.apache.qpid.AMQException;/* + * + * 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. + * + */ + +/** + * NOTE: this class currently extends AMQException but + * we should be using AMQExceptions internally in the code base for Protocol errors hence + * the message store interface should throw a different super class which this should be + * moved to reflect + */ +public class MessageStoreClosedException extends AMQException +{ + public MessageStoreClosedException() + { + super("Message store closed"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java new file mode 100644 index 0000000000..fdb56a1a55 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java @@ -0,0 +1,71 @@ +/* + * + * 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; + +import org.apache.log4j.Logger; + +/** + * A context that the store can use to associate with a transactional context. For example, it could store + * some kind of txn id. + * + * @author Apache Software Foundation + */ +public class StoreContext +{ + private static final Logger _logger = Logger.getLogger(StoreContext.class); + + private String _name; + private Object _payload; + + public StoreContext() + { + _name = "StoreContext"; + } + + public StoreContext(String name) + { + _name = name; + } + + public Object getPayload() + { + return _payload; + } + + public void setPayload(Object payload) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("public void setPayload(Object payload = " + payload + "): called"); + } + _payload = payload; + } + + /** + * Prints out the transactional context as a string, mainly for debugging purposes. + * + * @return The transactional context as a string. + */ + public String toString() + { + return "<_name = " + _name + ", _payload = " + _payload + ">"; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java new file mode 100644 index 0000000000..fbc8b3af7d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java @@ -0,0 +1,29 @@ +/* +* +* 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.subscription; + +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.AMQException; + +public interface ClientDeliveryMethod +{ + void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java new file mode 100644 index 0000000000..e2ed4104de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java @@ -0,0 +1,28 @@ +/* +* +* 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.subscription; + +import org.apache.qpid.server.queue.QueueEntry; + +public interface RecordDeliveryMethod +{ + void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java new file mode 100644 index 0000000000..9419572399 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java @@ -0,0 +1,96 @@ +/* + * + * 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.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; + +public interface Subscription +{ + + + public static enum State + { + ACTIVE, + SUSPENDED, + CLOSED + } + + public static interface StateListener + { + public void stateChange(Subscription sub, State oldState, State newState); + } + + AMQQueue getQueue(); + + QueueEntry.SubscriptionAcquiredState getOwningState(); + + void setQueue(AMQQueue queue); + + AMQChannel getChannel(); + + AMQShortString getConsumerTag(); + + boolean isSuspended(); + + boolean hasInterest(QueueEntry msg); + + boolean isAutoClose(); + + boolean isClosed(); + + boolean isBrowser(); + + void close(); + + boolean filtersMessages(); + + void send(QueueEntry msg) throws AMQException; + + void queueDeleted(AMQQueue queue); + + + boolean wouldSuspend(QueueEntry msg); + + void getSendLock(); + void releaseSendLock(); + + void resend(final QueueEntry entry) throws AMQException; + + void restoreCredit(final QueueEntry queueEntry); + + void setStateListener(final StateListener listener); + + public State getState(); + + QueueEntry getLastSeenEntry(); + + boolean setLastSeenEntry(QueueEntry expected, QueueEntry newValue); + + + boolean isActive(); + + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java new file mode 100644 index 0000000000..ce0362d73f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.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.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.AMQChannel; + +/** + * Allows the customisation of the creation of a subscription. This is typically done within an AMQQueue. This factory + * primarily assists testing although in future more sophisticated subscribers may need a different subscription + * implementation. + * + * @see org.apache.qpid.server.queue.AMQQueue + */ +public interface SubscriptionFactory +{ + Subscription createSubscription(int channel, + AMQProtocolSession protocolSession, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException; + + + Subscription createSubscription(AMQChannel channel, + AMQProtocolSession protocolSession, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, + FlowCreditManager creditManager, + ClientDeliveryMethod clientMethod, + RecordDeliveryMethod recordMethod + ) + throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java new file mode 100644 index 0000000000..5badbad642 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java @@ -0,0 +1,103 @@ +/* + * + * 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.subscription; + +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactory; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.common.AMQPFilterTypes; + +public class SubscriptionFactoryImpl implements SubscriptionFactory +{ + + /* private SubscriptionFactoryImpl() + { + + }*/ + + public Subscription createSubscription(int channelId, AMQProtocolSession protocolSession, + AMQShortString consumerTag, boolean acks, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException + { + AMQChannel channel = protocolSession.getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "channel :" + channelId + " not found in protocol session"); + } + ClientDeliveryMethod clientMethod = channel.getClientDeliveryMethod(); + RecordDeliveryMethod recordMethod = channel.getRecordDeliveryMethod(); + + + return createSubscription(channel, protocolSession, consumerTag, acks, filters, + noLocal, + creditManager, + clientMethod, + recordMethod + ); + } + + public Subscription createSubscription(final AMQChannel channel, + final AMQProtocolSession protocolSession, + final AMQShortString consumerTag, + final boolean acks, + final FieldTable filters, + final boolean noLocal, + final FlowCreditManager creditManager, + final ClientDeliveryMethod clientMethod, + final RecordDeliveryMethod recordMethod + ) + throws AMQException + { + boolean isBrowser; + + if (filters != null) + { + Boolean isBrowserObj = (Boolean) filters.get(AMQPFilterTypes.NO_CONSUME.getValue()); + isBrowser = (isBrowserObj != null) && isBrowserObj.booleanValue(); + } + else + { + isBrowser = false; + } + + if(isBrowser) + { + return new SubscriptionImpl.BrowserSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else if(acks) + { + return new SubscriptionImpl.AckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else + { + return new SubscriptionImpl.NoAckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + } + + + public static final SubscriptionFactoryImpl INSTANCE = new SubscriptionFactoryImpl(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java new file mode 100644 index 0000000000..a616c2ea35 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java @@ -0,0 +1,617 @@ +/* + * + * 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.subscription; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.filter.FilterManagerFactory; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.StoreContext; + +/** + * Encapsulation of a supscription to a queue. <p/> Ties together the protocol session of a subscriber, the consumer tag + * that was given out by the broker and the channel id. <p/> + */ +public abstract class SubscriptionImpl implements Subscription, FlowCreditManager.FlowCreditManagerListener +{ + + private StateListener _stateListener = new StateListener() + { + + public void stateChange(Subscription sub, State oldState, State newState) + { + + } + }; + + + private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE); + private final AtomicReference<QueueEntry> _queueContext = new AtomicReference<QueueEntry>(null); + private final ClientDeliveryMethod _deliveryMethod; + private final RecordDeliveryMethod _recordMethod; + + private QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + private final Lock _stateChangeLock; + + static final class BrowserSubscription extends SubscriptionImpl + { + public BrowserSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return true; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param msg The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry msg) throws AMQException + { + // We don't decrement the reference here as we don't want to consume the message + // but we do want to send it to the client. + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + sendToClient(msg, deliveryTag); + } + + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + } + + public static class NoAckSubscription extends SubscriptionImpl + { + public NoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param entry The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry entry) throws AMQException + { + + StoreContext storeContext = getChannel().getStoreContext(); + try + { // if we do not need to wait for client acknowledgements + // we can decrement the reference count immediately. + + // By doing this _before_ the send we ensure that it + // doesn't get sent if it can't be dequeued, preventing + // duplicate delivery on recovery. + + // The send may of course still fail, in which case, as + // the message is unacked, it will be lost. + entry.dequeue(storeContext); + + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + + sendToClient(entry, deliveryTag); + + } + entry.dispose(storeContext); + } + finally + { + //Only set delivered if it actually was writen successfully.. + // using a try->finally would set it even if an error occured. + // Is this what we want? + + entry.setDeliveredToSubscription(); + } + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + } + + static final class AckSubscription extends SubscriptionImpl + { + public AckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param entry The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry entry) throws AMQException + { + + try + { // if we do not need to wait for client acknowledgements + // we can decrement the reference count immediately. + + // By doing this _before_ the send we ensure that it + // doesn't get sent if it can't be dequeued, preventing + // duplicate delivery on recovery. + + // The send may of course still fail, in which case, as + // the message is unacked, it will be lost. + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + + + recordMessageDelivery(entry, deliveryTag); + sendToClient(entry, deliveryTag); + + + } + } + finally + { + //Only set delivered if it actually was writen successfully.. + // using a try->finally would set it even if an error occured. + // Is this what we want? + + entry.setDeliveredToSubscription(); + } + } + + + } + + + private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class); + + private final AMQChannel _channel; + + private final AMQShortString _consumerTag; + + + private final boolean _noLocal; + + private final FlowCreditManager _creditManager; + + private FilterManager _filters; + + private final Boolean _autoClose; + + + private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString(); + + private AMQQueue _queue; + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + + + + public SubscriptionImpl(AMQChannel channel , AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable arguments, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + + _channel = channel; + _consumerTag = consumerTag; + + _creditManager = creditManager; + creditManager.addStateListener(this); + + _noLocal = noLocal; + + + _filters = FilterManagerFactory.createManager(arguments); + + _deliveryMethod = deliveryMethod; + _recordMethod = recordMethod; + + + _stateChangeLock = new ReentrantLock(); + + + if (arguments != null) + { + Object autoClose = arguments.get(AMQPFilterTypes.AUTO_CLOSE.getValue()); + if (autoClose != null) + { + _autoClose = (Boolean) autoClose; + } + else + { + _autoClose = false; + } + } + else + { + _autoClose = false; + } + + + } + + + + public synchronized void setQueue(AMQQueue queue) + { + if(getQueue() != null) + { + throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue()); + } + _queue = queue; + } + + public String toString() + { + String subscriber = "[channel=" + _channel + + ", consumerTag=" + _consumerTag + + ", session=" + getProtocolSession().getKey() ; + + return subscriber + "]"; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param msg The message to send + * @throws AMQException + */ + abstract public void send(QueueEntry msg) throws AMQException; + + + public boolean isSuspended() + { + return !isActive() || _channel.isSuspended() || _deleted.get(); + } + + /** + * Callback indicating that a queue has been deleted. + * + * @param queue The queue to delete + */ + public void queueDeleted(AMQQueue queue) + { + _deleted.set(true); +// _channel.queueDeleted(queue); + } + + public boolean filtersMessages() + { + return _filters != null || _noLocal; + } + + public boolean hasInterest(QueueEntry entry) + { + //check that the message hasn't been rejected + if (entry.isRejectedBy(this)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Subscription:" + debugIdentity() + " rejected message:" + entry.debugIdentity()); + } +// return false; + } + + + + //todo - client id should be recoreded and this test removed but handled below + if (_noLocal) + { + final Object publisherId = entry.getMessage().getPublisherClientInstance(); + + // We don't want local messages so check to see if message is one we sent + Object localInstance; + + if (publisherId != null && (getProtocolSession().getClientProperties() != null) && + (localInstance = getProtocolSession().getClientProperties().getObject(CLIENT_PROPERTIES_INSTANCE)) != null) + { + if(publisherId.equals(localInstance)) + { + return false; + } + } + else + { + + localInstance = getProtocolSession().getClientIdentifier(); + //todo - client id should be recoreded and this test removed but handled here + + + if (localInstance != null && localInstance.equals(entry.getMessage().getPublisherIdentifier())) + { + return false; + } + } + + + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("(" + debugIdentity() + ") checking filters for message (" + entry.debugIdentity()); + } + return checkFilters(entry); + + } + + private String id = String.valueOf(System.identityHashCode(this)); + + private String debugIdentity() + { + return id; + } + + private boolean checkFilters(QueueEntry msg) + { + return (_filters == null) || _filters.allAllow(msg.getMessage()); + } + + public boolean isAutoClose() + { + return _autoClose; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + + public void close() + { + boolean closed = false; + State state = getState(); + + _stateChangeLock.lock(); + try + { + while(!closed && state != State.CLOSED) + { + closed = _state.compareAndSet(state, State.CLOSED); + if(!closed) + { + state = getState(); + } + else + { + _stateListener.stateChange(this,state, State.CLOSED); + } + } + _creditManager.removeListener(this); + } + finally + { + _stateChangeLock.unlock(); + } + + + if (closed) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Called close() on a closed subscription"); + } + + return; + } + + if (_logger.isInfoEnabled()) + { + _logger.info("Closing subscription (" + debugIdentity() + "):" + this); + } + } + + public boolean isClosed() + { + return getState() == State.CLOSED; + } + + + public boolean wouldSuspend(QueueEntry msg) + { + return !_creditManager.useCreditForMessage(msg.getMessage());//_channel.wouldSuspend(msg.getMessage()); + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void resend(final QueueEntry entry) throws AMQException + { + _queue.resend(entry, this); + } + + public AMQChannel getChannel() + { + return _channel; + } + + public AMQShortString getConsumerTag() + { + return _consumerTag; + } + + public AMQProtocolSession getProtocolSession() + { + return _channel.getProtocolSession(); + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void restoreCredit(final QueueEntry queueEntry) + { + _creditManager.addCredit(1, queueEntry.getSize()); + } + + + public void creditStateChanged(boolean hasCredit) + { + + if(hasCredit) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + else + { + // this is a hack to get round the issue of increasing bytes credit + _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE); + } + } + else + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + } + + public State getState() + { + return _state.get(); + } + + + public void setStateListener(final StateListener listener) + { + _stateListener = listener; + } + + + public QueueEntry getLastSeenEntry() + { + return _queueContext.get(); + } + + public boolean setLastSeenEntry(QueueEntry expected, QueueEntry newvalue) + { + return _queueContext.compareAndSet(expected,newvalue); + } + + + protected void sendToClient(final QueueEntry entry, final long deliveryTag) + throws AMQException + { + _deliveryMethod.deliverToClient(this,entry,deliveryTag); + } + + + protected void recordMessageDelivery(final QueueEntry entry, final long deliveryTag) + { + _recordMethod.recordMessageDelivery(this,entry,deliveryTag); + } + + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java new file mode 100644 index 0000000000..3fbb6bfa4a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java @@ -0,0 +1,247 @@ +/* +* +* 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.subscription; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.subscription.Subscription; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.nio.ByteBuffer; + +public class SubscriptionList +{ + + private final SubscriptionNode _head = new SubscriptionNode(); + + private AtomicReference<SubscriptionNode> _tail = new AtomicReference<SubscriptionNode>(_head); + private final AMQQueue _queue; + private AtomicInteger _size = new AtomicInteger(); + + + public final class SubscriptionNode + { + private final AtomicBoolean _deleted = new AtomicBoolean(); + private final AtomicReference<SubscriptionNode> _next = new AtomicReference<SubscriptionNode>(); + private final Subscription _sub; + + + public SubscriptionNode() + { + + _sub = null; + _deleted.set(true); + } + + public SubscriptionNode(final Subscription sub) + { + _sub = sub; + } + + + public SubscriptionNode getNext() + { + + SubscriptionNode next = nextNode(); + while(next != null && next.isDeleted()) + { + + final SubscriptionNode newNext = next.nextNode(); + if(newNext != null) + { + _next.compareAndSet(next, newNext); + next = nextNode(); + } + else + { + next = null; + } + + } + return next; + } + + private SubscriptionNode nextNode() + { + return _next.get(); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + + public boolean delete() + { + if(_deleted.compareAndSet(false,true)) + { + _size.decrementAndGet(); + advanceHead(); + return true; + } + else + { + return false; + } + } + + + public Subscription getSubscription() + { + return _sub; + } + } + + + public SubscriptionList(AMQQueue queue) + { + _queue = queue; + } + + private void advanceHead() + { + SubscriptionNode head = _head.nextNode(); + while(head._next.get() != null && head.isDeleted()) + { + + final SubscriptionNode newhead = head.nextNode(); + if(newhead != null) + { + _head._next.compareAndSet(head, newhead); + } + head = _head.nextNode(); + } + } + + + public SubscriptionNode add(Subscription sub) + { + SubscriptionNode node = new SubscriptionNode(sub); + for (;;) + { + SubscriptionNode tail = _tail.get(); + SubscriptionNode next = tail.nextNode(); + if (tail == _tail.get()) + { + if (next == null) + { + if (tail._next.compareAndSet(null, node)) + { + _tail.compareAndSet(tail, node); + _size.incrementAndGet(); + return node; + } + } + else + { + _tail.compareAndSet(tail, next); + } + } + } + + } + + public boolean remove(Subscription sub) + { + SubscriptionNode node = _head.getNext(); + while(node != null) + { + if(sub.equals(node._sub) && node.delete()) + { + return true; + } + node = node.getNext(); + } + return false; + } + + + public class SubscriptionNodeIterator + { + + private SubscriptionNode _lastNode; + + SubscriptionNodeIterator(SubscriptionNode startNode) + { + _lastNode = startNode; + } + + + public boolean atTail() + { + return _lastNode.nextNode() == null; + } + + public SubscriptionNode getNode() + { + + return _lastNode; + + } + + public boolean advance() + { + + if(!atTail()) + { + SubscriptionNode nextNode = _lastNode.nextNode(); + while(nextNode.isDeleted() && nextNode.nextNode() != null) + { + nextNode = nextNode.nextNode(); + } + _lastNode = nextNode; + return true; + + } + else + { + return false; + } + + } + + } + + + public SubscriptionNodeIterator iterator() + { + return new SubscriptionNodeIterator(_head); + } + + + public SubscriptionNode getHead() + { + return _head; + } + + public int size() + { + return _size.get(); + } + + + +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java new file mode 100644 index 0000000000..bdd27f2d1c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ThreadPoolFilter.java @@ -0,0 +1,705 @@ +/* + * + * 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.transport; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilterAdapter; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoSession; +import org.apache.mina.util.BlockingQueue; +import org.apache.mina.util.ByteBufferUtil; +import org.apache.mina.util.IdentityHashSet; +import org.apache.mina.util.Queue; +import org.apache.mina.util.Stack; + +/** + * A Thread-pooling filter. This filter forwards {@link IoHandler} events + * to its thread pool. + * <p/> + * This is an implementation of + * <a href="http://deuce.doc.wustl.edu/doc/pspdfs/lf.pdf">Leader/Followers + * thread pool</a> by Douglas C. Schmidt et al. + */ +public class ThreadPoolFilter extends IoFilterAdapter +{ + /** + * Default maximum size of thread pool (2G). + */ + public static final int DEFAULT_MAXIMUM_POOL_SIZE = Integer.MAX_VALUE; + + /** + * Default keep-alive time of thread pool (1 min). + */ + public static final int DEFAULT_KEEP_ALIVE_TIME = 60 * 1000; + + /** + * A queue which contains {@link Integer}s which represents reusable + * thread IDs. {@link Worker} first checks this queue and then + * uses {@link #threadId} when no reusable thread ID is available. + */ + private static final Queue threadIdReuseQueue = new Queue(); + private static int threadId = 0; + + private static int acquireThreadId() + { + synchronized (threadIdReuseQueue) + { + Integer id = (Integer) threadIdReuseQueue.pop(); + if (id == null) + { + return ++ threadId; + } + else + { + return id.intValue(); + } + } + } + + private static void releaseThreadId(int id) + { + synchronized (threadIdReuseQueue) + { + threadIdReuseQueue.push(new Integer(id)); + } + } + + private final String threadNamePrefix; + private final Map buffers = new IdentityHashMap(); + private final BlockingQueue unfetchedSessionBuffers = new BlockingQueue(); + private final Set allSessionBuffers = new IdentityHashSet(); + + private Worker leader; + private final Stack followers = new Stack(); + private final Set allWorkers = new IdentityHashSet(); + + private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE; + private int keepAliveTime = DEFAULT_KEEP_ALIVE_TIME; + + private boolean shuttingDown; + + private int poolSize; + private final Object poolSizeLock = new Object(); + + /** + * Creates a new instance of this filter with default thread pool settings. + */ + public ThreadPoolFilter() + { + this("IoThreadPool"); + } + + /** + * Creates a new instance of this filter with the specified thread name prefix + * and other default settings. + * + * @param threadNamePrefix the prefix of the thread names this pool will create. + */ + public ThreadPoolFilter(String threadNamePrefix) + { + if (threadNamePrefix == null) + { + throw new NullPointerException("threadNamePrefix"); + } + threadNamePrefix = threadNamePrefix.trim(); + if (threadNamePrefix.length() == 0) + { + throw new IllegalArgumentException("threadNamePrefix is empty."); + } + this.threadNamePrefix = threadNamePrefix; + } + + public String getThreadNamePrefix() + { + return threadNamePrefix; + } + + public int getPoolSize() + { + synchronized (poolSizeLock) + { + return poolSize; + } + } + + public int getMaximumPoolSize() + { + return maximumPoolSize; + } + + public int getKeepAliveTime() + { + return keepAliveTime; + } + + public void setMaximumPoolSize(int maximumPoolSize) + { + if (maximumPoolSize <= 0) + { + throw new IllegalArgumentException(); + } + this.maximumPoolSize = maximumPoolSize; + } + + public void setKeepAliveTime(int keepAliveTime) + { + this.keepAliveTime = keepAliveTime; + } + + public void init() + { + shuttingDown = false; + leader = new Worker(); + leader.start(); + leader.lead(); + } + + public void destroy() + { + shuttingDown = true; + int expectedPoolSize = 0; + while (getPoolSize() != expectedPoolSize) + { + List allWorkers; + synchronized (poolSizeLock) + { + allWorkers = new ArrayList(this.allWorkers); + } + + // You may not interrupt the current thread. + if (allWorkers.remove(Thread.currentThread())) + { + expectedPoolSize = 1; + } + + for (Iterator i = allWorkers.iterator(); i.hasNext();) + { + Worker worker = (Worker) i.next(); + while (worker.isAlive()) + { + worker.interrupt(); + try + { + // This timeout will help us from + // infinite lock-up and interrupt workers again. + worker.join(100); + } + catch (InterruptedException e) + { + } + } + } + } + + this.allSessionBuffers.clear(); + this.unfetchedSessionBuffers.clear(); + this.buffers.clear(); + this.followers.clear(); + this.leader = null; + } + + private void increasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize++; + allWorkers.add(worker); + } + } + + private void decreasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize--; + allWorkers.remove(worker); + } + } + + private void fireEvent(NextFilter nextFilter, IoSession session, + EventType type, Object data) + { + final BlockingQueue unfetchedSessionBuffers = this.unfetchedSessionBuffers; + final Set allSessionBuffers = this.allSessionBuffers; + final Event event = new Event(type, nextFilter, data); + + synchronized (unfetchedSessionBuffers) + { + final SessionBuffer buf = getSessionBuffer(session); + final Queue eventQueue = buf.eventQueue; + + synchronized (buf) + { + eventQueue.push(event); + } + + if (!allSessionBuffers.contains(buf)) + { + allSessionBuffers.add(buf); + unfetchedSessionBuffers.push(buf); + } + } + } + + /** + * Implement this method to fetch (or pop) a {@link SessionBuffer} from + * the given <tt>unfetchedSessionBuffers</tt>. The default implementation + * simply pops the buffer from it. You could prioritize the fetch order. + * + * @return A non-null {@link SessionBuffer} + */ + protected SessionBuffer fetchSessionBuffer(Queue unfetchedSessionBuffers) + { + return (SessionBuffer) unfetchedSessionBuffers.pop(); + } + + private SessionBuffer getSessionBuffer(IoSession session) + { + final Map buffers = this.buffers; + SessionBuffer buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + synchronized (buffers) + { + buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + buf = new SessionBuffer(session); + buffers.put(session, buf); + } + } + } + return buf; + } + + private void removeSessionBuffer(SessionBuffer buf) + { + final Map buffers = this.buffers; + final IoSession session = buf.session; + synchronized (buffers) + { + buffers.remove(session); + } + } + + protected static class SessionBuffer + { + private final IoSession session; + + private final Queue eventQueue = new Queue(); + + private SessionBuffer(IoSession session) + { + this.session = session; + } + + public IoSession getSession() + { + return session; + } + + public Queue getEventQueue() + { + return eventQueue; + } + } + + private class Worker extends Thread + { + private final int id; + private final Object promotionLock = new Object(); + private boolean dead; + + private Worker() + { + int id = acquireThreadId(); + this.id = id; + this.setName(threadNamePrefix + '-' + id); + increasePoolSize(this); + } + + public boolean lead() + { + final Object promotionLock = this.promotionLock; + synchronized (promotionLock) + { + if (dead) + { + return false; + } + + leader = this; + promotionLock.notify(); + } + + return true; + } + + public void run() + { + for (; ;) + { + if (!waitForPromotion()) + { + break; + } + + SessionBuffer buf = fetchBuffer(); + giveUpLead(); + if (buf == null) + { + break; + } + + processEvents(buf); + follow(); + releaseBuffer(buf); + } + + decreasePoolSize(this); + releaseThreadId(id); + } + + private SessionBuffer fetchBuffer() + { + BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + synchronized (unfetchedSessionBuffers) + { + while (!shuttingDown) + { + try + { + unfetchedSessionBuffers.waitForNewItem(); + } + catch (InterruptedException e) + { + continue; + } + + return ThreadPoolFilter.this.fetchSessionBuffer(unfetchedSessionBuffers); + } + } + + return null; + } + + private void processEvents(SessionBuffer buf) + { + final IoSession session = buf.session; + final Queue eventQueue = buf.eventQueue; + for (; ;) + { + Event event; + synchronized (buf) + { + event = (Event) eventQueue.pop(); + if (event == null) + { + break; + } + } + processEvent(event.getNextFilter(), session, + event.getType(), event.getData()); + } + } + + private void follow() + { + final Object promotionLock = this.promotionLock; + final Stack followers = ThreadPoolFilter.this.followers; + synchronized (promotionLock) + { + if (this != leader) + { + synchronized (followers) + { + followers.push(this); + } + } + } + } + + private void releaseBuffer(SessionBuffer buf) + { + final BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + final Set allSessionBuffers = ThreadPoolFilter.this.allSessionBuffers; + final Queue eventQueue = buf.eventQueue; + + synchronized (unfetchedSessionBuffers) + { + if (eventQueue.isEmpty()) + { + allSessionBuffers.remove(buf); + removeSessionBuffer(buf); + } + else + { + unfetchedSessionBuffers.push(buf); + } + } + } + + private boolean waitForPromotion() + { + final Object promotionLock = this.promotionLock; + + long startTime = System.currentTimeMillis(); + long currentTime = System.currentTimeMillis(); + + synchronized (promotionLock) + { + while (this != leader && !shuttingDown) + { + // Calculate remaining keep-alive time + int keepAliveTime = getKeepAliveTime(); + if (keepAliveTime > 0) + { + keepAliveTime -= (currentTime - startTime); + } + else + { + keepAliveTime = Integer.MAX_VALUE; + } + + // Break the loop if there's no remaining keep-alive time. + if (keepAliveTime <= 0) + { + break; + } + + // Wait for promotion + try + { + promotionLock.wait(keepAliveTime); + } + catch (InterruptedException e) + { + } + + // Update currentTime for the next iteration + currentTime = System.currentTimeMillis(); + } + + boolean timeToLead = this == leader && !shuttingDown; + + if (!timeToLead) + { + // time to die + synchronized (followers) + { + followers.remove(this); + } + + // Mark as dead explicitly when we've got promotionLock. + dead = true; + } + + return timeToLead; + } + } + + private void giveUpLead() + { + final Stack followers = ThreadPoolFilter.this.followers; + Worker worker; + do + { + synchronized (followers) + { + worker = (Worker) followers.pop(); + } + + if (worker == null) + { + // Increase the number of threads if we + // are not shutting down and we can increase the number. + if (!shuttingDown + && getPoolSize() < getMaximumPoolSize()) + { + worker = new Worker(); + worker.lead(); + worker.start(); + } + + // This loop should end because: + // 1) lead() is called already, + // 2) or it is shutting down and there's no more threads left. + break; + } + } + while (!worker.lead()); + } + } + + protected static class EventType + { + public static final EventType OPENED = new EventType("OPENED"); + + public static final EventType CLOSED = new EventType("CLOSED"); + + public static final EventType READ = new EventType("READ"); + + public static final EventType WRITTEN = new EventType("WRITTEN"); + + public static final EventType RECEIVED = new EventType("RECEIVED"); + + public static final EventType SENT = new EventType("SENT"); + + public static final EventType IDLE = new EventType("IDLE"); + + public static final EventType EXCEPTION = new EventType("EXCEPTION"); + + private final String value; + + private EventType(String value) + { + this.value = value; + } + + public String toString() + { + return value; + } + } + + protected static class Event + { + private final EventType type; + private final NextFilter nextFilter; + private final Object data; + + public Event(EventType type, NextFilter nextFilter, Object data) + { + this.type = type; + this.nextFilter = nextFilter; + this.data = data; + } + + public Object getData() + { + return data; + } + + + public NextFilter getNextFilter() + { + return nextFilter; + } + + + public EventType getType() + { + return type; + } + } + + public void sessionCreated(NextFilter nextFilter, IoSession session) + { + nextFilter.sessionCreated(session); + } + + public void sessionOpened(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.OPENED, null); + } + + public void sessionClosed(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.CLOSED, null); + } + + public void sessionIdle(NextFilter nextFilter, + IoSession session, IdleStatus status) + { + fireEvent(nextFilter, session, EventType.IDLE, status); + } + + public void exceptionCaught(NextFilter nextFilter, + IoSession session, Throwable cause) + { + fireEvent(nextFilter, session, EventType.EXCEPTION, cause); + } + + public void messageReceived(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.RECEIVED, message); + } + + public void messageSent(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.SENT, message); + } + + protected void processEvent(NextFilter nextFilter, + IoSession session, EventType type, + Object data) + { + if (type == EventType.RECEIVED) + { + nextFilter.messageReceived(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.SENT) + { + nextFilter.messageSent(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.EXCEPTION) + { + nextFilter.exceptionCaught(session, (Throwable) data); + } + else if (type == EventType.IDLE) + { + nextFilter.sessionIdle(session, (IdleStatus) data); + } + else if (type == EventType.OPENED) + { + nextFilter.sessionOpened(session); + } + else if (type == EventType.CLOSED) + { + nextFilter.sessionClosed(session); + } + } + + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) + { + nextFilter.filterWrite(session, writeRequest); + } + + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception + { + nextFilter.filterClose(session); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java new file mode 100644 index 0000000000..3c71282c57 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransactionalContext.java @@ -0,0 +1,293 @@ +/* + * + * 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.txn; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ack.TxAck; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; + +import java.util.List; +import java.util.ArrayList; + +/** A transactional context that only supports local transactions. */ +public class LocalTransactionalContext implements TransactionalContext +{ + private static final Logger _log = Logger.getLogger(LocalTransactionalContext.class); + + private final TxnBuffer _txnBuffer = new TxnBuffer(); + + private final List<DeliveryAction> _postCommitDeliveryList = new ArrayList<DeliveryAction>(); + + /** + * We keep hold of the ack operation so that we can consolidate acks, i.e. multiple acks within a txn are + * consolidated into a single operation + */ + private TxAck _ackOp; + + private boolean _inTran = false; + + /** Are there messages to deliver. NOT Has the message been delivered */ + private boolean _messageDelivered = false; + private final AMQChannel _channel; + + + private abstract class DeliveryAction + { + + abstract public void process() throws AMQException; + + } + + private class RequeueAction extends DeliveryAction + { + public QueueEntry entry; + + public RequeueAction(QueueEntry entry) + { + this.entry = entry; + } + + public void process() throws AMQException + { + entry.requeue(getStoreContext()); + } + } + + private class PublishAction extends DeliveryAction + { + private final AMQQueue _queue; + private final AMQMessage _message; + + public PublishAction(final AMQQueue queue, final AMQMessage message) + { + _queue = queue; + _message = message; + } + + public void process() throws AMQException + { + + _message.incrementReference(); + try + { + QueueEntry entry = _queue.enqueue(getStoreContext(),_message); + + if(entry.immediateAndNotDelivered()) + { + getReturnMessages().add(new NoConsumersException(_message)); + } + } + finally + { + _message.decrementReference(getStoreContext()); + } + } + } + + public LocalTransactionalContext(final AMQChannel channel) + { + _channel = channel; + } + + public StoreContext getStoreContext() + { + return _channel.getStoreContext(); + } + + public List<RequiredDeliveryException> getReturnMessages() + { + return _channel.getReturnMessages(); + } + + public MessageStore getMessageStore() + { + return _channel.getMessageStore(); + } + + + public void rollback() throws AMQException + { + _txnBuffer.rollback(getStoreContext()); + // Hack to deal with uncommitted non-transactional writes + if (getMessageStore().inTran(getStoreContext())) + { + getMessageStore().abortTran(getStoreContext()); + _inTran = false; + } + + _postCommitDeliveryList.clear(); + } + + public void deliver(final AMQQueue queue, AMQMessage message) throws AMQException + { + // A publication will result in the enlisting of several + // TxnOps. The first is an op that will store the message. + // Following that (and ordering is important), an op will + // be added for every queue onto which the message is + // enqueued. + _postCommitDeliveryList.add(new PublishAction(queue, message)); + _messageDelivered = true; + + } + + public void requeue(QueueEntry entry) throws AMQException + { + _postCommitDeliveryList.add(new RequeueAction(entry)); + _messageDelivered = true; + + } + + + private void checkAck(long deliveryTag, UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException + { + if (!unacknowledgedMessageMap.contains(deliveryTag)) + { + throw new AMQException("Ack with delivery tag " + deliveryTag + " not known for channel"); + } + } + + public void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple, + UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException + { + // check that the tag exists to give early failure + if (!multiple || (deliveryTag > 0)) + { + checkAck(deliveryTag, unacknowledgedMessageMap); + } + // we use a single txn op for all acks and update this op + // as new acks come in. If this is the first ack in the txn + // we will need to create and enlist the op. + if (_ackOp == null) + { + _ackOp = new TxAck(unacknowledgedMessageMap); + _txnBuffer.enlist(_ackOp); + } + // update the op to include this ack request + if (multiple && (deliveryTag == 0)) + { + // if have signalled to ack all, that refers only + // to all at this time + _ackOp.update(lastDeliveryTag, multiple); + } + else + { + _ackOp.update(deliveryTag, multiple); + } + if(!_inTran && _ackOp.checkPersistent()) + { + beginTranIfNecessary(); + } + } + + public void messageFullyReceived(boolean persistent) throws AMQException + { + // Not required in this transactional context + } + + public void messageProcessed(AMQProtocolSession protocolSession) throws AMQException + { + // Not required in this transactional context + } + + public void beginTranIfNecessary() throws AMQException + { + if (!_inTran) + { + if (_log.isDebugEnabled()) + { + _log.debug("Starting transaction on message store: " + this); + } + + getMessageStore().beginTran(getStoreContext()); + _inTran = true; + } + } + + public void commit() throws AMQException + { + if (_log.isDebugEnabled()) + { + _log.debug("Committing transactional context: " + this); + } + + if (_ackOp != null) + { + + _messageDelivered = true; + _ackOp.consolidate(); + // already enlisted, after commit will reset regardless of outcome + _ackOp = null; + } + + if (_messageDelivered && _inTran) + { + _txnBuffer.enlist(new StoreMessageOperation(getMessageStore())); + } + // fixme fail commit here ... QPID-440 + try + { + _txnBuffer.commit(getStoreContext()); + } + finally + { + _messageDelivered = false; + _inTran = getMessageStore().inTran(getStoreContext()); + } + + try + { + postCommitDelivery(); + } + catch (AMQException e) + { + // OK so what do we do now...? + _log.error("Failed to deliver messages following txn commit: " + e, e); + } + } + + private void postCommitDelivery() throws AMQException + { + if (_log.isDebugEnabled()) + { + _log.debug("Performing post commit delivery"); + } + + try + { + for (DeliveryAction dd : _postCommitDeliveryList) + { + dd.process(); + } + } + finally + { + _postCommitDeliveryList.clear(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java new file mode 100644 index 0000000000..28af36e3db --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NonTransactionalContext.java @@ -0,0 +1,213 @@ +/* + * + * 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.txn; + +import java.util.LinkedList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; + +/** @author Apache Software Foundation */ +public class NonTransactionalContext implements TransactionalContext +{ + private static final Logger _log = Logger.getLogger(NonTransactionalContext.class); + + /** Channel is useful for logging */ + private final AMQChannel _channel; + + /** Where to put undeliverable messages */ + private final List<RequiredDeliveryException> _returnMessages; + + + + private final MessageStore _messageStore; + + private final StoreContext _storeContext; + + /** Whether we are in a transaction */ + private boolean _inTran; + + public NonTransactionalContext(MessageStore messageStore, StoreContext storeContext, AMQChannel channel, + List<RequiredDeliveryException> returnMessages) + { + _channel = channel; + _storeContext = storeContext; + _returnMessages = returnMessages; + _messageStore = messageStore; + + } + + + public StoreContext getStoreContext() + { + return _storeContext; + } + + public void beginTranIfNecessary() throws AMQException + { + if (!_inTran) + { + _messageStore.beginTran(_storeContext); + _inTran = true; + } + } + + public void commit() throws AMQException + { + // Does not apply to this context + } + + public void rollback() throws AMQException + { + // Does not apply to this context + } + + public void deliver(final AMQQueue queue, AMQMessage message) throws AMQException + { + QueueEntry entry = queue.enqueue(_storeContext, message); + + //following check implements the functionality + //required by the 'immediate' flag: + if(entry.immediateAndNotDelivered()) + { + _returnMessages.add(new NoConsumersException(entry.getMessage())); + } + + } + + public void requeue(QueueEntry entry) throws AMQException + { + entry.requeue(_storeContext); + } + + public void acknowledgeMessage(final long deliveryTag, long lastDeliveryTag, + boolean multiple, final UnacknowledgedMessageMap unacknowledgedMessageMap) + throws AMQException + { + + final boolean debug = _log.isDebugEnabled(); + ; + if (multiple) + { + if (deliveryTag == 0) + { + + //Spec 2.1.6.11 ... If the multiple field is 1, and the delivery tag is zero, + // tells the server to acknowledge all outstanding mesages. + _log.info("Multiple ack on delivery tag 0. ACKing all messages. Current count:" + + unacknowledgedMessageMap.size()); + unacknowledgedMessageMap.visit(new UnacknowledgedMessageMap.Visitor() + { + public boolean callback(final long deliveryTag, QueueEntry message) throws AMQException + { + if (debug) + { + _log.debug("Discarding message: " + message.getMessage().getMessageId()); + } + if(message.getMessage().isPersistent()) + { + beginTranIfNecessary(); + } + //Message has been ack so discard it. This will dequeue and decrement the reference. + message.discard(_storeContext); + + return false; + } + + public void visitComplete() + { + unacknowledgedMessageMap.clear(); + } + }); + } + else + { + if (!unacknowledgedMessageMap.contains(deliveryTag)) + { + throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel"); + } + + unacknowledgedMessageMap.drainTo(deliveryTag, _storeContext); + } + } + else + { + QueueEntry msg; + msg = unacknowledgedMessageMap.get(deliveryTag); + + if (msg == null) + { + _log.info("Single ack on delivery tag " + deliveryTag + " not known for channel:" + + _channel.getChannelId()); + throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" + + _channel.getChannelId()); + } + + if (debug) + { + _log.debug("Discarding message: " + msg.getMessage().getMessageId()); + } + if(msg.getMessage().isPersistent()) + { + beginTranIfNecessary(); + } + + //Message has been ack so discard it. This will dequeue and decrement the reference. + msg.discard(_storeContext); + + unacknowledgedMessageMap.remove(deliveryTag); + + + if (debug) + { + _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag + " msg id " + + msg.getMessage().getMessageId()); + } + } + if(_inTran) + { + _messageStore.commitTran(_storeContext); + _inTran = false; + } + } + + public void messageFullyReceived(boolean persistent) throws AMQException + { + if (persistent) + { + _messageStore.commitTran(_storeContext); + _inTran = false; + } + } + + public void messageProcessed(AMQProtocolSession protocolSession) throws AMQException + { + _channel.processReturns(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.java new file mode 100644 index 0000000000..0e4d6c2030 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/StoreMessageOperation.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.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; + +/** + * A transactional operation to store messages in an underlying persistent store. When this operation + * commits it will do everything to ensure that all messages are safely committed to persistent + * storage. + */ +public class StoreMessageOperation implements TxnOp +{ + private final MessageStore _messsageStore; + + public StoreMessageOperation(MessageStore messageStore) + { + _messsageStore = messageStore; + } + + public void prepare(StoreContext context) throws AMQException + { + } + + public void undoPrepare() + { + } + + public void commit(StoreContext context) throws AMQException + { + _messsageStore.commitTran(context); + } + + public void rollback(StoreContext context) throws AMQException + { + _messsageStore.abortTran(context); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java new file mode 100644 index 0000000000..647ba66fb4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TransactionalContext.java @@ -0,0 +1,179 @@ +/* + * + * 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.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.StoreContext; + +/** + * TransactionalContext provides a context in which transactional operations on {@link AMQMessage}s are performed. + * Different levels of transactional support for the delivery of messages may be provided by different implementations + * of this interface. + * + * <p/>The fundamental transactional operations that can be performed on a message queue are 'enqueue' and 'dequeue'. + * In this interface, these have been recast as the {@link #messageFullyReceived} and {@link #acknowledgeMessage} + * operations. This interface essentially provides a way to make enqueueing and dequeuing transactional. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities + * <tr><td> Explicitly accept a transaction start notification. + * <tr><td> Commit all pending operations in a transaction. + * <tr><td> Rollback all pending operations in a transaction. + * <tr><td> Deliver a message to a queue as part of a transaction. + * <tr><td> Redeliver a message to a queue as part of a transaction. + * <tr><td> Mark a message as acknowledged as part of a transaction. + * <tr><td> Accept notification that a message has been completely received as part of a transaction. + * <tr><td> Accept notification that a message has been fully processed as part of a transaction. + * <tr><td> Associate a message store context with this transaction context. + * </table> + * + * @todo The 'fullyReceived' and 'messageProcessed' events sit uncomfortably in the responsibilities of a transactional + * context. They are non-transactional operations, used to trigger other side-effects. Consider moving them + * somewhere else, a seperate interface for example. + * + * @todo This transactional context could be written as a wrapper extension to a Queue implementation, that provides + * transactional management of the enqueue and dequeue operations, with added commit/rollback methods. Any + * queue implementation could be made transactional by wrapping it as a transactional queue. This would mean + * that the enqueue/dequeue operations do not need to be recast as deliver/acknowledge operations, which may be + * conceptually neater. + * + * For example: + * <pre> + * public interface Transactional + * { + * public void commit(); + * public void rollback(); + * } + * + * public interface TransactionalQueue<E> extends Transactional, SizeableQueue<E> + * {} + * + * public class Queues + * { + * ... + * // For transactional messaging, take a transactional view onto the queue. + * public static <E> TransactionalQueue<E> getTransactionalQueue(SizeableQueue<E> queue) { ... } + * + * // For non-transactional messaging, take a non-transactional view onto the queue. + * public static <E> TransactionalQueue<E> getNonTransactionalQueue(SizeableQueue<E> queue) { ... } + * } + * </pre> + */ +public interface TransactionalContext +{ + /** + * Explicitly begins the transaction, if it has not already been started. {@link #commit} or {@link #rollback} + * should automatically begin the next transaction in the chain. + * + * @throws AMQException If the transaction cannot be started for any reason. + */ + void beginTranIfNecessary() throws AMQException; + + /** + * Makes all pending operations on the transaction permanent and visible. + * + * @throws AMQException If the transaction cannot be committed for any reason. + */ + void commit() throws AMQException; + + /** + * Erases all pending operations on the transaction. + * + * @throws AMQException If the transaction cannot be committed for any reason. + */ + void rollback() throws AMQException; + + /** + * Delivers the specified message to the specified queue. + * + * <p/>This is an 'enqueue' operation. + * + * @param queue + * @param message The message to deliver + * @throws AMQException If the message cannot be delivered for any reason. + */ + void deliver(final AMQQueue queue, AMQMessage message) throws AMQException; + + /** + * Requeues the specified message entry (message queue pair) + * + * + * @param queueEntry The message,queue pair + * + * @throws AMQException If the message cannot be delivered for any reason. + */ + void requeue(QueueEntry queueEntry) throws AMQException; + + + /** + * Acknowledges a message or many messages as delivered. All messages up to a specified one, may be acknowledged by + * setting the 'multiple' flag. It is also possible for the acknowledged message id to be zero, when the 'multiple' + * flag is set, in which case an acknowledgement up to the latest delivered message should be done. + * + * <p/>This is a 'dequeue' operation. + * + * @param deliveryTag The id of the message to acknowledge, or zero, if using multiple acknowledgement + * up to the latest message. + * @param lastDeliveryTag The latest message delivered. + * @param multiple <tt>true</tt> if all message ids up the acknowledged one or latest delivered, are + * to be acknowledged, <tt>false</tt> otherwise. + * @param unacknowledgedMessageMap The unacknowledged messages in the transaction, to remove the acknowledged message + * from. + * + * @throws AMQException If the message cannot be acknowledged for any reason. + */ + void acknowledgeMessage(long deliveryTag, long lastDeliveryTag, boolean multiple, + UnacknowledgedMessageMap unacknowledgedMessageMap) throws AMQException; + + /** + * Notifies the transactional context that a message has been fully received. The actual message that was received + * is not specified. This event may be used to trigger a process related to the receipt of the message, for example, + * flushing its data to disk. + * + * @param persistent <tt>true</tt> if the received message is persistent, <tt>false</tt> otherwise. + * + * @throws AMQException If the fully received event cannot be processed for any reason. + */ + void messageFullyReceived(boolean persistent) throws AMQException; + + /** + * Notifies the transactional context that a message has been delivered, succesfully or otherwise. The actual + * message that was delivered is not specified. This event may be used to trigger a process related to the + * outcome of the delivery of the message, for example, cleaning up failed deliveries. + * + * @param protocolSession The protocol session of the deliverable message. + * + * @throws AMQException If the message processed event cannot be handled for any reason. + */ + void messageProcessed(AMQProtocolSession protocolSession) throws AMQException; + + /** + * Gets the message store context associated with this transactional context. + * + * @return The message store context associated with this transactional context. + */ + StoreContext getStoreContext(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java new file mode 100644 index 0000000000..46a68b6a23 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnBuffer.java @@ -0,0 +1,109 @@ +/* + * + * 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.txn; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; + +/** Holds a list of TxnOp instance representing transactional operations. */ +public class TxnBuffer +{ + private final List<TxnOp> _ops = new ArrayList<TxnOp>(); + private static final Logger _log = Logger.getLogger(TxnBuffer.class); + + public TxnBuffer() + { + } + + public void commit(StoreContext context) throws AMQException + { + if (_log.isDebugEnabled()) + { + _log.debug("Committing " + _ops.size() + " ops to commit.:" + _ops); + } + + if (prepare(context)) + { + for (TxnOp op : _ops) + { + op.commit(context); + } + } + _ops.clear(); + } + + private boolean prepare(StoreContext context) throws AMQException + { + for (int i = 0; i < _ops.size(); i++) + { + TxnOp op = _ops.get(i); + try + { + op.prepare(context); + } + catch (AMQException e) + { + undoPrepare(i); + throw e; + } + catch (RuntimeException e) + { + undoPrepare(i); + throw e; + } + } + return true; + } + + private void undoPrepare(int lastPrepared) + { + //compensate previously prepared ops + for (int j = 0; j < lastPrepared; j++) + { + _ops.get(j).undoPrepare(); + } + } + + + + public void rollback(StoreContext context) throws AMQException + { + for (TxnOp op : _ops) + { + op.rollback(context); + } + _ops.clear(); + } + + public void enlist(TxnOp op) + { + _ops.add(op); + } + + public void cancel(TxnOp op) + { + _ops.remove(op); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java new file mode 100644 index 0000000000..919c078cf0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TxnOp.java @@ -0,0 +1,55 @@ +/* + * + * 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.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; + +/** + * This provides the abstraction of an individual operation within a + * transaction. It is used by the TxnBuffer class. + */ +public interface TxnOp +{ + /** + * Do the part of the operation that updates persistent state + */ + public void prepare(StoreContext context) throws AMQException; + /** + * Complete the operation started by prepare. Can now update in + * memory state or make netork transfers. + */ + public void commit(StoreContext context) throws AMQException; + /** + * This is not the same as rollback. Unfortunately the use of an + * in memory reference count as a locking mechanism and a test for + * whether a message should be deleted means that as things are, + * handling an acknowledgement unavoidably alters both memory and + * persistent state on prepare. This is needed to 'compensate' or + * undo the in-memory change if the peristent update of later ops + * fails. + */ + public void undoPrepare(); + /** + * Rolls back the operation. + */ + public void rollback(StoreContext context) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java new file mode 100644 index 0000000000..e730e2f3c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java @@ -0,0 +1,131 @@ +/* + * + * 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.util; + +import java.util.Iterator; + +import org.apache.log4j.Logger; + +public class CircularBuffer implements Iterable +{ + + private static final Logger _logger = Logger.getLogger(CircularBuffer.class); + + private final Object[] _log; + private int _size; + private int _index; + + public CircularBuffer(int size) + { + _log = new Object[size]; + } + + public void add(Object o) + { + _log[_index++] = o; + _size = Math.min(_size+1, _log.length); + if(_index >= _log.length) + { + _index = 0; + } + } + + public Object get(int i) + { + if(i >= _log.length) + { + throw new ArrayIndexOutOfBoundsException(i); + } + return _log[index(i)]; + } + + public int size() { + return _size; + } + + public Iterator iterator() + { + return new Iterator() + { + private int i = 0; + + public boolean hasNext() + { + return i < _size; + } + + public Object next() + { + return get(i++); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString() + { + StringBuilder s = new StringBuilder(); + boolean first = true; + for(Object o : this) + { + if(!first) + { + s.append(", "); + } + else + { + first = false; + } + s.append(o); + } + return s.toString(); + } + + public void dump() + { + for(Object o : this) + { + _logger.info(o); + } + } + + int index(int i) + { + return _size == _log.length ? (_index + i) % _log.length : i; + } + + public static void main(String[] artgv) + { + String[] items = new String[]{ + "A","B","C","D","E","F","G","H","I","J","K" + }; + CircularBuffer buffer = new CircularBuffer(5); + for(String s : items) + { + buffer.add(s); + _logger.info(buffer); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java new file mode 100644 index 0000000000..cf5e71a6e2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/ConcurrentLinkedQueueNoSize.java @@ -0,0 +1,38 @@ +/* + * + * 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.util; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public class ConcurrentLinkedQueueNoSize<E> extends ConcurrentLinkedQueue<E> +{ + public int size() + { + if (isEmpty()) + { + return 0; + } + else + { + return 1; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java new file mode 100644 index 0000000000..eda97e0ed2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java @@ -0,0 +1,105 @@ +/* + * + * 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.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * Dynamic proxy that records invocations in a fixed size circular buffer, + * dumping details on hitting an exception. + * <p> + * Useful in debugging. + * <p> + */ +public class LoggingProxy implements InvocationHandler +{ + private final Object _target; + private final CircularBuffer _log; + + public LoggingProxy(Object target, int size) + { + _target = target; + _log = new CircularBuffer(size); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + try + { + entered(method, args); + Object result = method.invoke(_target, args); + returned(method, result); + return result; + } + catch(InvocationTargetException e) + { + dump(); + throw e.getTargetException(); + } + } + + void dump() + { + _log.dump(); + } + + CircularBuffer getBuffer() + { + return _log; + } + + private synchronized void entered(Method method, Object[] args) + { + if (args == null) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() entered"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered"); + } + } + + private synchronized void returned(Method method, Object result) + { + if (method.getReturnType() == Void.TYPE) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result); + } + } + + public Object getProxy(Class... c) + { + return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this); + } + + public int getBufferSize() { + return _log.size(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java new file mode 100644 index 0000000000..eda2d3a94e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/NullApplicationRegistry.java @@ -0,0 +1,86 @@ +/* + * + * 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.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Properties; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.plugins.AllowAll; +import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class NullApplicationRegistry extends ApplicationRegistry +{ + public NullApplicationRegistry() throws ConfigurationException + { + super(new ServerConfiguration(new PropertiesConfiguration())); + } + + public void initialise() throws Exception + { + _logger.info("Initialising NullApplicationRegistry"); + + _configuration.setHousekeepingExpiredMessageCheckPeriod(200); + + Properties users = new Properties(); + + users.put("guest", "guest"); + + _databaseManager = new PropertiesPrincipalDatabaseManager("default", users); + + _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager, AllowAll.FACTORY); + + _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null); + + _managedObjectRegistry = new NoopManagedObjectRegistry(); + _virtualHostRegistry = new VirtualHostRegistry(); + PropertiesConfiguration vhostProps = new PropertiesConfiguration(); + VirtualHostConfiguration hostConfig = new VirtualHostConfiguration("test", vhostProps); + VirtualHost dummyHost = new VirtualHost(hostConfig); + _virtualHostRegistry.registerVirtualHost(dummyHost); + _virtualHostRegistry.setDefaultVirtualHostName("test"); + _pluginManager = new PluginManager(""); + + } + + public Collection<String> getVirtualHostNames() + { + String[] hosts = {"test"}; + return Arrays.asList(hosts); + } +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java new file mode 100644 index 0000000000..f4c81fbbb8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.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.virtualhost;
+
+import java.io.IOException;
+
+import org.apache.qpid.server.management.MBeanAttribute;
+
+/**
+ * The management interface exposed to allow management of an Exchange.
+ * @version 0.1
+ */
+public interface ManagedVirtualHost
+{
+ static final String TYPE = "VirtualHost";
+ static final int VERSION = 1;
+
+ /**
+ * Returns the name of the managed virtualHost.
+ * @return the name of the exchange.
+ * @throws java.io.IOException
+ */
+ @MBeanAttribute(name="Name", description= TYPE + " Name")
+ String getName() throws IOException;
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java new file mode 100644 index 0000000000..0ae0594de8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java @@ -0,0 +1,593 @@ +/* + * + * 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.virtualhost; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.AMQBrokerManagerMBean; +import org.apache.qpid.server.configuration.ExchangeConfiguration; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.ConnectionRegistry; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.Accessable; +import org.apache.qpid.server.security.access.plugins.SimpleXML; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; + +import javax.management.NotCompliantMBeanException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +public class VirtualHost implements Accessable +{ + private static final Logger _logger = Logger.getLogger(VirtualHost.class); + + private final String _name; + + private ConnectionRegistry _connectionRegistry; + + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private MessageStore _messageStore; + + protected VirtualHostMBean _virtualHostMBean; + + private AMQBrokerManagerMBean _brokerMBean; + + private AuthenticationManager _authenticationManager; + + private ACLManager _accessManager; + + private final Timer _houseKeepingTimer; + private VirtualHostConfiguration _configuration; + + public void setAccessableName(String name) + { + _logger.warn("Setting Accessable Name for VirualHost is not allowed. (" + + name + ") ignored remains :" + getAccessableName()); + } + + public String getAccessableName() + { + return _name; + } + + public IConnectionRegistry getConnectionRegistry() + { + return _connectionRegistry; + } + + public VirtualHostConfiguration getConfiguration() + { + return _configuration; + } + + /** + * Abstract MBean class. This has some of the methods implemented from management intrerface for exchanges. Any + * implementaion of an Exchange MBean should extend this class. + */ + public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost + { + public VirtualHostMBean() throws NotCompliantMBeanException + { + super(ManagedVirtualHost.class, ManagedVirtualHost.TYPE, ManagedVirtualHost.VERSION); + } + + public String getObjectInstanceName() + { + return _name.toString(); + } + + public String getName() + { + return _name.toString(); + } + + public VirtualHost getVirtualHost() + { + return VirtualHost.this; + } + + } // End of MBean class + + /** + * Normal Constructor + * + * @param hostConfig + * + * @throws Exception + */ + public VirtualHost(VirtualHostConfiguration hostConfig) throws Exception + { + this(hostConfig, null); + } + + public VirtualHost(VirtualHostConfiguration hostConfig, MessageStore store) throws Exception + { + _configuration = hostConfig; + _name = hostConfig.getName(); + + if (_name == null || _name.length() == 0) + { + throw new IllegalArgumentException("Illegal name (" + _name + ") for virtualhost."); + } + + _virtualHostMBean = new VirtualHostMBean(); + + _connectionRegistry = new ConnectionRegistry(this); + + _houseKeepingTimer = new Timer("Queue-housekeeping-" + _name, true); + + _queueRegistry = new DefaultQueueRegistry(this); + + _exchangeFactory = new DefaultExchangeFactory(this); + _exchangeFactory.initialise(hostConfig); + + _exchangeRegistry = new DefaultExchangeRegistry(this); + + //Create a temporary RT to store the durable entries from the config file + // so we can replay them in to the real _RT after it has been loaded. + /// This should be removed after the _RT has been fully split from the the TL + + StartupRoutingTable configFileRT = new StartupRoutingTable(); + + _messageStore = configFileRT; + + // This needs to be after the RT has been defined as it creates the default durable exchanges. + _exchangeRegistry.initialise(); + initialiseModel(hostConfig); + + if (store != null) + { + _messageStore = store; + } + else + { + if (hostConfig == null) + { + throw new IllegalAccessException("HostConfig and MessageStore cannot be null"); + } + initialiseMessageStore(hostConfig); + } + + //Now that the RT has been initialised loop through the persistent queues/exchanges created from the config + // file and write them in to the new routing Table. + for (StartupRoutingTable.CreateQueueTuple cqt : configFileRT.queue) + { + _messageStore.createQueue(cqt.queue, cqt.arguments); + } + + for (Exchange exchange : configFileRT.exchange) + { + _messageStore.createExchange(exchange); + } + + for (StartupRoutingTable.CreateBindingTuple cbt : configFileRT.bindings) + { + _messageStore.bindQueue(cbt.exchange, cbt.routingKey, cbt.queue, cbt.arguments); + } + + _authenticationManager = new PrincipalDatabaseAuthenticationManager(_name, hostConfig); + + _accessManager = ApplicationRegistry.getInstance().getAccessManager(); + _accessManager.configureHostPlugins(hostConfig.getSecurityConfiguration()); + + _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean); + _brokerMBean.register(); + initialiseHouseKeeping(hostConfig.getHousekeepingExpiredMessageCheckPeriod()); + } + + private void initialiseHouseKeeping(long period) + { + /* add a timer task to iterate over queues, cleaning expired messages from queues with no consumers */ + if (period != 0L) + { + class RemoveExpiredMessagesTask extends TimerTask + { + public void run() + { + for (AMQQueue q : _queueRegistry.getQueues()) + { + + try + { + q.checkMessageStatus(); + } + catch (AMQException e) + { + _logger.error("Exception in housekeeping for queue: " + q.getName().toString(), e); + throw new RuntimeException(e); + } + } + } + } + + _houseKeepingTimer.scheduleAtFixedRate(new RemoveExpiredMessagesTask(), + period / 2, + period); + } + } + + private void initialiseMessageStore(VirtualHostConfiguration hostConfig) throws Exception + { + String messageStoreClass = hostConfig.getMessageStoreClass(); + + Class clazz = Class.forName(messageStoreClass); + Object o = clazz.newInstance(); + + if (!(o instanceof MessageStore)) + { + throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz + + " does not."); + } + MessageStore messageStore = (MessageStore) o; + messageStore.configure(this, "store", hostConfig); + _messageStore = messageStore; + } + + private void initialiseModel(VirtualHostConfiguration config) throws ConfigurationException, AMQException + { + _logger.debug("Loading configuration for virtualhost: " + config.getName()); + + List exchangeNames = config.getExchanges(); + + for (Object exchangeNameObj : exchangeNames) + { + String exchangeName = String.valueOf(exchangeNameObj); + configureExchange(config.getExchangeConfiguration(exchangeName)); + } + + String[] queueNames = config.getQueueNames(); + + for (Object queueNameObj : queueNames) + { + String queueName = String.valueOf(queueNameObj); + configureQueue(config.getQueueConfiguration(queueName)); + } + } + + private void configureExchange(ExchangeConfiguration exchangeConfiguration) throws AMQException + { + AMQShortString exchangeName = new AMQShortString(exchangeConfiguration.getName()); + + Exchange exchange; + exchange = _exchangeRegistry.getExchange(exchangeName); + if (exchange == null) + { + + AMQShortString type = new AMQShortString(exchangeConfiguration.getType()); + boolean durable = exchangeConfiguration.getDurable(); + boolean autodelete = exchangeConfiguration.getAutoDelete(); + + Exchange newExchange = _exchangeFactory.createExchange(exchangeName, type, durable, autodelete, 0); + _exchangeRegistry.registerExchange(newExchange); + } + } + + private void configureQueue(QueueConfiguration queueConfiguration) throws AMQException, ConfigurationException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueConfiguration, this); + + if (queue.isDurable()) + { + _messageStore.createQueue(queue); + } + + String exchangeName = queueConfiguration.getExchange(); + + Exchange exchange = _exchangeRegistry.getExchange(exchangeName == null ? null : new AMQShortString(exchangeName)); + + if (exchange == null) + { + exchange = _exchangeRegistry.getDefaultExchange(); + } + + if (exchange == null) + { + throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + exchangeName); + } + + List routingKeys = queueConfiguration.getRoutingKeys(); + if (routingKeys == null || routingKeys.isEmpty()) + { + routingKeys = Collections.singletonList(queue.getName()); + } + + for (Object routingKeyNameObj : routingKeys) + { + AMQShortString routingKey = new AMQShortString(String.valueOf(routingKeyNameObj)); + if (_logger.isInfoEnabled()) + { + _logger.info("Binding queue:" + queue + " with routing key '" + routingKey + "' to exchange:" + this); + } + queue.bind(exchange, routingKey, null); + } + + if (exchange != _exchangeRegistry.getDefaultExchange()) + { + queue.bind(_exchangeRegistry.getDefaultExchange(), queue.getName(), null); + } + } + + public String getName() + { + return _name; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ApplicationRegistry getApplicationRegistry() + { + throw new UnsupportedOperationException(); + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public ACLManager getAccessManager() + { + return _accessManager; + } + + public void close() throws Exception + { + + //Stop Connections + _connectionRegistry.close(); + + //Stop the Queues processing + if (_queueRegistry != null) + { + for (AMQQueue queue : _queueRegistry.getQueues()) + { + queue.stop(); + } + } + + //Stop Housekeeping + if (_houseKeepingTimer != null) + { + _houseKeepingTimer.cancel(); + } + + //Close MessageStore + if (_messageStore != null) + { + _messageStore.close(); + } + } + + public ManagedObject getBrokerMBean() + { + return _brokerMBean; + } + + public ManagedObject getManagedObject() + { + return _virtualHostMBean; + } + + /** + * Temporary Startup RT class to record the creation of persistent queues / exchanges. + * + * + * This is so we can replay the creation of queues/exchanges in to the real _RT after it has been loaded. + * This should be removed after the _RT has been fully split from the the TL + */ + private class StartupRoutingTable implements MessageStore + { + public List<Exchange> exchange = new LinkedList<Exchange>(); + public List<CreateQueueTuple> queue = new LinkedList<CreateQueueTuple>(); + public List<CreateBindingTuple> bindings = new LinkedList<CreateBindingTuple>(); + + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + { + } + + public void close() throws Exception + { + } + + public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void createExchange(Exchange exchange) throws AMQException + { + if (exchange.isDurable()) + { + this.exchange.add(exchange); + } + } + + public void removeExchange(Exchange exchange) throws AMQException + { + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + if (exchange.isDurable() && queue.isDurable()) + { + bindings.add(new CreateBindingTuple(exchange, routingKey, queue, args)); + } + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + } + + public void createQueue(AMQQueue queue) throws AMQException + { + createQueue(queue, null); + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException + { + if (queue.isDurable()) + { + this.queue.add(new CreateQueueTuple(queue, arguments)); + } + } + + public void removeQueue(AMQQueue queue) throws AMQException + { + } + + public void enqueueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void beginTran(StoreContext context) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void commitTran(StoreContext context) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void abortTran(StoreContext context) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean inTran(StoreContext context) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public Long getNewMessageId() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isPersistent() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + private class CreateQueueTuple + { + public AMQQueue queue; + public FieldTable arguments; + + public CreateQueueTuple(AMQQueue queue, FieldTable arguments) + { + this.queue = queue; + this.arguments = arguments; + } + } + + private class CreateBindingTuple + { + public AMQQueue queue; + public FieldTable arguments; + public Exchange exchange; + public AMQShortString routingKey; + + public CreateBindingTuple(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + { + this.exchange = exchange; + this.routingKey = routingKey; + this.queue = queue; + arguments = args; + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java new file mode 100644 index 0000000000..27917fac8a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java @@ -0,0 +1,70 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.virtualhost;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class VirtualHostRegistry
+{
+ private final Map<String, VirtualHost> _registry = new ConcurrentHashMap<String,VirtualHost>();
+
+
+ private String _defaultVirtualHostName;
+
+ public synchronized void registerVirtualHost(VirtualHost host) throws Exception
+ {
+ if(_registry.containsKey(host.getName()))
+ {
+ throw new Exception("Virtual Host with name " + host.getName() + " already registered.");
+ }
+ _registry.put(host.getName(),host);
+ }
+
+ public VirtualHost getVirtualHost(String name)
+ {
+ if(name == null || name.trim().length() == 0 )
+ {
+ name = getDefaultVirtualHostName();
+ }
+
+ return _registry.get(name);
+ }
+
+ private String getDefaultVirtualHostName()
+ {
+ return _defaultVirtualHostName;
+ }
+
+ public void setDefaultVirtualHostName(String defaultVirtualHostName)
+ {
+ _defaultVirtualHostName = defaultVirtualHostName;
+ }
+
+
+ public Collection<VirtualHost> getVirtualHosts()
+ {
+ return new ArrayList<VirtualHost>(_registry.values());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java new file mode 100644 index 0000000000..faa7b85d58 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java @@ -0,0 +1,652 @@ +/* + * 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.tools.messagestore; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.configuration.Configuration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.tools.messagestore.commands.Clear; +import org.apache.qpid.tools.messagestore.commands.Command; +import org.apache.qpid.tools.messagestore.commands.Copy; +import org.apache.qpid.tools.messagestore.commands.Dump; +import org.apache.qpid.tools.messagestore.commands.Help; +import org.apache.qpid.tools.messagestore.commands.List; +import org.apache.qpid.tools.messagestore.commands.Load; +import org.apache.qpid.tools.messagestore.commands.Quit; +import org.apache.qpid.tools.messagestore.commands.Select; +import org.apache.qpid.tools.messagestore.commands.Show; +import org.apache.qpid.tools.messagestore.commands.Move; +import org.apache.qpid.tools.messagestore.commands.Purge; +import org.apache.qpid.tools.utils.CommandParser; +import org.apache.qpid.tools.utils.Console; +import org.apache.qpid.tools.utils.SimpleCommandParser; +import org.apache.qpid.tools.utils.SimpleConsole; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * MessageStoreTool. + */ +public class MessageStoreTool +{ + /** Text outputted at the start of each console.*/ + private static final String BOILER_PLATE = "MessageStoreTool - for examining Persistent Qpid Broker MessageStore instances"; + + /** I/O Wrapper. */ + protected Console _console; + + /** Batch mode flag. */ + protected boolean _batchMode; + + /** Internal State object. */ + private State _state = new State(); + + private HashMap<String, Command> _commands = new HashMap<String, Command>(); + + /** SLF4J Logger. */ + private static Logger _devlog = LoggerFactory.getLogger(MessageStoreTool.class); + + /** Loaded configuration file. */ + private Configuration _config; + + /** Control used for main run loop. */ + private boolean _running = true; + private boolean _initialised = false; + + //---------------------------------------------------------------------------------------------------/ + + public static void main(String[] args) throws Configuration.InitException + { + + MessageStoreTool tool = new MessageStoreTool(args); + + tool.start(); + } + + + public MessageStoreTool(String[] args) throws Configuration.InitException + { + this(args, System.in, System.out); + } + + public MessageStoreTool(String[] args, InputStream in, OutputStream out) throws Configuration.InitException + { + BufferedReader consoleReader = new BufferedReader(new InputStreamReader(in)); + BufferedWriter consoleWriter = new BufferedWriter(new OutputStreamWriter(out)); + + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook(this))); + _batchMode = false; + + _console = new SimpleConsole(consoleWriter, consoleReader); + + _config = new Configuration(); + + setOptions(); + _config.processCommandline(args); + } + + + private void setOptions() + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = + OptionBuilder.withArgName("file").hasArg() + .withDescription("use given configuration file By " + + "default looks for a file named " + + Configuration.DEFAULT_CONFIG_FILE + " in " + Configuration.QPID_HOME) + .withLongOpt("config") + .create("c"); + + _config.setOption(help); + _config.setOption(version); + _config.setOption(configFile); + } + + public State getState() + { + return _state; + } + + public Map<String, Command> getCommands() + { + return _commands; + } + + public void setConfigurationFile(String configfile) throws Configuration.InitException + { + _config.loadConfig(new File(configfile)); + setup(); + } + + public Console getConsole() + { + return _console; + } + + public void setConsole(Console console) + { + _console = console; + } + + /** + * Simple ShutdownHook to cleanly shutdown the databases + */ + class ShutdownHook implements Runnable + { + MessageStoreTool _tool; + + ShutdownHook(MessageStoreTool messageStoreTool) + { + _tool = messageStoreTool; + } + + public void run() + { + _tool.quit(); + } + } + + public void quit() + { + _running = false; + + if (_initialised) + { + ApplicationRegistry.remove(1); + } + + _console.println("...exiting"); + + _console.close(); + } + + public void setBatchMode(boolean batchmode) + { + _batchMode = batchmode; + } + + /** + * Main loop + */ + protected void start() + { + setup(); + + if (!_initialised) + { + System.exit(1); + } + + _console.println(""); + + _console.println(BOILER_PLATE); + + runCLI(); + } + + private void setup() + { + loadDefaultVirtualHosts(); + + loadCommands(); + + _state.clearAll(); + } + + private void loadCommands() + { + _commands.clear(); + //todo Dynamically load the classes that exis in com.redhat.etp.qpid.commands + _commands.put("close", new Clear(this)); + _commands.put("copy", new Copy(this)); + _commands.put("dump", new Dump(this)); + _commands.put("help", new Help(this)); + _commands.put("list", new List(this)); + _commands.put("load", new Load(this)); + _commands.put("move", new Move(this)); + _commands.put("purge", new Purge(this)); + _commands.put("quit", new Quit(this)); + _commands.put("select", new Select(this)); + _commands.put("show", new Show(this)); + } + + private void loadDefaultVirtualHosts() + { + final File configFile = _config.getConfigFile(); + + loadVirtualHosts(configFile); + } + + private void loadVirtualHosts(File configFile) + { + + if (!configFile.exists()) + { + _devlog.error("Config file not found:" + configFile.getAbsolutePath()); + return; + } + else + { + _devlog.debug("using config file :" + configFile.getAbsolutePath()); + } + + try + { + ConfigurationFileApplicationRegistry registry = new ConfigurationFileApplicationRegistry(configFile); + + ApplicationRegistry.remove(1); + + ApplicationRegistry.initialise(registry); + + checkMessageStores(); + _initialised = true; + } + catch (ConfigurationException e) + { + _console.println("Unable to load configuration due to configuration error: " + e.getMessage()); + e.printStackTrace(); + } + catch (Exception e) + { + _console.println("Unable to load configuration due to: " + e.getMessage()); + e.printStackTrace(); + } + + + } + + private void checkMessageStores() + { + Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts(); + + boolean warning = false; + for (VirtualHost vhost : vhosts) + { + if (vhost.getMessageStore() instanceof MemoryMessageStore) + { + _console.println("WARNING: Virtualhost '" + vhost.getName() + "' is using a MemoryMessageStore. " + + "Changes will not persist."); + warning = true; + } + } + + if (warning) + { + _console.println(""); + _console.println("Please ensure you are using the correct config file currently using '" + + _config.getConfigFile().getAbsolutePath() + "'"); + _console.println("New config file can be specifed by 'load <config file>' or -c on the commandline."); + _console.println(""); + } + } + + private void runCLI() + { + while (_running) + { + if (!_batchMode) + { + printPrompt(); + } + + String[] args = _console.readCommand(); + + while (args != null) + { + exec(args); + + if (_running) + { + if (!_batchMode) + { + printPrompt(); + } + + args = _console.readCommand(); + } + } + } + } + + private void printPrompt() + { + _console.print(prompt()); + } + + + /** + * Execute a script (batch mode). + * + * @param script The file script + */ + protected void runScripts(String script) + { + //Store Current State + boolean oldBatch = _batchMode; + CommandParser oldParser = _console.getCommandParser(); + setBatchMode(true); + + try + { + _devlog.debug("Running script '" + script + "'"); + + _console.setCommandParser(new SimpleCommandParser(new BufferedReader(new FileReader(script)))); + + start(); + } + catch (java.io.FileNotFoundException e) + { + _devlog.error("Script not found: '" + script + "' due to:" + e.getMessage()); + } + + //Restore previous state + _console.setCommandParser(oldParser); + setBatchMode(oldBatch); + } + + public String prompt() + { + String state = _state.toString(); + if (state != null && state.length() != 0) + { + return state + ":bdb$ "; + } + else + { + return "bdb$ "; + } + } + + /** + * Execute the command. + * + * @param args [command, arg0, arg1...]. + */ + protected void exec(String[] args) + { + // Comment lines start with a # + if (args.length == 0 || args[0].startsWith("#")) + { + return; + } + + final String command = args[0]; + + Command cmd = _commands.get(command); + + if (cmd == null) + { + _console.println("Command not understood: " + command); + } + else + { + cmd.execute(args); + } + } + + + /** + * Displays usage info. + */ + protected static void help() + { + System.out.println(BOILER_PLATE); + System.out.println("Usage: java " + MessageStoreTool.class + " [Options]"); + System.out.println(" [-c <broker config file>] : Defaults to \"$QPID_HOME/etc/config.xml\""); + } + + + /** + * This class is used to store the current state of the tool. + * + * This is then interrogated by the various commands to augment their behaviour. + * + * + */ + public class State + { + private VirtualHost _vhost = null; + private AMQQueue _queue = null; + private Exchange _exchange = null; + private java.util.List<Long> _msgids = null; + + public State() + { + } + + public void setQueue(AMQQueue queue) + { + _queue = queue; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void setVhost(VirtualHost vhost) + { + _vhost = vhost; + } + + public VirtualHost getVhost() + { + return _vhost; + } + + public Exchange getExchange() + { + return _exchange; + } + + public void setExchange(Exchange exchange) + { + _exchange = exchange; + } + + public String toString() + { + StringBuilder status = new StringBuilder(); + + if (_vhost != null) + { + status.append(_vhost.getName()); + + if (_exchange != null) + { + status.append("["); + status.append(_exchange.getName()); + status.append("]"); + + if (_queue != null) + { + status.append("->'"); + status.append(_queue.getName()); + status.append("'"); + + if (_msgids != null) + { + status.append(printMessages()); + } + } + } + } + + return status.toString(); + } + + + public String printMessages() + { + StringBuilder sb = new StringBuilder(); + + Long previous = null; + + Long start = null; + for (Long id : _msgids) + { + if (previous != null) + { + if (id == previous + 1) + { + if (start == null) + { + start = previous; + } + } + else + { + if (start != null) + { + sb.append(","); + sb.append(start); + sb.append("-"); + sb.append(id); + start = null; + } + else + { + sb.append(","); + sb.append(previous); + } + } + } + + previous = id; + } + + if (start != null) + { + sb.append(","); + sb.append(start); + sb.append("-"); + sb.append(_msgids.get(_msgids.size() - 1)); + } + else + { + sb.append(","); + sb.append(previous); + } + + // surround list in () + sb.replace(0, 1, "("); + sb.append(")"); + return sb.toString(); + } + + public void clearAll() + { + _vhost = null; + clearExchange(); + } + + public void clearExchange() + { + _exchange = null; + clearQueue(); + } + + public void clearQueue() + { + _queue = null; + clearMessages(); + } + + public void clearMessages() + { + _msgids = null; + } + + /** + * A common location to provide parsing of the message id string + * utilised by a number of the commands. + * The String is comma separated list of ids that can be individual ids + * or a range (4-10) + * + * @param msgString string of msg ids to parse 1,2,4-10 + */ + public void setMessages(String msgString) + { + StringTokenizer tok = new StringTokenizer(msgString, ","); + + if (tok.hasMoreTokens()) + { + _msgids = new LinkedList<Long>(); + } + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + if (next.contains("-")) + { + Long start = Long.parseLong(next.substring(0, next.indexOf("-"))); + Long end = Long.parseLong(next.substring(next.indexOf("-") + 1)); + + if (end >= start) + { + for (long l = start; l <= end; l++) + { + _msgids.add(l); + } + } + } + else + { + _msgids.add(Long.parseLong(next)); + } + } + + } + + public void setMessages(java.util.List<Long> msgids) + { + _msgids = msgids; + } + + public java.util.List<Long> getMessages() + { + return _msgids; + } + }//Class State + +}//Class MessageStoreTool diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java new file mode 100644 index 0000000000..5444197cb4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +public abstract class AbstractCommand implements Command +{ + protected Console _console; + protected MessageStoreTool _tool; + + public AbstractCommand(MessageStoreTool tool) + { + _console = tool.getConsole(); + _tool = tool; + } + + public void setOutput(Console out) + { + _console = out; + } + + protected void commandError(String message, String[] args) + { + _console.print(getCommand() + " : " + message); + + if (args != null) + { + for (int i = 1; i < args.length; i++) + { + _console.print(args[i]); + } + } + _console.println(""); + _console.println(help()); + } + + + public abstract String help(); + + public abstract String usage(); + + public abstract String getCommand(); + + + public abstract void execute(String... args); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java new file mode 100644 index 0000000000..b0006b3fe6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java @@ -0,0 +1,85 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Clear extends AbstractCommand +{ + public Clear(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Clears any selection."; + } + + public String usage() + { + return "clear [ all | virtualhost | exchange | queue | msgs ]"; + } + + public String getCommand() + { + return "clear"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length < 1) + { + doClose("all"); + } + else + { + doClose(args[1]); + } + } + + private void doClose(String type) + { + if (type.equals("virtualhost") + || type.equals("all")) + { + _tool.getState().clearAll(); + } + + if (type.equals("exchange")) + { + _tool.getState().clearExchange(); + } + + if (type.equals("queue")) + { + _tool.getState().clearQueue(); + } + + if (type.equals("msgs")) + { + _tool.getState().clearMessages(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java new file mode 100644 index 0000000000..bfa775a34a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java @@ -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. + * + * + */ +package org.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.utils.Console; + +public interface Command +{ + public void setOutput(Console out); + + public String help(); + + public abstract String usage(); + + String getCommand(); + + public void execute(String... args); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java new file mode 100644 index 0000000000..0869d9a497 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java @@ -0,0 +1,55 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.server.queue.AMQQueue; + +public class Copy extends Move +{ + public Copy(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Copy messages between queues.";/*\n" + + "The currently selected message set will be copied to the specifed queue.\n" + + "Alternatively the values can be provided on the command line."; */ + } + + public String usage() + { + return "copy to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "copy"; + } + + protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue) + { + fromQueue.copyMessagesToAnotherQueue(start, end, toQueue.getName().toString(), _storeContext); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java new file mode 100644 index 0000000000..731f6140f9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java @@ -0,0 +1,302 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.commons.codec.binary.Hex; +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntryImpl; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class Dump extends Show +{ + private static final int LINE_SIZE = 8; + private static final String DEFAULT_ENCODING = "utf-8"; + private static final boolean SPACE_BYTES = true; + private static final String BYTE_SPACER = " "; + private static final String NON_PRINTING_ASCII_CHAR = "?"; + + protected boolean _content = true; + + public Dump(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Dump selected message content. Default: show=content"; + } + + public String usage() + { + return getCommand() + " [show=[all],[msgheaders],[_amqHeaders],[routing],[content]] [id=<msgid e.g. 1,2,4-10>]"; + } + + public String getCommand() + { + return "dump"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("show=")) + { + _content = arg.contains("content") || arg.contains("all"); + } + } + + parseArgs(args); + } + + performShow(); + } + + + protected List<List> createMessageData(java.util.List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting, + boolean showMessageHeaders) + { + + List<List> display = new LinkedList<List>(); + + List<String> hex = new LinkedList<String>(); + List<String> ascii = new LinkedList<String>(); + display.add(hex); + display.add(ascii); + + for (QueueEntry entry : messages) + { + AMQMessage msg = entry.getMessage(); + if (!includeMsg(msg, msgids)) + { + continue; + } + + //Add divider between messages + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + // Show general message information + hex.add(Show.Columns.ID.name()); + ascii.add(msg.getMessageId().toString()); + + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + if (showRouting) + { + addShowInformation(hex, ascii, msg, "Routing Details", true, false, false); + } + if (showHeaders) + { + addShowInformation(hex, ascii, msg, "Headers", false, true, false); + } + if (showMessageHeaders) + { + addShowInformation(hex, ascii, msg, null, false, false, true); + } + + // Add Content Body seciont + hex.add("Content Body"); + ascii.add(""); + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + Iterator bodies = msg.getContentBodyIterator(); + if (bodies.hasNext()) + { + + hex.add("Hex"); + hex.add(Console.ROW_DIVIDER); + + + ascii.add("ASCII"); + ascii.add(Console.ROW_DIVIDER); + + while (bodies.hasNext()) + { + ContentChunk chunk = (ContentChunk) bodies.next(); + + //Duplicate so we don't destroy original data :) + ByteBuffer hexBuffer = chunk.getData().duplicate(); + + ByteBuffer charBuffer = hexBuffer.duplicate(); + + Hex hexencoder = new Hex(); + + while (hexBuffer.hasRemaining()) + { + byte[] line = new byte[LINE_SIZE]; + + int bufsize = hexBuffer.remaining(); + if (bufsize < LINE_SIZE) + { + hexBuffer.get(line, 0, bufsize); + } + else + { + bufsize = line.length; + hexBuffer.get(line); + } + + byte[] encoded = hexencoder.encode(line); + + try + { + String encStr = new String(encoded, 0, bufsize * 2, DEFAULT_ENCODING); + String hexLine = ""; + + int strKength = encStr.length(); + for (int c = 0; c < strKength; c++) + { + hexLine += encStr.charAt(c); + + if (c % 2 == 1 && SPACE_BYTES) + { + hexLine += BYTE_SPACER; + } + } + + hex.add(hexLine); + } + catch (UnsupportedEncodingException e) + { + _console.println(e.getMessage()); + return null; + } + } + + while (charBuffer.hasRemaining()) + { + String asciiLine = ""; + + for (int pos = 0; pos < LINE_SIZE; pos++) + { + if (charBuffer.hasRemaining()) + { + byte ch = charBuffer.get(); + + if (isPrintable(ch)) + { + asciiLine += (char) ch; + } + else + { + asciiLine += NON_PRINTING_ASCII_CHAR; + } + + if (SPACE_BYTES) + { + asciiLine += BYTE_SPACER; + } + } + else + { + break; + } + } + + ascii.add(asciiLine); + } + } + } + else + { + List<String> result = new LinkedList<String>(); + + display.add(result); + result.add("No ContentBodies"); + } + } + + // if hex is empty then we have no data to display + if (hex.size() == 0) + { + return null; + } + + return display; + } + + private void addShowInformation(List<String> column1, List<String> column2, AMQMessage msg, + String title, boolean routing, boolean headers, boolean messageHeaders) + { + List<QueueEntry> single = new LinkedList<QueueEntry>(); + single.add(new QueueEntryImpl(null,msg, Long.MIN_VALUE)); + + List<List> routingData = super.createMessageData(null, single, headers, routing, messageHeaders); + + //Reformat data + if (title != null) + { + column1.add(title); + column2.add(""); + column1.add(Console.ROW_DIVIDER); + column2.add(Console.ROW_DIVIDER); + } + + // look at all columns in the routing Data + for (List item : routingData) + { + // the item should be: + // Title + // *divider + // value + // otherwise we can't reason about the correct value + if (item.size() == 3) + { + //Filter out the columns we are not interested in. + + String columnName = item.get(0).toString(); + + if (!(columnName.equals(Show.Columns.ID.name()) + || columnName.equals(Show.Columns.Size.name()))) + { + column1.add(columnName); + column2.add(item.get(2).toString()); + } + } + } + column1.add(Console.ROW_DIVIDER); + column2.add(Console.ROW_DIVIDER); + } + + private boolean isPrintable(byte c) + { + return c > 31 && c < 127; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java new file mode 100644 index 0000000000..0f9546541b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java @@ -0,0 +1,98 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.LinkedList; +import java.util.Map; + +public class Help extends AbstractCommand +{ + public Help(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Provides detailed help on commands."; + } + + public String getCommand() + { + return "help"; + } + + public String usage() + { + return "help [<command>]"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 1) + { + Command command = _tool.getCommands().get(args[1]); + if (command != null) + { + _console.println(command.help()); + _console.println("Usage:" + command.usage()); + } + else + { + commandError("Command not found: ", args); + } + } + else + { + java.util.List<java.util.List> data = new LinkedList<java.util.List>(); + + java.util.List<String> commandName = new LinkedList<String>(); + java.util.List<String> commandDescription = new LinkedList<String>(); + + data.add(commandName); + data.add(commandDescription); + + //Set up Headers + commandName.add("Command"); + commandDescription.add("Description"); + + commandName.add(Console.ROW_DIVIDER); + commandDescription.add(Console.ROW_DIVIDER); + + //Add current Commands with descriptions + Map<String, Command> commands = _tool.getCommands(); + + for (Command command : commands.values()) + { + commandName.add(command.getCommand()); + commandDescription.add(command.help()); + } + + _console.printMap("Available Commands", data); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java new file mode 100644 index 0000000000..df8b59ec19 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java @@ -0,0 +1,314 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.Collection; +import java.util.LinkedList; + +public class List extends AbstractCommand +{ + + public List(MessageStoreTool tool) + { + super(tool); + } + + public void setOutput(Console out) + { + _console = out; + } + + public String help() + { + return "list available items."; + } + + public String usage() + { + return "list queues [<exchange>] | exchanges | bindings [<exchange>] | all"; + } + + public String getCommand() + { + return "list"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 1) + { + if ((args[1].equals("exchanges")) + || (args[1].equals("queues")) + || (args[1].equals("bindings")) + || (args[1].equals("all"))) + { + if (args.length == 2) + { + doList(args[1]); + } + else if (args.length == 3) + { + doList(args[1], args[2]); + } + } + else + { + commandError("Unknown options. ", args); + } + } + else if (args.length < 2) + { + doList("all"); + } + else + { + doList(args[1]); + } + } + + private void doList(String... listItem) + { + if (_tool.getState().getVhost() == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + listVirtualHosts(); + return; + } + + VirtualHost vhost = _tool.getState().getVhost(); + + java.util.List<String> data = null; + + if (listItem[0].equals("queues")) + { + if (listItem.length > 1) + { + data = listQueues(vhost, new AMQShortString(listItem[1])); + } + else + { + Exchange exchange = _tool.getState().getExchange(); + data = listQueues(vhost, exchange); + } + } + + if (listItem[0].equals("exchanges")) + { + data = listExchanges(vhost); + } + + if (listItem[0].equals("bindings")) + { + + if (listItem.length > 1) + { + data = listBindings(vhost, new AMQShortString(listItem[1])); + } + else + { + Exchange exchange = _tool.getState().getExchange(); + + data = listBindings(vhost, exchange); + } + } + + if (data != null) + { + if (data.size() == 1) + { + _console.println("No '" + listItem[0] + "' to display,"); + } + else + { + _console.displayList(true, data.toArray(new String[0])); + } + } + + + if (listItem[0].equals("all")) + { + + boolean displayed = false; + Exchange exchange = _tool.getState().getExchange(); + + //Do the display here for each one so that they are pretty printed + data = listQueues(vhost, exchange); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + + if (exchange == null) + { + data = listExchanges(vhost); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + } + + data = listBindings(vhost, exchange); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + + if (!displayed) + { + _console.println("Nothing to list"); + } + } + } + + private void listVirtualHosts() + { + Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance() + .getVirtualHostRegistry().getVirtualHosts(); + + String[] data = new String[vhosts.size() + 1]; + + data[0] = "Available VirtualHosts"; + + int index = 1; + for (VirtualHost vhost : vhosts) + { + data[index] = vhost.getName(); + index++; + } + + _console.displayList(true, data); + } + + private java.util.List<String> listBindings(VirtualHost vhost, AMQShortString exchangeName) + { + return listBindings(vhost, vhost.getExchangeRegistry().getExchange(exchangeName)); + } + + private java.util.List<String> listBindings(VirtualHost vhost, Exchange exchange) + { + Collection<AMQShortString> queues = vhost.getQueueRegistry().getQueueNames(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Current Bindings"); + + for (AMQShortString queue : queues) + { + if (exchange != null) + { + if (exchange.isBound(queue)) + { + data.add(queue.toString()); + } + } + else + { + data.add(queue.toString()); + } + } + + return data; + } + + private java.util.List<String> listExchanges(VirtualHost vhost) + { + Collection<AMQShortString> queues = vhost.getExchangeRegistry().getExchangeNames(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Available Exchanges"); + + for (AMQShortString queue : queues) + { + data.add(queue.toString()); + } + + return data; + } + + private java.util.List<String> listQueues(VirtualHost vhost, AMQShortString exchangeName) + { + return listQueues(vhost, vhost.getExchangeRegistry().getExchange(exchangeName)); + } + + private java.util.List<String> listQueues(VirtualHost vhost, Exchange exchange) + { + Collection<AMQQueue> queues = vhost.getQueueRegistry().getQueues(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Available Queues"); + + for (AMQQueue queue : queues) + { + if (exchange != null) + { + if (exchange.isBound(queue)) + { + data.add(queue.getName().toString()); + } + } + else + { + data.add(queue.getName().toString()); + } + } + + if (exchange != null) + { + if (queues.size() == 1) + { + return null; + } + } + + return data; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java new file mode 100644 index 0000000000..244a311c30 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java @@ -0,0 +1,94 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.configuration.Configuration; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Load extends AbstractCommand +{ + public Load(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Loads specified broker configuration file."; + } + + public String usage() + { + return "load <configuration file>"; + } + + public String getCommand() + { + return "load"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 2) + { + _console.print("load " + args[1] + ": additional options not understood:"); + for (int i = 2; i < args.length; i++) + { + _console.print(args[i] + " "); + } + _console.println(""); + } + else if (args.length < 2) + { + _console.println("Enter Configuration file."); + String input = _console.readln(); + if (input != null) + { + doLoad(input); + } + else + { + _console.println("Did not recognise config file."); + } + } + else + { + doLoad(args[1]); + } + } + + private void doLoad(String configfile) + { + _console.println("Loading Configuration:" + configfile); + + try + { + _tool.setConfigurationFile(configfile); + } + catch (Configuration.InitException e) + { + _console.println("Unable to open config file due to: '" + e.getMessage() + "'"); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java new file mode 100644 index 0000000000..a8dd58ca83 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java @@ -0,0 +1,206 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.QueueEntryImpl; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +import java.util.LinkedList; +import java.util.List; + +public class Move extends AbstractCommand +{ + + /** + * Since the Coopy command is not associated with a real channel we can safely create our own store context + * for use in the few methods that require one. + */ + protected StoreContext _storeContext = new StoreContext(); + + public Move(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Move messages between queues.";/*\n" + + "The currently selected message set will be moved to the specifed queue.\n" + + "Alternatively the values can be provided on the command line.";*/ + } + + public String usage() + { + return "move to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "move"; + } + + public void execute(String... args) + { + AMQQueue toQueue = null; + AMQQueue fromQueue = _tool.getState().getQueue(); + java.util.List<Long> msgids = _tool.getState().getMessages(); + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("to=")) + { + String queueName = arg.substring(arg.indexOf("=") + 1); + toQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + } + + if (arg.startsWith("from=")) + { + String queueName = arg.substring(arg.indexOf("=") + 1); + fromQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + } + + if (arg.startsWith("msgids=")) + { + String msgidStr = arg.substring(arg.indexOf("=") + 1); + + // Record the current message selection + java.util.List<Long> currentIDs = _tool.getState().getMessages(); + + // Use the ToolState class to perform the messasge parsing + _tool.getState().setMessages(msgidStr); + msgids = _tool.getState().getMessages(); + + // Reset the original selection of messages + _tool.getState().setMessages(currentIDs); + } + } + } + + if (!checkRequirements(fromQueue, toQueue, msgids)) + { + return; + } + + processIDs(fromQueue, toQueue, msgids); + } + + private void processIDs(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids) + { + Long previous = null; + Long start = null; + + if (msgids == null) + { + msgids = allMessageIDs(fromQueue); + } + + if (msgids == null || msgids.size() == 0) + { + _console.println("No Messages to move."); + return; + } + + for (long id : msgids) + { + if (previous != null) + { + if (id == previous + 1) + { + if (start == null) + { + start = previous; + } + } + else + { + if (start != null) + { + //move a range of ids + doCommand(fromQueue, start, id, toQueue); + start = null; + } + else + { + //move a single id + doCommand(fromQueue, id, id, toQueue); + } + } + } + + previous = id; + } + + if (start != null) + { + //move a range of ids + doCommand(fromQueue, start, previous, toQueue); + } + } + + private List<Long> allMessageIDs(AMQQueue fromQueue) + { + List<Long> ids = new LinkedList<Long>(); + + if (fromQueue != null) + { + List<QueueEntry> messages = fromQueue.getMessagesOnTheQueue(); + if (messages != null) + { + for (QueueEntry msg : messages) + { + ids.add(msg.getMessage().getMessageId()); + } + } + } + + return ids; + } + + protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, List<Long> msgids) + { + if (toQueue == null) + { + _console.println("Destination queue not specifed."); + _console.println(usage()); + return false; + } + + if (fromQueue == null) + { + _console.println("Source queue not specifed."); + _console.println(usage()); + return false; + } + + return true; + } + + protected void doCommand(AMQQueue fromQueue, long start, long id, AMQQueue toQueue) + { + fromQueue.moveMessagesToAnotherQueue(start, id, toQueue.getName().toString(), _storeContext); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java new file mode 100644 index 0000000000..5e99997863 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java @@ -0,0 +1,67 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.server.queue.AMQQueue; + +public class Purge extends Move +{ + public Purge(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Purge messages from a queue.\n" + + "The currently selected message set will be purged from the specifed queue.\n" + + "Alternatively the values can be provided on the command line."; + } + + public String usage() + { + return "purge from=<queue> [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "purge"; + } + + + protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids) + { + if (fromQueue == null) + { + _console.println("Source queue not specifed."); + _console.println(usage()); + return false; + } + + return true; + } + + protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue) + { + fromQueue.removeMessagesFromQueue(start, end, _storeContext); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java new file mode 100644 index 0000000000..a81bc07c38 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java @@ -0,0 +1,54 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Quit extends AbstractCommand +{ + public Quit(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Quit the tool."; + } + + public String usage() + { + return "quit"; + } + + public String getCommand() + { + return "quit"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals("quit"); + + _tool.quit(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java new file mode 100644 index 0000000000..ff59568374 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java @@ -0,0 +1,233 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +import java.util.LinkedList; +import java.util.StringTokenizer; + +public class Select extends AbstractCommand +{ + + public Select(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Perform a selection."; + } + + public String usage() + { + return "select virtualhost <name> |exchange <name> |queue <name> | msg id=<msgids eg. 1,2,4-10>"; + } + + public String getCommand() + { + return "select"; + } + + public void execute(String... args) + { + assert args.length > 2; + assert args[0].equals("select"); + + if (args.length < 3) + { + if (args[1].equals("show")) + { + doSelect(args[1], null); + } + else + { + _console.print("select : unknown command:"); + _console.println(help()); + } + } + else + { + if (args[1].equals("virtualhost") + || args[1].equals("vhost") + || args[1].equals("exchange") + || args[1].equals("queue") + || args[1].equals("msg") + ) + { + doSelect(args[1], args[2]); + } + else + { + _console.println(help()); + } + } + } + + private void doSelect(String type, String item) + { + if (type.equals("virtualhost")) + { + + VirtualHost vhost = ApplicationRegistry.getInstance() + .getVirtualHostRegistry().getVirtualHost(item); + + if (vhost == null) + { + _console.println("Virtualhost '" + item + "' not found."); + } + else + { + _tool.getState().setVhost(vhost); + } + } + + if (type.equals("exchange")) + { + + VirtualHost vhost = _tool.getState().getVhost(); + + if (vhost == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + return; + } + + + Exchange exchange = vhost.getExchangeRegistry().getExchange(new AMQShortString(item)); + + if (exchange == null) + { + _console.println("Exchange '" + item + "' not found."); + } + else + { + _tool.getState().setExchange(exchange); + } + + if (_tool.getState().getQueue() != null) + { + if (!exchange.isBound(_tool.getState().getQueue())) + { + _tool.getState().setQueue(null); + } + } + } + + if (type.equals("queue")) + { + VirtualHost vhost = _tool.getState().getVhost(); + + if (vhost == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + return; + } + + AMQQueue queue = vhost.getQueueRegistry().getQueue(new AMQShortString(item)); + + if (queue == null) + { + _console.println("Queue '" + item + "' not found."); + } + else + { + _tool.getState().setQueue(queue); + + if (_tool.getState().getExchange() == null) + { + for (AMQShortString exchangeName : vhost.getExchangeRegistry().getExchangeNames()) + { + Exchange exchange = vhost.getExchangeRegistry().getExchange(exchangeName); + if (exchange.isBound(queue)) + { + _tool.getState().setExchange(exchange); + break; + } + } + } + + //remove the message selection + _tool.getState().setMessages((java.util.List<Long>) null); + } + } + + if (type.equals("msg")) + { + if (item.startsWith("id=")) + { + StringTokenizer tok = new StringTokenizer(item.substring(item.indexOf("=") + 1), ","); + + java.util.List<Long> msgids = null; + + if (tok.hasMoreTokens()) + { + msgids = new LinkedList<Long>(); + } + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + if (next.contains("-")) + { + Long start = Long.parseLong(next.substring(0, next.indexOf("-"))); + Long end = Long.parseLong(next.substring(next.indexOf("-") + 1)); + + if (end >= start) + { + for (long l = start; l <= end; l++) + { + msgids.add(l); + } + } + } + else + { + msgids.add(Long.parseLong(next)); + } + } + + _tool.getState().setMessages(msgids); + } + + } + + if (type.equals("show")) + { + _console.println(_tool.getState().toString()); + if (_tool.getState().getMessages() != null) + { + _console.print("Msgs:"); + for (Long l : _tool.getState().getMessages()) + { + _console.print(" " + l); + } + _console.println(""); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java new file mode 100644 index 0000000000..2fa017fc64 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java @@ -0,0 +1,515 @@ +/* + * 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.tools.messagestore.commands; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntryImpl; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.LinkedList; +import java.util.List; + +public class Show extends AbstractCommand +{ + protected boolean _amqHeaders = false; + protected boolean _routing = false; + protected boolean _msgHeaders = false; + + public Show(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Shows the messages headers."; + } + + public String usage() + { + return getCommand() + " [show=[all],[msgheaders],[amqheaders],[routing]] [id=<msgid e.g. 1,2,4-10>]"; + } + + public String getCommand() + { + return "show"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length < 2) + { + parseArgs("all"); + } + else + { + parseArgs(args); + } + + performShow(); + } + + protected void parseArgs(String... args) + { + List<Long> msgids = null; + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("show=")) + { + _msgHeaders = arg.contains("msgheaders") || arg.contains("all"); + _amqHeaders = arg.contains("amqheaders") || arg.contains("all"); + _routing = arg.contains("routing") || arg.contains("all"); + } + + if (arg.startsWith("id=")) + { + _tool.getState().setMessages(msgids); + } + }//for args + }// if args > 2 + } + + protected void performShow() + { + if (_tool.getState().getVhost() == null) + { + _console.println("No Virtualhost selected. 'DuSelect' a Virtualhost first."); + return; + } + + AMQQueue _queue = _tool.getState().getQueue(); + + List<Long> msgids = _tool.getState().getMessages(); + + if (_queue != null) + { + List<QueueEntry> messages = _queue.getMessagesOnTheQueue(); + if (messages == null || messages.size() == 0) + { + _console.println("No messages on queue"); + return; + } + + List<List> data = createMessageData(msgids, messages, _amqHeaders, _routing, _msgHeaders); + if (data != null) + { + _console.printMap(null, data); + } + else + { + String message = "No data to display."; + if (msgids != null) + { + message += " Is message selection correct? " + _tool.getState().printMessages(); + } + _console.println(message); + } + + } + else + { + _console.println("No Queue specified to show."); + } + } + + /** + * Create the list data for display from the messages. + * + * @param msgids The list of message ids to display + * @param messages A list of messages to format and display. + * @param showHeaders should the header info be shown + * @param showRouting show the routing info be shown + * @param showMessageHeaders show the msg headers be shown + * @return the formated data lists for printing + */ + protected List<List> createMessageData(List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting, + boolean showMessageHeaders) + { + + // Currenly exposed message properties +// //Printing the content Body +// msg.getContentBodyIterator(); +// //Print the Headers +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppIdAsString(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getClusterId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getContentType(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getCorrelationId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getDeliveryMode(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getEncoding(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getExpiration(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getMessageId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPriority(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPropertyFlags(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getReplyTo(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getTimestamp(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getType(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getUserId(); +// +// //Print out all the property names +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders().getPropertyNames(); +// +// msg.getMessageId(); +// msg.getSize(); +// msg.getArrivalTime(); + +// msg.getDeliveredSubscription(); +// msg.getDeliveredToConsumer(); +// msg.getMessageHandle(); +// msg.getMessageId(); +// msg.getMessagePublishInfo(); +// msg.getPublisher(); + +// msg.getStoreContext(); +// msg.isAllContentReceived(); +// msg.isPersistent(); +// msg.isRedelivered(); +// msg.isRejectedBy(); +// msg.isTaken(); + + //Header setup + + List<List> data = new LinkedList<List>(); + + List<String> id = new LinkedList<String>(); + data.add(id); + id.add(Columns.ID.name()); + id.add(Console.ROW_DIVIDER); + + List<String> exchange = new LinkedList<String>(); + List<String> routingkey = new LinkedList<String>(); + List<String> immediate = new LinkedList<String>(); + List<String> mandatory = new LinkedList<String>(); + if (showRouting) + { + data.add(exchange); + exchange.add(Columns.Exchange.name()); + exchange.add(Console.ROW_DIVIDER); + + data.add(routingkey); + routingkey.add(Columns.RoutingKey.name()); + routingkey.add(Console.ROW_DIVIDER); + + data.add(immediate); + immediate.add(Columns.isImmediate.name()); + immediate.add(Console.ROW_DIVIDER); + + data.add(mandatory); + mandatory.add(Columns.isMandatory.name()); + mandatory.add(Console.ROW_DIVIDER); + } + + List<String> size = new LinkedList<String>(); + List<String> appid = new LinkedList<String>(); + List<String> clusterid = new LinkedList<String>(); + List<String> contenttype = new LinkedList<String>(); + List<String> correlationid = new LinkedList<String>(); + List<String> deliverymode = new LinkedList<String>(); + List<String> encoding = new LinkedList<String>(); + List<String> arrival = new LinkedList<String>(); + List<String> expiration = new LinkedList<String>(); + List<String> priority = new LinkedList<String>(); + List<String> propertyflag = new LinkedList<String>(); + List<String> replyto = new LinkedList<String>(); + List<String> timestamp = new LinkedList<String>(); + List<String> type = new LinkedList<String>(); + List<String> userid = new LinkedList<String>(); + List<String> ispersitent = new LinkedList<String>(); + List<String> isredelivered = new LinkedList<String>(); + List<String> isdelivered = new LinkedList<String>(); + + data.add(size); + size.add(Columns.Size.name()); + size.add(Console.ROW_DIVIDER); + + if (showHeaders) + { + data.add(ispersitent); + ispersitent.add(Columns.isPersistent.name()); + ispersitent.add(Console.ROW_DIVIDER); + + data.add(isredelivered); + isredelivered.add(Columns.isRedelivered.name()); + isredelivered.add(Console.ROW_DIVIDER); + + data.add(isdelivered); + isdelivered.add(Columns.isDelivered.name()); + isdelivered.add(Console.ROW_DIVIDER); + + data.add(appid); + appid.add(Columns.App_ID.name()); + appid.add(Console.ROW_DIVIDER); + + data.add(clusterid); + clusterid.add(Columns.Cluster_ID.name()); + clusterid.add(Console.ROW_DIVIDER); + + data.add(contenttype); + contenttype.add(Columns.Content_Type.name()); + contenttype.add(Console.ROW_DIVIDER); + + data.add(correlationid); + correlationid.add(Columns.Correlation_ID.name()); + correlationid.add(Console.ROW_DIVIDER); + + data.add(deliverymode); + deliverymode.add(Columns.Delivery_Mode.name()); + deliverymode.add(Console.ROW_DIVIDER); + + data.add(encoding); + encoding.add(Columns.Encoding.name()); + encoding.add(Console.ROW_DIVIDER); + + data.add(arrival); + expiration.add(Columns.Arrival.name()); + expiration.add(Console.ROW_DIVIDER); + + data.add(expiration); + expiration.add(Columns.Expiration.name()); + expiration.add(Console.ROW_DIVIDER); + + data.add(priority); + priority.add(Columns.Priority.name()); + priority.add(Console.ROW_DIVIDER); + + data.add(propertyflag); + propertyflag.add(Columns.Property_Flag.name()); + propertyflag.add(Console.ROW_DIVIDER); + + data.add(replyto); + replyto.add(Columns.ReplyTo.name()); + replyto.add(Console.ROW_DIVIDER); + + data.add(timestamp); + timestamp.add(Columns.Timestamp.name()); + timestamp.add(Console.ROW_DIVIDER); + + data.add(type); + type.add(Columns.Type.name()); + type.add(Console.ROW_DIVIDER); + + data.add(userid); + userid.add(Columns.UserID.name()); + userid.add(Console.ROW_DIVIDER); + } + + List<String> msgHeaders = new LinkedList<String>(); + if (showMessageHeaders) + { + data.add(msgHeaders); + msgHeaders.add(Columns.MsgHeaders.name()); + msgHeaders.add(Console.ROW_DIVIDER); + } + + //Add create the table of data + for (QueueEntry entry : messages) + { + AMQMessage msg = entry.getMessage(); + if (!includeMsg(msg, msgids)) + { + continue; + } + + id.add(msg.getMessageId().toString()); + + size.add("" + msg.getSize()); + + arrival.add("" + msg.getArrivalTime()); + + try + { + ispersitent.add(msg.isPersistent() ? "true" : "false"); + } + catch (AMQException e) + { + ispersitent.add("n/a"); + } + + isredelivered.add(msg.isRedelivered() ? "true" : "false"); + + isdelivered.add(msg.getDeliveredToConsumer() ? "true" : "false"); + +// msg.getMessageHandle(); + + BasicContentHeaderProperties headers = null; + + try + { + headers = ((BasicContentHeaderProperties) msg.getContentHeaderBody().properties); + } + catch (AMQException e) + { + //ignore +// commandError("Unable to read properties for message: " + e.getMessage(), null); + } + + if (headers != null) + { + String appidS = headers.getAppIdAsString(); + appid.add(appidS == null ? "null" : appidS); + + String clusterS = headers.getClusterIdAsString(); + clusterid.add(clusterS == null ? "null" : clusterS); + + String contentS = headers.getContentTypeAsString(); + contenttype.add(contentS == null ? "null" : contentS); + + String correlationS = headers.getCorrelationIdAsString(); + correlationid.add(correlationS == null ? "null" : correlationS); + + deliverymode.add("" + headers.getDeliveryMode()); + + AMQShortString encodeSS = headers.getEncoding(); + encoding.add(encodeSS == null ? "null" : encodeSS.toString()); + + expiration.add("" + headers.getExpiration()); + + FieldTable headerFT = headers.getHeaders(); + msgHeaders.add(headerFT == null ? "none" : "" + headerFT.toString()); + + priority.add("" + headers.getPriority()); + propertyflag.add("" + headers.getPropertyFlags()); + + AMQShortString replytoSS = headers.getReplyTo(); + replyto.add(replytoSS == null ? "null" : replytoSS.toString()); + + timestamp.add("" + headers.getTimestamp()); + + AMQShortString typeSS = headers.getType(); + type.add(typeSS == null ? "null" : typeSS.toString()); + + AMQShortString useridSS = headers.getUserId(); + userid.add(useridSS == null ? "null" : useridSS.toString()); + + MessagePublishInfo info = null; + try + { + info = msg.getMessagePublishInfo(); + } + catch (AMQException e) + { + //ignore + } + + if (info != null) + { + AMQShortString exchangeSS = info.getExchange(); + exchange.add(exchangeSS == null ? "null" : exchangeSS.toString()); + + AMQShortString routingkeySS = info.getRoutingKey(); + routingkey.add(routingkeySS == null ? "null" : routingkeySS.toString()); + + immediate.add(info.isImmediate() ? "true" : "false"); + mandatory.add(info.isMandatory() ? "true" : "false"); + } + +// msg.getPublisher(); -- only used in clustering +// msg.getStoreContext(); +// msg.isAllContentReceived(); + + }// if headers!=null + +// need to access internal map and do lookups. +// msg.isTaken(); +// msg.getDeliveredSubscription(); +// msg.isRejectedBy(); + + } + + // if id only had the header and the divider in it then we have no data to display + if (id.size() == 2) + { + return null; + } + return data; + } + + protected boolean includeMsg(AMQMessage msg, List<Long> msgids) + { + if (msgids == null) + { + return true; + } + + Long msgid = msg.getMessageId(); + + boolean found = false; + + if (msgids != null) + { + //check msgid is in msgids + for (Long l : msgids) + { + if (l.equals(msgid)) + { + found = true; + break; + } + } + } + return found; + } + + public enum Columns + { + ID, + Size, + Exchange, + RoutingKey, + isImmediate, + isMandatory, + isPersistent, + isRedelivered, + isDelivered, + App_ID, + Cluster_ID, + Content_Type, + Correlation_ID, + Delivery_Mode, + Encoding, + Arrival, + Expiration, + Priority, + Property_Flag, + ReplyTo, + Timestamp, + Type, + UserID, + MsgHeaders + } +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java new file mode 100644 index 0000000000..c27c52eb8e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java @@ -0,0 +1,81 @@ +/* + * 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.tools.security; + +import org.apache.commons.codec.binary.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.DigestException; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; + +public class Passwd +{ + public static void main(String args[]) throws NoSuchAlgorithmException, DigestException, IOException + { + if (args.length != 2) + { + System.out.println("Passwd <username> <password>"); + System.exit(0); + } + + byte[] data = args[1].getBytes("utf-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + Base64 b64 = new Base64(); + + byte[] encoded = b64.encode(digest); + + output(args[0], encoded); + } + + private static void output(String user, byte[] encoded) throws IOException + { + +// File passwdFile = new File("qpid.passwd"); + + PrintStream ps = new PrintStream(System.out); + + user += ":"; + ps.write(user.getBytes("utf-8")); + + for (byte b : encoded) + { + ps.write(b); + } + + ps.println(); + + ps.flush(); + ps.close(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java new file mode 100644 index 0000000000..986fea32cc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java @@ -0,0 +1,51 @@ +/* + * 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.tools.utils; + +public interface CommandParser +{ + /** + * If there is more than one command received on the last parse request. + * + * Subsequent calls to parse will utilise this input rather than reading new data from the input source + * @return boolean + */ + boolean more(); + + /** + * True if the currently parsed command has been requested as a background operation + * + * @return boolean + */ + boolean isBackground(); + + /** + * Parses user commands, and groups tokens in the + * String[] format that all Java main's love. + * + * If more than one command is provided in one input line then the more() method will return true. + * A subsequent call to parse() will continue to parse that input line before reading new input. + * + * @return <code>input</code> split in args[] format; null if eof. + * @throws java.io.IOException if there is a problem reading from the input stream + */ + String[] parse() throws java.io.IOException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java new file mode 100644 index 0000000000..cf457d1ea5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java @@ -0,0 +1,90 @@ +/* + * 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.tools.utils; + +import java.util.List; + +public interface Console +{ + public enum CellFormat + { + CENTRED, LEFT, RIGHT + } + + public static String ROW_DIVIDER = "*divider"; + + public void print(String... message); + + public void println(String... message); + + public String readln(); + + /** + * Reads and parses the command line. + * + * + * @return The next command or null + */ + public String[] readCommand(); + + public CommandParser getCommandParser(); + + public void setCommandParser(CommandParser parser); + + /** + * + * Prints the list of String nicely. + * + * +-------------+ + * | Heading | + * +-------------+ + * | Item 1 | + * | Item 2 | + * | Item 3 | + * +-------------+ + * + * @param hasTitle should list[0] be used as a heading + * @param list The list of Strings to display + */ + public void displayList(boolean hasTitle, String... list); + + /** + * + * Prints the list of String nicely. + * + * +----------------------------+ + * | Heading | + * +----------------------------+ + * | title | title | .. + * +----------------------------+ + * | Item 2 | value 2 | .. + * +----------------------------+ (*divider) + * | Item 3 | value 2 | .. + * +----------------------------+ + * + * @param title The title to display if any + * @param entries the entries to display in a map. + */ + void printMap(String title, List<List> entries); + + + public void close(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java new file mode 100644 index 0000000000..09444ccdd7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java @@ -0,0 +1,121 @@ +/* + * 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.tools.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.StringTokenizer; + +public class SimpleCommandParser implements CommandParser +{ + private static final String COMMAND_SEPERATOR = ";"; + + /** Input source of commands */ + protected BufferedReader _reader; + + /** The next list of commands from the command line */ + private StringBuilder _nextCommand = null; + + public SimpleCommandParser(BufferedReader reader) + { + _reader = reader; + } + + public boolean more() + { + return _nextCommand != null; + } + + public boolean isBackground() + { + return false; + } + + public String[] parse() throws IOException + { + String[] commands = null; + + String input = null; + + if (_nextCommand == null) + { + input = _reader.readLine(); + } + else + { + input = _nextCommand.toString(); + _nextCommand = null; + } + + if (input == null) + { + return null; + } + + StringTokenizer tok = new StringTokenizer(input, " "); + + int tokenCount = tok.countTokens(); + int index = 0; + + if (tokenCount > 0) + { + commands = new String[tokenCount]; + boolean commandComplete = false; + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + + if (next.equals(COMMAND_SEPERATOR)) + { + commandComplete = true; + _nextCommand = new StringBuilder(); + continue; + } + + if (commandComplete) + { + _nextCommand.append(next); + _nextCommand.append(" "); + } + else + { + commands[index] = next; + index++; + } + } + + } + + //Reduce the String[] if not all the tokens were used in this command. + // i.e. there is more than one command on the line. + if (index != tokenCount) + { + String[] shortCommands = new String[index]; + System.arraycopy(commands, 0, shortCommands, 0, index); + return shortCommands; + } + else + { + return commands; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java new file mode 100644 index 0000000000..ec080a4611 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java @@ -0,0 +1,363 @@ +/* + * 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.tools.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +public class SimpleConsole implements Console +{ + /** SLF4J Logger. */ + private static Logger _devlog = LoggerFactory.getLogger(SimpleConsole.class); + + /** Console Writer. */ + protected static BufferedWriter _consoleWriter; + + /** Console Reader. */ + protected static BufferedReader _consoleReader; + + /** Parser for command-line input. */ + protected CommandParser _parser; + + public SimpleConsole(BufferedWriter writer, BufferedReader reader) + { + _consoleWriter = writer; + _consoleReader = reader; + _parser = new SimpleCommandParser(_consoleReader); + } + + public void print(String... message) + { + try + { + for (String s : message) + { + _consoleWriter.write(s); + } + _consoleWriter.flush(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to write:" + message); + } + + } + + public void println(String... message) + { + print(message); + print(System.getProperty("line.separator")); + } + + + public String readln() + { + try + { + return _consoleReader.readLine(); + } + catch (IOException e) + { + _devlog.debug("Unable to read input due to:" + e.getMessage()); + return null; + } + } + + public String[] readCommand() + { + try + { + return _parser.parse(); + } + catch (IOException e) + { + _devlog.error("Error reading command:" + e.getMessage()); + return new String[0]; + } + } + + public CommandParser getCommandParser() + { + return _parser; + } + + public void setCommandParser(CommandParser parser) + { + _parser = parser; + } + + public void displayList(boolean hasTitle, String... list) + { + java.util.List<java.util.List> data = new LinkedList<List>(); + + java.util.List<String> values = new LinkedList<String>(); + + data.add(values); + + for (String value : list) + { + values.add(value); + } + + if (hasTitle) + { + values.add(1, "*divider"); + } + + printMap(null, data); + } + + /** + * + * Prints the list of String nicely. + * + * +----------------------------+ + * | Heading | + * +----------------------------+ + * | title | title | .. + * +----------------------------+ + * | Item 2 | value 2 | .. + * | Item 3 | value 2 | .. + * +----------------------------+ + * + * @param title The title to display if any + * @param entries the entries to display in a map. + */ + public void printMap(String title, java.util.List<java.util.List> entries) + { + try + { + int columns = entries.size(); + + int[] columnWidth = new int[columns]; + + // calculate row count + int rowMax = 0; + + //the longest item + int itemMax = 0; + + for (int i = 0; i < columns; i++) + { + int columnIRowMax = entries.get(i).size(); + + if (columnIRowMax > rowMax) + { + rowMax = columnIRowMax; + } + for (Object values : entries.get(i)) + { + if (values.toString().equals(Console.ROW_DIVIDER)) + { + continue; + } + + int itemLength = values.toString().length(); + + //note for single width + if (itemLength > itemMax) + { + itemMax = itemLength; + } + + //note for mulit width + if (itemLength > columnWidth[i]) + { + columnWidth[i] = itemLength; + } + + } + } + + int tableWidth = 0; + + + for (int i = 0; i < columns; i++) + { + // plus 2 for the space padding + columnWidth[i] += 2; + } + for (int size : columnWidth) + { + tableWidth += size; + } + tableWidth += (columns - 1); + + if (title != null) + { + if (title.length() > tableWidth) + { + tableWidth = title.length(); + } + + printCellRow("+", "-", tableWidth); + + printCell(CellFormat.CENTRED, "|", tableWidth, " " + title + " ", 0); + _consoleWriter.newLine(); + + } + + //put top line | or bottom of title + printCellRow("+", "-", tableWidth); + + //print the table data + int row = 0; + + for (; row < rowMax; row++) + { + for (int i = 0; i < columns; i++) + { + java.util.List columnData = entries.get(i); + + String value; + // does this column have a value for this row + if (columnData.size() > row) + { + value = " " + columnData.get(row).toString() + " "; + } + else + { + value = " "; + } + + if (i == 0 && value.equals(" " + Console.ROW_DIVIDER + " ")) + { + printCellRow("+", "-", tableWidth); + //move on to the next row + break; + } + else + { + printCell(CellFormat.LEFT, "|", columnWidth[i], value, i); + } + + // if it is the last row then do a new line. + if (i == columns - 1) + { + _consoleWriter.newLine(); + } + } + } + + printCellRow("+", "-", tableWidth); + + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to write."); + } + } + + public void close() + { + + try + { + _consoleReader.close(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to close reader."); + } + + try + { + + _consoleWriter.close(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to close writer."); + } + + } + + private void printCell(CellFormat format, String edge, int cellWidth, String cell, int column) throws IOException + { + int pad = cellWidth - cell.length(); + + if (column == 0) + { + _consoleWriter.write(edge); + } + + switch (format) + { + case CENTRED: + printPad(" ", pad / 2); + break; + case RIGHT: + printPad(" ", pad); + break; + } + + _consoleWriter.write(cell); + + + switch (format) + { + case CENTRED: + // if pad isn't even put the extra one on the right + if (pad % 2 == 0) + { + printPad(" ", pad / 2); + } + else + { + printPad(" ", (pad / 2) + 1); + } + break; + case LEFT: + printPad(" ", pad); + break; + } + + + _consoleWriter.write(edge); + + } + + private void printCellRow(String edge, String mid, int cellWidth) throws IOException + { + _consoleWriter.write(edge); + + printPad(mid, cellWidth); + + _consoleWriter.write(edge); + _consoleWriter.newLine(); + } + + private void printPad(String padChar, int count) throws IOException + { + for (int i = 0; i < count; i++) + { + _consoleWriter.write(padChar); + } + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java new file mode 100644 index 0000000000..b94f2ef76f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/AMQBrokerManagerMBeanTest.java @@ -0,0 +1,91 @@ +/* + * + * 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; + +import junit.framework.TestCase; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.ManagedBroker; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class AMQBrokerManagerMBeanTest extends TestCase +{ + private QueueRegistry _queueRegistry; + private ExchangeRegistry _exchangeRegistry; + + public void testExchangeOperations() throws Exception + { + String exchange1 = "testExchange1_" + System.currentTimeMillis(); + String exchange2 = "testExchange2_" + System.currentTimeMillis(); + String exchange3 = "testExchange3_" + System.currentTimeMillis(); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null); + + VirtualHost vHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + + ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHost.VirtualHostMBean) vHost.getManagedObject()); + mbean.createNewExchange(exchange1, "direct", false); + mbean.createNewExchange(exchange2, "topic", false); + mbean.createNewExchange(exchange3, "headers", false); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) != null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) != null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) != null); + + mbean.unregisterExchange(exchange1); + mbean.unregisterExchange(exchange2); + mbean.unregisterExchange(exchange3); + + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange1)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange2)) == null); + assertTrue(_exchangeRegistry.getExchange(new AMQShortString(exchange3)) == null); + } + + public void testQueueOperations() throws Exception + { + String queueName = "testQueue_" + System.currentTimeMillis(); + VirtualHost vHost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"); + + ManagedBroker mbean = new AMQBrokerManagerMBean((VirtualHost.VirtualHostMBean) vHost.getManagedObject()); + + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null); + + mbean.createNewQueue(queueName, "test", false); + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) != null); + + mbean.deleteQueue(queueName); + assertTrue(_queueRegistry.getQueue(new AMQShortString(queueName)) == null); + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + _queueRegistry = appRegistry.getVirtualHostRegistry().getVirtualHost("test").getQueueRegistry(); + _exchangeRegistry = appRegistry.getVirtualHostRegistry().getVirtualHost("test").getExchangeRegistry(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java new file mode 100644 index 0000000000..5fbf9484f7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ExtractResendAndRequeueTest.java @@ -0,0 +1,255 @@ +/* + * + * 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; + +import junit.framework.TestCase; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.queue.MockQueueEntry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.SimpleQueueEntryList; +import org.apache.qpid.server.queue.MockAMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntryIterator; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.AMQException; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Iterator; + +/** + * QPID-1385 : Race condition between added to unacked map and resending due to a rollback. + * + * In AMQChannel _unackedMap.clear() was done after the visit. This meant that the clear was not in the same + * synchronized block as as the preparation to resend. + * + * This clearing/prep for resend was done as a result of the rollback call. HOWEVER, the delivery thread was still + * in the process of sending messages to the client. It is therefore possible that a message could block on the + * _unackedMap lock waiting for the visit to compelete so that it can add the new message to the unackedMap.... + * which is then cleared by the resend/rollback thread. + * + * This problem was encountered by the testSend2ThenRollback test. + * + * To try and increase the chance of the race condition occuring this test will send multiple messages so that the + * delivery thread will be in progress while the rollback method is called. Hopefully this will cause the + * deliveryTag to be lost + */ +public class ExtractResendAndRequeueTest extends TestCase +{ + + UnacknowledgedMessageMapImpl _unacknowledgedMessageMap; + private static final int INITIAL_MSG_COUNT = 10; + private AMQQueue _queue = new MockAMQQueue(); + private LinkedList<QueueEntry> _referenceList = new LinkedList<QueueEntry>(); + + @Override + public void setUp() throws AMQException + { + _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(100); + + long id = 0; + SimpleQueueEntryList list = new SimpleQueueEntryList(_queue); + + // Add initial messages to QueueEntryList + for (int count = 0; count < INITIAL_MSG_COUNT; count++) + { + AMQMessage msg = new MockAMQMessage(id); + + list.add(msg); + + //Increment ID; + id++; + } + + // Iterate through the QueueEntryList and add entries to unacknowledgeMessageMap and referecenList + QueueEntryIterator queueEntries = list.iterator(); + while(queueEntries.advance()) + { + QueueEntry entry = queueEntries.getNode(); + _unacknowledgedMessageMap.add(entry.getMessage().getMessageId(), entry); + + // Store the entry for future inspection + _referenceList.add(entry); + } + + assertEquals("Map does not contain correct setup data", INITIAL_MSG_COUNT, _unacknowledgedMessageMap.size()); + } + + /** + * Helper method to create a new subscription and aquire the given messages. + * + * @param messageList The messages to aquire + * + * @return Subscription that performed the aquire + */ + private Subscription createSubscriptionAndAquireMessages(LinkedList<QueueEntry> messageList) + { + Subscription subscription = new MockSubscription(); + + // Aquire messages in subscription + for (QueueEntry entry : messageList) + { + entry.acquire(subscription); + } + + return subscription; + } + + /** + * This is the normal consumer rollback method. + * + * An active consumer that has aquired messages expects those messasges to be reset when rollback is requested. + * + * This test validates that the msgToResend map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testResend() throws AMQException + { + //We don't need the subscription object here. + createSubscriptionAndAquireMessages(_referenceList); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, new StoreContext())); + + assertEquals("Message count for resend not correct.", INITIAL_MSG_COUNT, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * This is the normal consumer close method. + * + * When a consumer that has aquired messages expects closes the messages that it has aquired should be removed from + * the unacknowledgeMap and placed in msgToRequeue + * + * This test validates that the msgToRequeue map includes all the messages and none are left behind. + * + * @throws AMQException the visit interface throws this + */ + public void testRequeueDueToSubscriptionClosure() throws AMQException + { + Subscription subscription = createSubscriptionAndAquireMessages(_referenceList); + + // Close subscription + subscription.close(); + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend doesn't matter here. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, new StoreContext())); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that messages are requeued + * requeueIfUnabletoResend(set to true) then all messages should be sent to the msgToRequeue map. + * + * @throws AMQException the visit interface throws this + */ + + public void testRequeueDueToMessageHavingNoConsumerTag() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = true so all messages should go to msgToRequeue + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, true, new StoreContext())); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", INITIAL_MSG_COUNT, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + } + + /** + * If the subscription is null, due to message being retrieved via a GET, And we request that we don't + * requeueIfUnabletoResend(set to false) then all messages should be dropped as we do not have a dead letter queue. + * + * @throws AMQException the visit interface throws this + */ + + public void testDrop() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + // requeueIfUnabletoResend = false so all messages should be dropped all maps should be empty + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, new StoreContext())); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + + } + + /** + * If the subscription is null, due to message being retrieved via a GET, AND the queue upon which the message was + * delivered has been deleted then it is not possible to requeue. Currently we simply discar the message but in the + * future we may wish to dead letter the message. + * + * Validate that at the end of the visit all Maps are empty and all messages are marked as deleted + * + * @throws AMQException the visit interface throws this + */ + public void testDiscard() throws AMQException + { + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + _queue.delete(); + + // requeueIfUnabletoResend : value doesn't matter here as queue has been deleted + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, msgToRequeue, + msgToResend, false, new StoreContext())); + + assertEquals("Message count for resend not correct.", 0, msgToResend.size()); + assertEquals("Message count for requeue not correct.", 0, msgToRequeue.size()); + assertEquals("Map was not emptied", 0, _unacknowledgedMessageMap.size()); + + for (QueueEntry entry : _referenceList) + { + assertTrue("Message was not discarded", entry.isDeleted()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java new file mode 100644 index 0000000000..59543874b4 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/RunBrokerWithCommand.java @@ -0,0 +1,132 @@ +/* + * 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; + +import org.apache.log4j.Logger; +import org.apache.log4j.Level; + +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +public class RunBrokerWithCommand +{ + public static void main(String[] args) + { + //Start the broker + try + { + String[] fudge = args.clone(); + + // Override the first value which is the command we are going to run later. + fudge[0] = "-v"; + new Main(fudge).startup(); + } + catch (Exception e) + { + System.err.println("Unable to start broker due to: " + e.getMessage()); + + e.printStackTrace(); + exit(1); + } + + Logger.getRootLogger().setLevel(Level.ERROR); + + //run command + try + { + Process task = Runtime.getRuntime().exec(args[0]); + System.err.println("Started Proccess: " + args[0]); + + InputStream inputStream = task.getInputStream(); + + InputStream errorStream = task.getErrorStream(); + + Thread out = new Thread(new Outputter("[OUT]", new BufferedReader(new InputStreamReader(inputStream)))); + Thread err = new Thread(new Outputter("[ERR]", new BufferedReader(new InputStreamReader(errorStream)))); + + out.start(); + err.start(); + + out.join(); + err.join(); + + System.err.println("Waiting for process to exit: " + args[0]); + task.waitFor(); + System.err.println("Done Proccess: " + args[0]); + + } + catch (IOException e) + { + System.err.println("Proccess had problems: " + e.getMessage()); + e.printStackTrace(System.err); + exit(1); + } + catch (InterruptedException e) + { + System.err.println("Proccess had problems: " + e.getMessage()); + e.printStackTrace(System.err); + + exit(1); + } + + + exit(0); + } + + private static void exit(int i) + { + Logger.getRootLogger().setLevel(Level.INFO); + System.exit(i); + } + + static class Outputter implements Runnable + { + + BufferedReader reader; + String prefix; + + Outputter(String s, BufferedReader r) + { + prefix = s; + reader = r; + } + + public void run() + { + String line; + try + { + while ((line = reader.readLine()) != null) + { + System.out.println(prefix + line); + } + } + catch (IOException e) + { + System.out.println("Error occured reading; " + e.getMessage()); + } + } + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java new file mode 100644 index 0000000000..a0304a7b01 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java @@ -0,0 +1,128 @@ +package org.apache.qpid.server; + +import junit.framework.TestCase; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.AMQException;/* + * + * 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. + * + */ + +public class SelectorParserTest extends TestCase +{ + public void testSelectorWithHyphen() + { + testPass("Cost = 2 AND \"property-with-hyphen\" = 'wibble'"); + } + + public void testLike() + { + testFail("Cost LIKE 2"); + testPass("Cost LIKE 'Hello'"); + } + + public void testStringQuoted() + { + testPass("string = 'Test'"); + } + + public void testProperty() + { + testPass("prop1 = prop2"); + } + + public void testPropertyNames() + { + testPass("$min= TRUE AND _max= FALSE AND Prop_2 = true AND prop$3 = false"); + } + + public void testProtected() + { + testFail("NULL = 0 "); + testFail("TRUE = 0 "); + testFail("FALSE = 0 "); + testFail("NOT = 0 "); + testFail("AND = 0 "); + testFail("OR = 0 "); + testFail("BETWEEN = 0 "); + testFail("LIKE = 0 "); + testFail("IN = 0 "); + testFail("IS = 0 "); + testFail("ESCAPE = 0 "); + } + + + public void testBoolean() + { + testPass("min= TRUE AND max= FALSE "); + testPass("min= true AND max= false"); + } + + public void testDouble() + { + testPass("positive=31E2 AND negative=-31.4E3"); + testPass("min=" + Double.MIN_VALUE + " AND max=" + Double.MAX_VALUE); + } + + public void testLong() + { + testPass("minLong=" + Long.MIN_VALUE + "L AND maxLong=" + Long.MAX_VALUE + "L"); + } + + public void testInt() + { + testPass("minInt=" + Integer.MIN_VALUE + " AND maxInt=" + Integer.MAX_VALUE); + } + + public void testSigned() + { + testPass("negative=-42 AND positive=+42"); + } + + public void testOctal() + { + testPass("octal=042"); + } + + + private void testPass(String selector) + { + try + { + new JMSSelectorFilter(selector); + } + catch (AMQException e) + { + fail("Selector '" + selector + "' was not parsed :" + e.getMessage()); + } + } + + private void testFail(String selector) + { + try + { + new JMSSelectorFilter(selector); + fail("Selector '" + selector + "' was parsed "); + } + catch (AMQException e) + { + //normal path + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.java new file mode 100644 index 0000000000..9ef4af2932 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/AcknowledgeTest.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.ack; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.util.InternalBrokerBaseCase; + +import java.util.List; + +public class AcknowledgeTest extends InternalBrokerBaseCase +{ + + public void testTransactionalSingleAck() throws AMQException + { + _channel.setLocalTransactional(); + runMessageAck(1, 1, 1, false, 0); + } + + public void testTransactionalMultiAck() throws AMQException + { + _channel.setLocalTransactional(); + runMessageAck(10, 1, 5, true, 5); + } + + public void testTransactionalAckAll() throws AMQException + { + _channel.setLocalTransactional(); + runMessageAck(10, 1, 0, true, 0); + } + + public void testNonTransactionalSingleAck() throws AMQException + { + runMessageAck(1, 1, 1, false, 0); + } + + public void testNonTransactionalMultiAck() throws AMQException + { + runMessageAck(10, 1, 5, true, 5); + } + + public void testNonTransactionalAckAll() throws AMQException + { + runMessageAck(10, 1, 0, true, 0); + } + + protected void runMessageAck(int sendMessageCount, long firstDeliveryTag, long acknowledgeDeliveryTag, boolean acknowldegeMultiple, int remainingUnackedMessages) throws AMQException + { + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + publishMessages(_session, _channel, sendMessageCount); + + if (_channel.isTransactional()) + { + _channel.commit(); + } + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, _channel.getUnacknowledgedMessageMap().size()); + + //Subscribe to the queue + AMQShortString subscriber = subscribe(_session, _channel, _queue); + + _queue.deliverAsync(); + + //Wait for the messages to be delivered + _session.awaitDelivery(sendMessageCount); + + //Check that they are all waiting to be acknoledged + assertEquals("Channel should have unacked msgs", sendMessageCount, _channel.getUnacknowledgedMessageMap().size()); + + List<InternalTestProtocolSession.DeliveryPair> messages = _session.getDelivers(_channel.getChannelId(), subscriber, sendMessageCount); + + //Double check we received the right number of messages + assertEquals(sendMessageCount, messages.size()); + + //Check that the first message has the expected deliveryTag + assertEquals("First message does not have expected deliveryTag", firstDeliveryTag, messages.get(0).getDeliveryTag()); + + //Send required Acknowledgement + _channel.acknowledgeMessage(acknowledgeDeliveryTag, acknowldegeMultiple); + + if (_channel.isTransactional()) + { + _channel.commit(); + } + + // Check Remaining Acknowledgements + assertEquals("Channel unacked msgs count incorrect", remainingUnackedMessages, _channel.getUnacknowledgedMessageMap().size()); + + //Check store contents are also correct. + checkStoreContents(remainingUnackedMessages); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java new file mode 100644 index 0000000000..dfbbd56d6f --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/ack/TxAckTest.java @@ -0,0 +1,283 @@ +/* + * + * 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.ack; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.AMQMessageHandle; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; + +import java.util.*; + +public class TxAckTest extends TestCase +{ + private Scenario individual; + private Scenario multiple; + private Scenario combined; + + protected void setUp() throws Exception + { + super.setUp(); + + //ack only 5th msg + individual = new Scenario(10, Arrays.asList(5l), Arrays.asList(1l, 2l, 3l, 4l, 6l, 7l, 8l, 9l, 10l)); + individual.update(5, false); + + //ack all up to and including 5th msg + multiple = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l), Arrays.asList(6l, 7l, 8l, 9l, 10l)); + multiple.update(5, true); + + //leave only 8th and 9th unacked + combined = new Scenario(10, Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 10l), Arrays.asList(8l, 9l)); + combined.update(3, false); + combined.update(5, true); + combined.update(7, true); + combined.update(2, true);//should be ignored + combined.update(1, false);//should be ignored + combined.update(10, false); + } + + @Override + protected void tearDown() throws Exception + { + individual.stop(); + multiple.stop(); + combined.stop(); + } + + public void testPrepare() throws AMQException + { + individual.prepare(); + multiple.prepare(); + combined.prepare(); + } + + public void testUndoPrepare() throws AMQException + { + individual.undoPrepare(); + multiple.undoPrepare(); + combined.undoPrepare(); + } + + public void testCommit() throws AMQException + { + individual.commit(); + multiple.commit(); + combined.commit(); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(TxAckTest.class); + } + + private class Scenario + { + private final UnacknowledgedMessageMap _map = new UnacknowledgedMessageMapImpl(5000); + private final TxAck _op = new TxAck(_map); + private final List<Long> _acked; + private final List<Long> _unacked; + private StoreContext _storeContext = new StoreContext(); + private AMQQueue _queue; + + Scenario(int messageCount, List<Long> acked, List<Long> unacked) throws Exception + { + TransactionalContext txnContext = new NonTransactionalContext(new TestMemoryMessageStore(), + _storeContext, null, + new LinkedList<RequiredDeliveryException>() + ); + + PropertiesConfiguration env = new PropertiesConfiguration(); + env.setProperty("name", "test"); + VirtualHost virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env), null); + + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("test"), false, null, false, + virtualHost, null); + + for (int i = 0; i < messageCount; i++) + { + long deliveryTag = i + 1; + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + TestMessage message = new TestMessage(deliveryTag, i, info, txnContext.getStoreContext()); + _map.add(deliveryTag, _queue.enqueue(new StoreContext(), message)); + } + _acked = acked; + _unacked = unacked; + } + + void update(long deliverytag, boolean multiple) + { + _op.update(deliverytag, multiple); + } + + private void assertCount(List<Long> tags, int expected) + { + for (long tag : tags) + { + QueueEntry u = _map.get(tag); + assertTrue("Message not found for tag " + tag, u != null); + ((TestMessage) u.getMessage()).assertCountEquals(expected); + } + } + + void prepare() throws AMQException + { + _op.consolidate(); + _op.prepare(_storeContext); + + assertCount(_acked, -1); + assertCount(_unacked, 0); + + } + + void undoPrepare() + { + _op.consolidate(); + _op.undoPrepare(); + + assertCount(_acked, 1); + assertCount(_unacked, 0); + } + + void commit() + { + _op.consolidate(); + _op.commit(_storeContext); + + //check acked messages are removed from map + Set<Long> keys = new HashSet<Long>(_map.getDeliveryTags()); + keys.retainAll(_acked); + assertTrue("Expected messages with following tags to have been removed from map: " + keys, keys.isEmpty()); + //check unacked messages are still in map + keys = new HashSet<Long>(_unacked); + keys.removeAll(_map.getDeliveryTags()); + assertTrue("Expected messages with following tags to still be in map: " + keys, keys.isEmpty()); + } + + public void stop() + { + _queue.stop(); + } + } + + private static AMQMessageHandle createMessageHandle(final long messageId, final MessagePublishInfo publishBody) + { + final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, + null, + false); + try + { + amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(), + publishBody, + new ContentHeaderBody() + { + public int getSize() + { + return 1; + } + }); + } + catch (AMQException e) + { + // won't happen + } + + + return amqMessageHandle; + } + + + private class TestMessage extends AMQMessage + { + private final long _tag; + private int _count; + + TestMessage(long tag, long messageId, MessagePublishInfo publishBody, StoreContext storeContext) + throws AMQException + { + super(createMessageHandle(messageId, publishBody), storeContext, publishBody); + _tag = tag; + } + + + public boolean incrementReference() + { + _count++; + return true; + } + + public void decrementReference(StoreContext context) + { + _count--; + } + + void assertCountEquals(int expected) + { + assertEquals("Wrong count for message with tag " + _tag, expected, _count); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java new file mode 100644 index 0000000000..9692cf2727 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java @@ -0,0 +1,136 @@ +/* + * + * 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.configuration; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.PropertiesConfiguration; + +public class QueueConfigurationTest extends TestCase +{ + + private VirtualHostConfiguration _emptyConf; + private PropertiesConfiguration _env; + private ServerConfiguration _fullServerConf; + private VirtualHostConfiguration _fullHostConf; + + public void setUp() throws Exception + { + _env = new PropertiesConfiguration(); + _emptyConf = new VirtualHostConfiguration("test", _env); + + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("queues.maximumMessageAge", 1); + fullEnv.setProperty("queues.maximumQueueDepth", 1); + fullEnv.setProperty("queues.maximumMessageSize", 1); + fullEnv.setProperty("queues.maximumMessageCount", 1); + fullEnv.setProperty("queues.minimumAlertRepeatGap", 1); + + _fullHostConf = new VirtualHostConfiguration("test", fullEnv); + + } + + public void testGetMaximumMessageAge() + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf); + assertEquals(0, qConf.getMaximumMessageAge()); + + // Check explicit value + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("maximumMessageAge", 2); + qConf = new QueueConfiguration("test", fullEnv, _fullHostConf); + assertEquals(2, qConf.getMaximumMessageAge()); + + // Check inherited value + qConf = new QueueConfiguration("test", _env, _fullHostConf); + assertEquals(1, qConf.getMaximumMessageAge()); + } + + public void testGetMaximumQueueDepth() + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf); + assertEquals(0, qConf.getMaximumQueueDepth()); + + // Check explicit value + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("maximumQueueDepth", 2); + qConf = new QueueConfiguration("test", fullEnv, _fullHostConf); + assertEquals(2, qConf.getMaximumQueueDepth()); + + // Check inherited value + qConf = new QueueConfiguration("test", _env, _fullHostConf); + assertEquals(1, qConf.getMaximumQueueDepth()); + } + + public void testGetMaximumMessageSize() + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf); + assertEquals(0, qConf.getMaximumMessageSize()); + + // Check explicit value + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("maximumMessageSize", 2); + qConf = new QueueConfiguration("test", fullEnv, _fullHostConf); + assertEquals(2, qConf.getMaximumMessageSize()); + + // Check inherited value + qConf = new QueueConfiguration("test", _env, _fullHostConf); + assertEquals(1, qConf.getMaximumMessageSize()); + } + + public void testGetMaximumMessageCount() + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf); + assertEquals(0, qConf.getMaximumMessageCount()); + + // Check explicit value + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("maximumMessageCount", 2); + qConf = new QueueConfiguration("test", fullEnv, _fullHostConf); + assertEquals(2, qConf.getMaximumMessageCount()); + + // Check inherited value + qConf = new QueueConfiguration("test", _env, _fullHostConf); + assertEquals(1, qConf.getMaximumMessageCount()); + } + + public void testGetMinimumAlertRepeatGap() + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _env, _emptyConf); + assertEquals(0, qConf.getMinimumAlertRepeatGap()); + + // Check explicit value + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("minimumAlertRepeatGap", 2); + qConf = new QueueConfiguration("test", fullEnv, _fullHostConf); + assertEquals(2, qConf.getMinimumAlertRepeatGap()); + + // Check inherited value + qConf = new QueueConfiguration("test", _env, _fullHostConf); + assertEquals(1, qConf.getMinimumAlertRepeatGap()); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java new file mode 100644 index 0000000000..2c39d006b9 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/ServerConfigurationTest.java @@ -0,0 +1,867 @@ +/* + * + * 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.configuration; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.List; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.TestIoSession; +import org.apache.qpid.server.queue.MockProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +import junit.framework.TestCase; + +public class ServerConfigurationTest extends TestCase +{ + + private XMLConfiguration _config; + + @Override + public void setUp() + { + _config = new XMLConfiguration(); + } + + @Override + public void tearDown() + { + ApplicationRegistry.removeAll(); + } + + public void testSetJMXManagementPort() throws ConfigurationException + { + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.setJMXManagementPort(23); + assertEquals(23, serverConfig.getJMXManagementPort()); + } + + public void testGetJMXManagementPort() throws ConfigurationException + { + _config.setProperty("management.jmxport", 42); + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(42, serverConfig.getJMXManagementPort()); + } + + public void testGetPlatformMbeanserver() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getPlatformMbeanserver()); + + // Check value we set + _config.setProperty("management.platform-mbeanserver", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getPlatformMbeanserver()); + } + + public void testGetPluginDirectory() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(null, serverConfig.getPluginDirectory()); + + // Check value we set + _config.setProperty("plugin-directory", "/path/to/plugins"); + serverConfig = new ServerConfiguration(_config); + assertEquals("/path/to/plugins", serverConfig.getPluginDirectory()); + } + + public void testGetPrincipalDatabaseNames() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getPrincipalDatabaseNames().size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).name", "a"); + _config.setProperty("security.principal-databases.principal-database(1).name", "b"); + serverConfig = new ServerConfiguration(_config); + List<String> dbs = serverConfig.getPrincipalDatabaseNames(); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseClass() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getPrincipalDatabaseClass().size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).class", "a"); + _config.setProperty("security.principal-databases.principal-database(1).class", "b"); + serverConfig = new ServerConfiguration(_config); + List<String> dbs = serverConfig.getPrincipalDatabaseClass(); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseAttributeNames() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getPrincipalDatabaseAttributeNames(1).size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.name", "a"); + _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.name", "b"); + serverConfig = new ServerConfiguration(_config); + List<String> dbs = serverConfig.getPrincipalDatabaseAttributeNames(0); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetPrincipalDatabaseAttributeValues() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getPrincipalDatabaseAttributeValues(1).size()); + + // Check value we set + _config.setProperty("security.principal-databases.principal-database(0).attributes(0).attribute.value", "a"); + _config.setProperty("security.principal-databases.principal-database(0).attributes(1).attribute.value", "b"); + serverConfig = new ServerConfiguration(_config); + List<String> dbs = serverConfig.getPrincipalDatabaseAttributeValues(0); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetManagementAccessList() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getManagementAccessList().size()); + + // Check value we set + _config.setProperty("security.jmx.access(0)", "a"); + _config.setProperty("security.jmx.access(1)", "b"); + serverConfig = new ServerConfiguration(_config); + List<String> dbs = serverConfig.getManagementAccessList(); + assertEquals(2, dbs.size()); + assertEquals("a", dbs.get(0)); + assertEquals("b", dbs.get(1)); + } + + public void testGetFrameSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(65536, serverConfig.getFrameSize()); + + // Check value we set + _config.setProperty("advanced.framesize", "23"); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getFrameSize()); + } + + public void testGetProtectIOEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getProtectIOEnabled()); + + // Check value we set + _config.setProperty("broker.connector.protectio.enabled", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getProtectIOEnabled()); + } + + public void testGetBufferReadLimit() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(262144, serverConfig.getBufferReadLimit()); + + // Check value we set + _config.setProperty("broker.connector.protectio.readBufferLimitSize", 23); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getBufferReadLimit()); + } + + public void testGetBufferWriteLimit() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(262144, serverConfig.getBufferWriteLimit()); + + // Check value we set + _config.setProperty("broker.connector.protectio.writeBufferLimitSize", 23); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getBufferWriteLimit()); + } + + public void testGetSynchedClocks() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getSynchedClocks()); + + // Check value we set + _config.setProperty("advanced.synced-clocks", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getSynchedClocks()); + } + + public void testGetMsgAuth() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getMsgAuth()); + + // Check value we set + _config.setProperty("security.msg-auth", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getMsgAuth()); + } + + public void testGetJMXPrincipalDatabase() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(null, serverConfig.getJMXPrincipalDatabase()); + + // Check value we set + _config.setProperty("security.jmx.principal-database", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getJMXPrincipalDatabase()); + } + + public void testGetManagementKeyStorePath() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(null, serverConfig.getManagementKeyStorePath()); + + // Check value we set + _config.setProperty("management.ssl.keyStorePath", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getManagementKeyStorePath()); + } + + public void testGetManagementSSLEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getManagementSSLEnabled()); + + // Check value we set + _config.setProperty("management.ssl.enabled", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getManagementSSLEnabled()); + } + + public void testGetManagementKeyStorePassword() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(null, serverConfig.getManagementKeyStorePassword()); + + // Check value we set + _config.setProperty("management.ssl.keyStorePassword", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getManagementKeyStorePassword()); + } + + public void testGetQueueAutoRegister() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getQueueAutoRegister()); + + // Check value we set + _config.setProperty("queue.auto_register", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getQueueAutoRegister()); + } + + public void testGetManagementEnabled() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getManagementEnabled()); + + // Check value we set + _config.setProperty("management.enabled", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getManagementEnabled()); + } + + public void testSetManagementEnabled() throws ConfigurationException + { + // Check value we set + ServerConfiguration serverConfig = new ServerConfiguration(_config); + serverConfig.setManagementEnabled(false); + assertEquals(false, serverConfig.getManagementEnabled()); + } + + public void testGetHeartBeatDelay() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(5, serverConfig.getHeartBeatDelay()); + + // Check value we set + _config.setProperty("heartbeat.delay", 23); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getHeartBeatDelay()); + } + + public void testGetHeartBeatTimeout() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(2.0, serverConfig.getHeartBeatTimeout()); + + // Check value we set + _config.setProperty("heartbeat.timeoutFactor", 2.3); + serverConfig = new ServerConfiguration(_config); + assertEquals(2.3, serverConfig.getHeartBeatTimeout()); + } + + public void testGetMaximumMessageAge() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getMaximumMessageAge()); + + // Check value we set + _config.setProperty("maximumMessageAge", 10L); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getMaximumMessageAge()); + } + + public void testGetMaximumMessageCount() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getMaximumMessageCount()); + + // Check value we set + _config.setProperty("maximumMessageCount", 10L); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getMaximumMessageCount()); + } + + public void testGetMaximumQueueDepth() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getMaximumQueueDepth()); + + // Check value we set + _config.setProperty("maximumQueueDepth", 10L); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getMaximumQueueDepth()); + } + + public void testGetMaximumMessageSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getMaximumMessageSize()); + + // Check value we set + _config.setProperty("maximumMessageSize", 10L); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getMaximumMessageSize()); + } + + public void testGetMinimumAlertRepeatGap() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(0, serverConfig.getMinimumAlertRepeatGap()); + + // Check value we set + _config.setProperty("minimumAlertRepeatGap", 10L); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getMinimumAlertRepeatGap()); + } + + public void testGetProcessors() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(4, serverConfig.getProcessors()); + + // Check value we set + _config.setProperty("connector.processors", 10); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getProcessors()); + } + + public void testGetPort() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(5672, serverConfig.getPort()); + + // Check value we set + _config.setProperty("connector.port", 10); + serverConfig = new ServerConfiguration(_config); + assertEquals(10, serverConfig.getPort()); + } + + public void testGetBind() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals("wildcard", serverConfig.getBind()); + + // Check value we set + _config.setProperty("connector.bind", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getBind()); + } + + public void testGetReceiveBufferSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(32767, serverConfig.getReceiveBufferSize()); + + // Check value we set + _config.setProperty("connector.socketReceiveBuffer", "23"); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getReceiveBufferSize()); + } + + public void testGetWriteBufferSize() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(32767, serverConfig.getWriteBufferSize()); + + // Check value we set + _config.setProperty("connector.socketWriteBuffer", "23"); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getWriteBufferSize()); + } + + public void testGetTcpNoDelay() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getTcpNoDelay()); + + // Check value we set + _config.setProperty("connector.tcpNoDelay", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getTcpNoDelay()); + } + + public void testGetEnableExecutorPool() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getEnableExecutorPool()); + + // Check value we set + _config.setProperty("advanced.filterchain[@enableExecutorPool]", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getEnableExecutorPool()); + } + + public void testGetEnablePooledAllocator() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getEnablePooledAllocator()); + + // Check value we set + _config.setProperty("advanced.enablePooledAllocator", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getEnablePooledAllocator()); + } + + public void testGetEnableDirectBuffers() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getEnableDirectBuffers()); + + // Check value we set + _config.setProperty("advanced.enableDirectBuffers", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getEnableDirectBuffers()); + } + + public void testGetEnableSSL() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getEnableSSL()); + + // Check value we set + _config.setProperty("connector.ssl.enabled", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getEnableSSL()); + } + + public void testGetSSLOnly() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getSSLOnly()); + + // Check value we set + _config.setProperty("connector.ssl.sslOnly", false); + serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getSSLOnly()); + } + + public void testGetSSLPort() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(8672, serverConfig.getSSLPort()); + + // Check value we set + _config.setProperty("connector.ssl.port", 23); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getSSLPort()); + } + + public void testGetKeystorePath() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals("none", serverConfig.getKeystorePath()); + + // Check value we set + _config.setProperty("connector.ssl.keystorePath", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getKeystorePath()); + } + + public void testGetKeystorePassword() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals("none", serverConfig.getKeystorePassword()); + + // Check value we set + _config.setProperty("connector.ssl.keystorePassword", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getKeystorePassword()); + } + + public void testGetCertType() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals("SunX509", serverConfig.getCertType()); + + // Check value we set + _config.setProperty("connector.ssl.certType", "a"); + serverConfig = new ServerConfiguration(_config); + assertEquals("a", serverConfig.getCertType()); + } + + public void testGetQpidNIO() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getQpidNIO()); + + // Check value we set + _config.setProperty("connector.qpidnio", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getQpidNIO()); + } + + public void testGetUseBiasedWrites() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(false, serverConfig.getUseBiasedWrites()); + + // Check value we set + _config.setProperty("advanced.useWriteBiasedPool", true); + serverConfig = new ServerConfiguration(_config); + assertEquals(true, serverConfig.getUseBiasedWrites()); + } + + public void testGetHousekeepingExpiredMessageCheckPeriod() throws ConfigurationException + { + // Check default + ServerConfiguration serverConfig = new ServerConfiguration(_config); + assertEquals(30000, serverConfig.getHousekeepingCheckPeriod()); + + // Check value we set + _config.setProperty("housekeeping.expiredMessageCheckPeriod", 23L); + serverConfig = new ServerConfiguration(_config); + assertEquals(23, serverConfig.getHousekeepingCheckPeriod()); + serverConfig.setHousekeepingExpiredMessageCheckPeriod(42L); + assertEquals(42, serverConfig.getHousekeepingCheckPeriod()); + } + + public void testSingleConfiguration() throws IOException, ConfigurationException + { + File fileA = File.createTempFile(getClass().getName(), null); + fileA.deleteOnExit(); + FileWriter out = new FileWriter(fileA); + out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>"); + out.close(); + ServerConfiguration conf = new ServerConfiguration(fileA); + assertEquals(4235, conf.getSSLPort()); + } + + public void testCombinedConfiguration() throws IOException, ConfigurationException + { + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker><connector><port>2342</port><ssl><port>4235</port></ssl></connector></broker>"); + out.close(); + + out = new FileWriter(fileB); + out.write("<broker><connector><ssl><port>2345</port></ssl><qpidnio>true</qpidnio></connector></broker>"); + out.close(); + + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + assertEquals(4235, config.getSSLPort()); // From first file, not + // overriden by second + assertEquals(2342, config.getPort()); // From the first file, not + // present in the second + assertEquals(true, config.getQpidNIO()); // From the second file, not + // present in the first + } + + public void testVariableInterpolation() throws Exception + { + File mainFile = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<broker>\n"); + out.write("\t<work>foo</work>\n"); + out.write("\t<management><ssl><keyStorePath>${work}</keyStorePath></ssl></management>\n"); + out.write("</broker>\n"); + out.close(); + + ServerConfiguration config = new ServerConfiguration(mainFile.getAbsoluteFile()); + assertEquals("Did not get correct interpolated value", + "foo", config.getManagementKeyStorePath()); + } + + public void testCombinedConfigurationFirewall() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<access>/dev/null</access>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + out.write("\t<virtualhosts>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>test</name>\n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + TestIoSession iosession = new TestIoSession(); + iosession.setAddress("127.0.0.1"); + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test"); + AMQCodecFactory codecFactory = new AMQCodecFactory(true); + AMQProtocolSession session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory); + assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost)); + } + + public void testCombinedConfigurationFirewallReload() throws Exception + { + // Write out config + File mainFile = File.createTempFile(getClass().getName(), null); + File fileA = File.createTempFile(getClass().getName(), null); + File fileB = File.createTempFile(getClass().getName(), null); + + mainFile.deleteOnExit(); + fileA.deleteOnExit(); + fileB.deleteOnExit(); + + FileWriter out = new FileWriter(mainFile); + out.write("<configuration><system/>"); + out.write("<xml fileName=\"" + fileA.getAbsolutePath() + "\"/>"); + out.write("</configuration>"); + out.close(); + + out = new FileWriter(fileA); + out.write("<broker>\n"); + out.write("\t<management><enabled>false</enabled></management>\n"); + out.write("\t<security>\n"); + out.write("\t\t<principal-databases>\n"); + out.write("\t\t\t<principal-database>\n"); + out.write("\t\t\t\t<name>passwordfile</name>\n"); + out.write("\t\t\t\t<class>org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase</class>\n"); + out.write("\t\t\t\t<attributes>\n"); + out.write("\t\t\t\t\t<attribute>\n"); + out.write("\t\t\t\t\t\t<name>passwordFile</name>\n"); + out.write("\t\t\t\t\t\t<value>/dev/null</value>\n"); + out.write("\t\t\t\t\t</attribute>\n"); + out.write("\t\t\t\t</attributes>\n"); + out.write("\t\t\t</principal-database>\n"); + out.write("\t\t</principal-databases>\n"); + out.write("\t\t<jmx>\n"); + out.write("\t\t\t<access>/dev/null</access>\n"); + out.write("\t\t\t<principal-database>passwordfile</principal-database>\n"); + out.write("\t\t</jmx>\n"); + out.write("\t\t<firewall>\n"); + out.write("\t\t\t<xml fileName=\"" + fileB.getAbsolutePath() + "\"/>"); + out.write("\t\t</firewall>\n"); + out.write("\t</security>\n"); + out.write("\t<virtualhosts>\n"); + out.write("\t\t<virtualhost>\n"); + out.write("\t\t\t<name>test</name>\n"); + out.write("\t\t</virtualhost>\n"); + out.write("\t</virtualhosts>\n"); + out.write("</broker>\n"); + out.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + // Load config + ApplicationRegistry reg = new ConfigurationFileApplicationRegistry(mainFile); + ApplicationRegistry.initialise(reg, 1); + + // Test config + TestIoSession iosession = new TestIoSession(); + iosession.setAddress("127.0.0.1"); + VirtualHostRegistry virtualHostRegistry = reg.getVirtualHostRegistry(); + VirtualHost virtualHost = virtualHostRegistry.getVirtualHost("test"); + AMQCodecFactory codecFactory = new AMQCodecFactory(true); + AMQProtocolSession session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory); + assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost)); + + RandomAccessFile fileBRandom = new RandomAccessFile(fileB, "rw"); + fileBRandom.setLength(0); + fileBRandom.seek(0); + fileBRandom.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"allow\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + reg.getConfiguration().reparseConfigFile(); + + assertTrue(reg.getAccessManager().authoriseConnect(session, virtualHost)); + + fileBRandom = new RandomAccessFile(fileB, "rw"); + fileBRandom.setLength(0); + fileBRandom.seek(0); + fileBRandom.close(); + + out = new FileWriter(fileB); + out.write("<firewall>\n"); + out.write("\t<rule access=\"deny\" network=\"127.0.0.1\"/>"); + out.write("</firewall>\n"); + out.close(); + + reg.getConfiguration().reparseConfigFile(); + + assertFalse(reg.getAccessManager().authoriseConnect(session, virtualHost)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java new file mode 100644 index 0000000000..3b83190e42 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/TestPropertyUtils.java @@ -0,0 +1,50 @@ +/* + * + * 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.configuration; + +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.configuration.PropertyUtils; + +import junit.framework.TestCase; + +// TODO: This belongs in the "common" module. +public class TestPropertyUtils extends TestCase +{ + public void testSimpleExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}"); + assertEquals(expandedProperty, "fruity"); + } + + public void testDualExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + System.setProperty("concrete", "horrible"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}xyz${concrete}"); + assertEquals(expandedProperty, "fruityxyzhorrible"); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(TestPropertyUtils.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java new file mode 100644 index 0000000000..ba504d3064 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java @@ -0,0 +1,124 @@ +/* + * 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.configuration; + + +import junit.framework.TestCase; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.AMQPriorityQueue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class VirtualHostConfigurationTest extends TestCase +{ + + private VirtualHostConfiguration vhostConfig; + private XMLConfiguration configXml; + + @Override + protected void setUp() throws Exception + { + // Fill config file with stuff + configXml = new XMLConfiguration(); + configXml.setRootElementName("virtualhosts"); + configXml.addProperty("virtualhost(-1).name", "test"); + } + + public void testQueuePriority() throws Exception + { + // Set up queue with 5 priorities + configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)", + "atest"); + configXml.addProperty("virtualhost.test.queues.queue.atest(-1).exchange", + "amq.direct"); + configXml.addProperty("virtualhost.test.queues.queue.atest.priorities", + "5"); + + // Set up queue with JMS style priorities + configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)", + "ptest"); + configXml.addProperty("virtualhost.test.queues.queue.ptest(-1).exchange", + "amq.direct"); + configXml.addProperty("virtualhost.test.queues.queue.ptest.priority", + "true"); + + // Set up queue with no priorities + configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)", + "ntest"); + configXml.addProperty("virtualhost.test.queues.queue.ntest(-1).exchange", + "amq.direct"); + configXml.addProperty("virtualhost.test.queues.queue.ntest.priority", + "false"); + + VirtualHost vhost = new VirtualHost(new VirtualHostConfiguration("test", configXml.subset("virtualhost.test"))); + + // Check that atest was a priority queue with 5 priorities + AMQQueue atest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest")); + assertTrue(atest instanceof AMQPriorityQueue); + assertEquals(5, ((AMQPriorityQueue) atest).getPriorities()); + + // Check that ptest was a priority queue with 10 priorities + AMQQueue ptest = vhost.getQueueRegistry().getQueue(new AMQShortString("ptest")); + assertTrue(ptest instanceof AMQPriorityQueue); + assertEquals(10, ((AMQPriorityQueue) ptest).getPriorities()); + + // Check that ntest wasn't a priority queue + AMQQueue ntest = vhost.getQueueRegistry().getQueue(new AMQShortString("ntest")); + assertFalse(ntest instanceof AMQPriorityQueue); + } + + public void testQueueAlerts() throws Exception + { + // Set up queue with 5 priorities + configXml.addProperty("virtualhost.test.queues.exchange", "amq.topic"); + configXml.addProperty("virtualhost.test.queues.maximumQueueDepth", "1"); + configXml.addProperty("virtualhost.test.queues.maximumMessageSize", "2"); + configXml.addProperty("virtualhost.test.queues.maximumMessageAge", "3"); + + configXml.addProperty("virtualhost.test.queues(-1).queue(1).name(1)", "atest"); + configXml.addProperty("virtualhost.test.queues.queue.atest(-1).exchange", "amq.direct"); + configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumQueueDepth", "4"); + configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumMessageSize", "5"); + configXml.addProperty("virtualhost.test.queues.queue.atest(-1).maximumMessageAge", "6"); + + configXml.addProperty("virtualhost.test.queues(-1).queue(-1).name(-1)", "btest"); + + VirtualHost vhost = new VirtualHost(new VirtualHostConfiguration("test", configXml.subset("virtualhost.test"))); + + // Check specifically configured values + AMQQueue aTest = vhost.getQueueRegistry().getQueue(new AMQShortString("atest")); + assertEquals(4, aTest.getMaximumQueueDepth()); + assertEquals(5, aTest.getMaximumMessageSize()); + assertEquals(6, aTest.getMaximumMessageAge()); + + // Check default values + AMQQueue bTest = vhost.getQueueRegistry().getQueue(new AMQShortString("btest")); + assertEquals(1, bTest.getMaximumQueueDepth()); + assertEquals(2, bTest.getMaximumMessageSize()); + assertEquals(3, bTest.getMaximumMessageAge()); + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java new file mode 100644 index 0000000000..6dcb187a37 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java @@ -0,0 +1,562 @@ +/* + * + * 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.exchange; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.log4j.Logger; + +import java.util.*; + +public class AbstractHeadersExchangeTestBase extends TestCase +{ + private static final Logger _log = Logger.getLogger(AbstractHeadersExchangeTestBase.class); + + private final HeadersExchange exchange = new HeadersExchange(); + protected final Set<TestQueue> queues = new HashSet<TestQueue>(); + + /** + * Not used in this test, just there to stub out the routing calls + */ + private MessageStore _store = new MemoryMessageStore(); + + private StoreContext _storeContext = new StoreContext(); + + private MessageHandleFactory _handleFactory = new MessageHandleFactory(); + + private int count; + + public void testDoNothing() + { + // this is here only to make junit under Eclipse happy + } + + protected TestQueue bindDefault(String... bindings) throws AMQException + { + return bind("Queue" + (++count), bindings); + } + + protected TestQueue bind(String queueName, String... bindings) throws AMQException + { + return bind(queueName, getHeaders(bindings)); + } + + protected TestQueue bind(String queue, FieldTable bindings) throws AMQException + { + return bind(new TestQueue(new AMQShortString(queue)), bindings); + } + + protected TestQueue bind(TestQueue queue, String... bindings) throws AMQException + { + return bind(queue, getHeaders(bindings)); + } + + protected TestQueue bind(TestQueue queue, FieldTable bindings) throws AMQException + { + queues.add(queue); + exchange.registerQueue(null, queue, bindings); + return queue; + } + + + protected void route(Message m) throws AMQException + { + m.route(exchange); + m.getIncomingMessage().routingComplete(_store, _handleFactory); + if(m.getIncomingMessage().allContentReceived()) + { + m.getIncomingMessage().deliverToQueues(); + } + } + + protected void routeAndTest(Message m, TestQueue... expected) throws AMQException + { + routeAndTest(m, false, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, boolean expectReturn, TestQueue... expected) throws AMQException + { + routeAndTest(m, expectReturn, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, List<TestQueue> expected) throws AMQException + { + routeAndTest(m, false, expected); + } + + protected void routeAndTest(Message m, boolean expectReturn, List<TestQueue> expected) throws AMQException + { + try + { + route(m); + assertFalse("Expected "+m+" to be returned due to manadatory flag, and lack of routing",expectReturn); + for (TestQueue q : queues) + { + if (expected.contains(q)) + { + assertTrue("Expected " + m + " to be delivered to " + q, q.isInQueue(m)); + //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q; + } + else + { + assertFalse("Did not expect " + m + " to be delivered to " + q, q.isInQueue(m)); + //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q; + } + } + } + + catch (NoRouteException ex) + { + assertTrue("Expected "+m+" not to be returned",expectReturn); + } + + } + + static FieldTable getHeaders(String... entries) + { + FieldTable headers = FieldTableFactory.newFieldTable(); + for (String s : entries) + { + String[] parts = s.split("=", 2); + headers.setObject(parts[0], parts.length > 1 ? parts[1] : ""); + } + return headers; + } + + + static final class MessagePublishInfoImpl implements MessagePublishInfo + { + private AMQShortString _exchange; + private boolean _immediate; + private boolean _mandatory; + private AMQShortString _routingKey; + + public MessagePublishInfoImpl(AMQShortString routingKey) + { + _routingKey = routingKey; + } + + public MessagePublishInfoImpl(AMQShortString exchange, boolean immediate, boolean mandatory, AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange; + } + + public boolean isImmediate() + { + return _immediate; + + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + + + public void setExchange(AMQShortString exchange) + { + _exchange = exchange; + } + + public void setImmediate(boolean immediate) + { + _immediate = immediate; + } + + public void setMandatory(boolean mandatory) + { + _mandatory = mandatory; + } + + public void setRoutingKey(AMQShortString routingKey) + { + _routingKey = routingKey; + } + } + + static MessagePublishInfo getPublishRequest(final String id) + { + return new MessagePublishInfoImpl(null, false, false, new AMQShortString(id)); + } + + static ContentHeaderBody getContentHeader(FieldTable headers) + { + ContentHeaderBody header = new ContentHeaderBody(); + header.properties = getProperties(headers); + return header; + } + + static BasicContentHeaderProperties getProperties(FieldTable headers) + { + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setHeaders(headers); + return properties; + } + + static class TestQueue extends SimpleAMQQueue + { + final List<HeadersExchangeTest.Message> messages = new ArrayList<HeadersExchangeTest.Message>(); + + public TestQueue(AMQShortString name) throws AMQException + { + super(name, false, new AMQShortString("test"), true, ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test")); + ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test").getQueueRegistry().registerQueue(this); + } + + /** + * We override this method so that the default behaviour, which attempts to use a delivery manager, is + * not invoked. It is unnecessary since for this test we only care to know whether the message was + * sent to the queue; the queue processing logic is not being tested. + * @param msg + * @throws AMQException + */ + @Override + public QueueEntry enqueue(StoreContext context, AMQMessage msg) throws AMQException + { + messages.add( new HeadersExchangeTest.Message(msg)); + return new QueueEntry() + { + + public AMQQueue getQueue() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQMessage getMessage() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getSize() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean getDeliveredToConsumer() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean expired() throws AMQException + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAcquired() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean acquire() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean acquire(Subscription sub) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean delete() + { + return false; + } + + public boolean isDeleted() + { + return false; + } + + public boolean acquiredBySubscription() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setDeliveredToSubscription() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void release() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public String debugIdentity() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean immediateAndNotDelivered() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setRedelivered(boolean b) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Subscription getDeliveredSubscription() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void reject() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void reject(Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isRejectedBy(Subscription subscription) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void requeue(StoreContext storeContext) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeue(final StoreContext storeContext) throws FailedDequeueException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dispose(final StoreContext storeContext) throws MessageCleanupException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void restoreCredit() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isQueueDeleted() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void addStateChangeListener(StateChangeListener listener) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean removeStateChangeListener(StateChangeListener listener) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int compareTo(final QueueEntry o) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + }; + } + + boolean isInQueue(Message msg) + { + return messages.contains(msg); + } + + } + + /** + * Just add some extra utility methods to AMQMessage to aid testing. + */ + static class Message extends AMQMessage + { + private class TestIncomingMessage extends IncomingMessage + { + + public TestIncomingMessage(final long messageId, + final MessagePublishInfo info, + final TransactionalContext txnContext, + final AMQProtocolSession publisher) + { + super(messageId, info, txnContext, publisher); + } + + + public AMQMessage getUnderlyingMessage() + { + return Message.this; + } + + + public ContentHeaderBody getContentHeaderBody() + { + try + { + return Message.this.getContentHeaderBody(); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + } + + private IncomingMessage _incoming; + + private static MessageStore _messageStore = new SkeletonMessageStore(); + + private static StoreContext _storeContext = new StoreContext(); + + + private static TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext, + null, + new LinkedList<RequiredDeliveryException>() + ); + + Message(String id, String... headers) throws AMQException + { + this(id, getHeaders(headers)); + } + + Message(String id, FieldTable headers) throws AMQException + { + this(_messageStore.getNewMessageId(),getPublishRequest(id), getContentHeader(headers), null); + } + + public IncomingMessage getIncomingMessage() + { + return _incoming; + } + + private Message(long messageId, + MessagePublishInfo publish, + ContentHeaderBody header, + List<ContentBody> bodies) throws AMQException + { + super(createMessageHandle(messageId, publish, header), _txnContext.getStoreContext(), publish); + + + + _incoming = new TestIncomingMessage(getMessageId(),publish,_txnContext,new MockProtocolSession(_messageStore)); + _incoming.setContentHeaderBody(header); + + + } + + private static AMQMessageHandle createMessageHandle(final long messageId, + final MessagePublishInfo publish, + final ContentHeaderBody header) + { + + final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, + _messageStore, + true); + + try + { + amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(),publish,header); + } + catch (AMQException e) + { + + } + return amqMessageHandle; + } + + private Message(AMQMessage msg) throws AMQException + { + super(msg); + } + + + + void route(Exchange exchange) throws AMQException + { + exchange.route(_incoming); + } + + + public int hashCode() + { + return getKey().hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o); + } + + private boolean equals(HeadersExchangeTest.Message m) + { + return getKey().equals(m.getKey()); + } + + public String toString() + { + return getKey().toString(); + } + + private Object getKey() + { + try + { + return getMessagePublishInfo().getRoutingKey(); + } + catch (AMQException e) + { + _log.error("Error getting routing key: " + e, e); + return null; + } + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java new file mode 100644 index 0000000000..aa25e207a9 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DestWildExchangeTest.java @@ -0,0 +1,595 @@ +/* + * 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.exchange; + +import junit.framework.TestCase; +import junit.framework.Assert; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +import java.util.LinkedList; + +public class DestWildExchangeTest extends TestCase +{ + + TopicExchange _exchange; + + VirtualHost _vhost; + MessageStore _store; + StoreContext _context; + + InternalTestProtocolSession _protocolSession; + + + public void setUp() throws AMQException + { + _exchange = new TopicExchange(); + _vhost = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts().iterator().next(); + _store = new MemoryMessageStore(); + _context = new StoreContext(); + _protocolSession = new InternalTestProtocolSession(); + } + + public void tearDown() + { + ApplicationRegistry.remove(1); + } + + + public void testNoRoute() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*#b"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null); + + + MessagePublishInfo info = new PublishInfo(new AMQShortString("a.b")); + + IncomingMessage message = new IncomingMessage(0L, info, null, _protocolSession); + + _exchange.route(message); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testDirectMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("ab"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.b"), queue, null); + + + IncomingMessage message = createMessage("a.b"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + try + { + routeMessage(message); + fail("Message has no route and should fail to be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testStarMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a*"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.*"), queue, null); + + + IncomingMessage message = createMessage("a.b"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a"); + + try + { + routeMessage(message); + fail("Message has no route and should fail to be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testHashMatch() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.#"), queue, null); + + + IncomingMessage message = createMessage("a.b.c"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.b"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.c"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("b"); + + try + { + routeMessage(message); + fail("Message has no route and should fail to be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testMidHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.*.#.b"), queue, null); + + + IncomingMessage message = createMessage("a.c.d.b"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.c.b"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMatchafterHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.*.#.b.c"), queue, null); + + + IncomingMessage message = createMessage("a.c.b.b"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.a.b.c"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.b.c.b"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.b.c.b.c"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + + public void testHashAfterHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.*.#.b.c.#.d"), queue, null); + + + IncomingMessage message = createMessage("a.c.b.b.c"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + + message = createMessage("a.a.b.c.d"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testHashHash() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a#"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.#.*.#.d"), queue, null); + + + IncomingMessage message = createMessage("a.c.b.b.c"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + message = createMessage("a.a.b.c.d"); + + try + { + routeMessage(message); + } + catch (AMQException nre) + { + fail("Message has no route and should be routed"); + } + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message recevied", (Object) message.getMessageId(), queue.getMessagesOnTheQueue().get(0).getMessage().getMessageId()); + + queue.deleteMessageFromTop(_context); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testSubMatchFails() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.b.c.d"), queue, null); + + + IncomingMessage message = createMessage("a.b.c"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + private void routeMessage(final IncomingMessage message) + throws AMQException + { + _exchange.route(message); + message.routingComplete(_store, new MessageHandleFactory()); + message.deliverToQueues(); + } + + public void testMoreRouting() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.b"), queue, null); + + + IncomingMessage message = createMessage("a.b.c"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMoreQueue() throws AMQException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("a"), false, null, false, _vhost, null); + _exchange.registerQueue(new AMQShortString("a.b"), queue, null); + + + IncomingMessage message = createMessage("a"); + + try + { + routeMessage(message); + fail("Message has route and should not be routed"); + } + catch (AMQException nre) + { + } + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + private IncomingMessage createMessage(String s) throws AMQException + { + MessagePublishInfo info = new PublishInfo(new AMQShortString(s)); + + TransactionalContext trancontext = new NonTransactionalContext(_store, _context, null, + new LinkedList<RequiredDeliveryException>() + ); + + IncomingMessage message = new IncomingMessage(0L, info, trancontext,_protocolSession); + message.setContentHeaderBody( new ContentHeaderBody()); + + + return message; + } + + + class PublishInfo implements MessagePublishInfo + { + AMQShortString _routingkey; + + PublishInfo(AMQShortString routingkey) + { + _routingkey = routingkey; + } + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return true; + } + + public AMQShortString getRoutingKey() + { + return _routingkey; + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java new file mode 100644 index 0000000000..8ce7b4c0e1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/ExchangeMBeanTest.java @@ -0,0 +1,145 @@ +/* + * + * 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.exchange; + +import junit.framework.TestCase; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; + +import javax.management.openmbean.TabularData; +import java.util.ArrayList; + +/** + * Unit test class for testing different Exchange MBean operations + */ +public class ExchangeMBeanTest extends TestCase +{ + private AMQQueue _queue; + private QueueRegistry _queueRegistry; + private VirtualHost _virtualHost; + + /** + * Test for direct exchange mbean + * @throws Exception + */ + + public void testDirectExchangeMBean() throws Exception + { + DirectExchange exchange = new DirectExchange(); + exchange.initialise(_virtualHost, ExchangeDefaults.DIRECT_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getName().toString(), "binding1"); + mbean.createNewBinding(_queue.getName().toString(), "binding2"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.direct"); + assertEquals(mbean.getExchangeType(), "direct"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + /** + * Test for "topic" exchange mbean + * @throws Exception + */ + + public void testTopicExchangeMBean() throws Exception + { + TopicExchange exchange = new TopicExchange(); + exchange.initialise(_virtualHost,ExchangeDefaults.TOPIC_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getName().toString(), "binding1"); + mbean.createNewBinding(_queue.getName().toString(), "binding2"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.topic"); + assertEquals(mbean.getExchangeType(), "topic"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + /** + * Test for "Headers" exchange mbean + * @throws Exception + */ + + public void testHeadersExchangeMBean() throws Exception + { + HeadersExchange exchange = new HeadersExchange(); + exchange.initialise(_virtualHost,ExchangeDefaults.HEADERS_EXCHANGE_NAME, false, 0, true); + ManagedObject managedObj = exchange.getManagedObject(); + ManagedExchange mbean = (ManagedExchange)managedObj; + + mbean.createNewBinding(_queue.getName().toString(), "key1=binding1,key2=binding2"); + mbean.createNewBinding(_queue.getName().toString(), "key3=binding3"); + + TabularData data = mbean.bindings(); + ArrayList<Object> list = new ArrayList<Object>(data.values()); + assertTrue(list.size() == 2); + + // test general exchange properties + assertEquals(mbean.getName(), "amq.match"); + assertEquals(mbean.getExchangeType(), "headers"); + assertTrue(mbean.getTicketNo() == 0); + assertTrue(!mbean.isDurable()); + assertTrue(mbean.isAutoDelete()); + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1); + _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test"); + _queueRegistry = _virtualHost.getQueueRegistry(); + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("ExchangeMBeanTest"), false, _virtualHost, + null); + _queueRegistry.registerQueue(_queue); + } + + protected void tearDown() + { + ApplicationRegistry.remove(1); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java new file mode 100644 index 0000000000..86ba96bf5d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java @@ -0,0 +1,199 @@ +/* + * + * 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.exchange; + +import java.util.Map; +import java.util.HashMap; + +import junit.framework.TestCase; +import org.apache.qpid.framing.FieldTable; + +/** + */ +public class HeadersBindingTest extends TestCase +{ + private FieldTable bindHeaders = new FieldTable(); + private FieldTable matchHeaders = new FieldTable(); + + public void testDefault_1() + { + bindHeaders.setString("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testDefault_2() + { + bindHeaders.setString("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testDefault_3() + { + bindHeaders.setString("A", "Value of A"); + + matchHeaders.setString("A", "Altered value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAll_1() + { + bindHeaders.setString("X-match", "all"); + bindHeaders.setString("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAll_2() + { + bindHeaders.setString("X-match", "all"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAll_3() + { + bindHeaders.setString("X-match", "all"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAll_4() + { + bindHeaders.setString("X-match", "all"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAll_5() + { + bindHeaders.setString("X-match", "all"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_1() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_2() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_3() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_4() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_5() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public void testAny_6() + { + bindHeaders.setString("X-match", "any"); + bindHeaders.setString("A", "Value of A"); + bindHeaders.setString("B", "Value of B"); + + matchHeaders.setString("A", "Altered value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersBindingTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java new file mode 100644 index 0000000000..fd11ddeae2 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java @@ -0,0 +1,106 @@ +/* + * + * 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.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.NullApplicationRegistry; +import org.apache.qpid.framing.BasicPublishBody; + +public class HeadersExchangeTest extends AbstractHeadersExchangeTestBase +{ + protected void setUp() throws Exception + { + super.setUp(); + ApplicationRegistry.initialise(new NullApplicationRegistry(), 1); + } + + protected void tearDown() + { + ApplicationRegistry.remove(1); + } + + public void testSimple() throws AMQException + { + TestQueue q1 = bindDefault("F0000"); + TestQueue q2 = bindDefault("F0000=Aardvark"); + TestQueue q3 = bindDefault("F0001"); + TestQueue q4 = bindDefault("F0001=Bear"); + TestQueue q5 = bindDefault("F0000", "F0001"); + TestQueue q6 = bindDefault("F0000=Aardvark", "F0001=Bear"); + TestQueue q7 = bindDefault("F0000", "F0001=Bear"); + TestQueue q8 = bindDefault("F0000=Aardvark", "F0001"); + + routeAndTest(new Message("Message1", "F0000"), q1); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q5, q8); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q3, q4, q5, q7); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), + q1, q2, q3, q4, q5, q6, q7, q8); + routeAndTest(new Message("Message6", "F0002")); + + Message m7 = new Message("Message7", "XXXXX"); + + MessagePublishInfoImpl pb7 = (MessagePublishInfoImpl) (m7.getMessagePublishInfo()); + pb7.setMandatory(true); + routeAndTest(m7,true); + + Message m8 = new Message("Message8", "F0000"); + MessagePublishInfoImpl pb8 = (MessagePublishInfoImpl)(m8.getMessagePublishInfo()); + pb8.setMandatory(true); + routeAndTest(m8,false,q1); + + + } + + public void testAny() throws AMQException + { + TestQueue q1 = bindDefault("F0000", "F0001", "X-match=any"); + TestQueue q2 = bindDefault("F0000=Aardvark", "F0001=Bear", "X-match=any"); + TestQueue q3 = bindDefault("F0000", "F0001=Bear", "X-match=any"); + TestQueue q4 = bindDefault("F0000=Aardvark", "F0001", "X-match=any"); + TestQueue q6 = bindDefault("F0000=Apple", "F0001", "X-match=any"); + + routeAndTest(new Message("Message1", "F0000"), q1, q3); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2, q3, q4); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message6", "F0002")); + } + + public void testMandatory() throws AMQException + { + bindDefault("F0000"); + Message m1 = new Message("Message1", "XXXXX"); + Message m2 = new Message("Message2", "F0000"); + MessagePublishInfoImpl pb1 = (MessagePublishInfoImpl) (m1.getMessagePublishInfo()); + pb1.setMandatory(true); + MessagePublishInfoImpl pb2 = (MessagePublishInfoImpl) (m2.getMessagePublishInfo()); + pb2.setMandatory(true); + routeAndTest(m1,true); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersExchangeTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java new file mode 100644 index 0000000000..40153be331 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/logging/management/LoggingManagementMBeanTest.java @@ -0,0 +1,413 @@ +/* + * 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.logging.management; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularDataSupport; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import junit.framework.TestCase; + +public class LoggingManagementMBeanTest extends TestCase +{ + private static final String TEST_LOGGER = "LoggingManagementMBeanTestLogger"; + private static final String TEST_LOGGER_CHILD1 = "LoggingManagementMBeanTestLogger.child1"; + private static final String TEST_LOGGER_CHILD2 = "LoggingManagementMBeanTestLogger.child2"; + + private static final String CATEGORY_PRIORITY = "LogManMBeanTest.category.priority"; + private static final String CATEGORY_LEVEL = "LogManMBeanTest.category.level"; + private static final String LOGGER_LEVEL = "LogManMBeanTest.logger.level"; + + private static final String NAME_INDEX = LoggingManagement.COMPOSITE_ITEM_NAMES[0]; + private static final String LEVEL_INDEX = LoggingManagement.COMPOSITE_ITEM_NAMES[1]; + + private static final String NEWLINE = System.getProperty("line.separator"); + + private File _testConfigFile; + + protected void setUp() throws Exception + { + _testConfigFile = createTempTestLog4JConfig(); + } + + private File createTempTestLog4JConfig() + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE); + writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE); + + writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " + + "threshold=\"null\">"+NEWLINE); + + writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE); + writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE); + writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE); + writer.write(" </layout>"+NEWLINE); + writer.write(" </appender>"+NEWLINE); + + //Example of a 'category' with a 'priority' + writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_PRIORITY +"\">"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'category' with a 'level' + writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_LEVEL +"\">"+NEWLINE); + writer.write(" <level value=\"warn\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </category>"+NEWLINE); + + //Example of a 'logger' with a 'level' + writer.write(" <logger additivity=\"true\" name=\"" + LOGGER_LEVEL + "\">"+NEWLINE); + writer.write(" <level value=\"error\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </logger>"+NEWLINE); + + //'root' logger + writer.write(" <root>"+NEWLINE); + writer.write(" <priority value=\"info\"/>"+NEWLINE); + writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE); + writer.write(" </root>"+NEWLINE); + + writer.write("</log4j:configuration>"+NEWLINE); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create temporary test log4j configuration"); + } + + return tmpFile; + } + + + + //******* Test Methods ******* // + + public void testSetRuntimeLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //create child1 test logger, check its *effective* level is the same as the parent, "info" + Logger log1 = Logger.getLogger(TEST_LOGGER_CHILD1); + assertTrue("Test logger's level was not the expected value", + log1.getEffectiveLevel().toString().equalsIgnoreCase("info")); + + //now change its level to "warn" + assertTrue("Failed to set logger level", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "warn")); + + //check the change, see its actual level is "warn + assertTrue("Test logger's level was not the expected value", + log1.getLevel().toString().equalsIgnoreCase("warn")); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeLoggerLevel(TEST_LOGGER_CHILD1, "made.up.level")); + } + + public void testSetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to info + assertTrue("Failed to set root logger level", lm.setRuntimeRootLoggerLevel("debug")); + //check it is now actually info + Level currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("debug"))); + + //try an invalid level + assertFalse("Trying to set an invalid level succeded", lm.setRuntimeRootLoggerLevel("made.up.level")); + + //set root loggers level to warn + assertTrue("Failed to set logger level", lm.setRuntimeRootLoggerLevel("info")); + //check it is now actually warn + currentLevel = log.getLevel(); + assertTrue("Logger level was not expected value", currentLevel.equals(Level.toLevel("info"))); + + //restore original level + log.setLevel(origLevel); + } + + public void testGetRuntimeRootLoggerLevel() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + Logger log = Logger.getRootLogger(); + + //get current root logger level + Level origLevel = log.getLevel(); + + //change level twice to ensure a new level is actually selected + + //set root loggers level to debug + log.setLevel(Level.toLevel("debug")); + //check it is now actually debug + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("debug")); + + + //set root loggers level to warn + log.setLevel(Level.toLevel("info")); + //check it is now actually warn + assertTrue("Logger level was not expected value", lm.getRuntimeRootLoggerLevel().equalsIgnoreCase("info")); + + //restore original level + log.setLevel(origLevel); + } + + public void testViewEffectiveRuntimeLoggerLevels() + { + LoggingManagementMBean lm = null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //(re)create a parent test logger, set its level explicitly + Logger log = Logger.getLogger(TEST_LOGGER); + log.setLevel(Level.toLevel("info")); + + //retrieve the current effective runtime logger level values + TabularDataSupport levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString()); + } + + //check child2 does not exist already + assertFalse("Did not expect this logger to exist already", list.containsKey(TEST_LOGGER_CHILD2)); + + //create child2 test logger + Logger log2 = Logger.getLogger(TEST_LOGGER_CHILD2); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString()); + } + + //verify the parent and child2 loggers are present in returned values + assertTrue(TEST_LOGGER + " logger was not in the returned list", list.containsKey(TEST_LOGGER)); + assertTrue(TEST_LOGGER_CHILD2 + " logger was not in the returned list", list.containsKey(TEST_LOGGER_CHILD2)); + + //check child2's effective level is the same as the parent, "info" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("info")); + + //now change its level explicitly to "warn" + log2.setLevel(Level.toLevel("warn")); + + //retrieve the current effective runtime logger level values + levels = (TabularDataSupport) lm.viewEffectiveRuntimeLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString()); + } + + //check child2's effective level is now "warn" + assertTrue("Test logger's level was not the expected value", + list.get(TEST_LOGGER_CHILD2).equalsIgnoreCase("warn")); + } + + public void testViewAndSetConfigFileLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current values + TabularDataSupport levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + Collection<Object> records = levels.values(); + Map<String,String> list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved before update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(CATEGORY_PRIORITY)); + assertTrue(CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(CATEGORY_LEVEL)); + assertTrue(LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(LOGGER_LEVEL)); + + //check that their level is as expected + assertTrue(CATEGORY_PRIORITY + " logger's level was incorrect", list.get(CATEGORY_PRIORITY).equalsIgnoreCase("info")); + assertTrue(CATEGORY_LEVEL + " logger's level was incorrect", list.get(CATEGORY_LEVEL).equalsIgnoreCase("warn")); + assertTrue(LOGGER_LEVEL + " logger's level was incorrect", list.get(LOGGER_LEVEL).equalsIgnoreCase("error")); + + //increase their levels a notch to test the 3 different types of logger definition are successfully updated + //change the category+priority to warn + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(CATEGORY_PRIORITY, "warn")); + //change the category+level to error + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(CATEGORY_LEVEL, "error")); + //change the logger+level to trace + assertTrue("failed to set new level", lm.setConfigFileLoggerLevel(LOGGER_LEVEL, "trace")); + + //try an invalid level + assertFalse("Use of an invalid logger level was successfull", lm.setConfigFileLoggerLevel(LOGGER_LEVEL, "made.up.level")); + + //try an invalid logger name + assertFalse("Use of an invalid logger name was successfull", lm.setConfigFileLoggerLevel("made.up.logger.name", "info")); + + //retrieve the new values from the file and check them + levels = (TabularDataSupport) lm.viewConfigFileLoggerLevels(); + records = levels.values(); + list = new HashMap<String,String>(); + for (Object o : records) + { + CompositeData data = (CompositeData) o; + list.put(data.get(NAME_INDEX).toString(), data.get(LEVEL_INDEX).toString()); + } + + //check the 3 different types of logger definition are successfully retrieved after update + assertTrue("Wrong number of items in returned list", list.size() == 3); + assertTrue(CATEGORY_PRIORITY + " logger was not in the returned list", list.containsKey(CATEGORY_PRIORITY)); + assertTrue(CATEGORY_LEVEL + " logger was not in the returned list", list.containsKey(CATEGORY_LEVEL)); + assertTrue(LOGGER_LEVEL + " logger was not in the returned list", list.containsKey(LOGGER_LEVEL)); + + //check that their level is as expected after the changes + assertTrue(CATEGORY_PRIORITY + " logger's level was incorrect", list.get(CATEGORY_PRIORITY).equalsIgnoreCase("warn")); + assertTrue(CATEGORY_LEVEL + " logger's level was incorrect", list.get(CATEGORY_LEVEL).equalsIgnoreCase("error")); + assertTrue(LOGGER_LEVEL + " logger's level was incorrect", list.get(LOGGER_LEVEL).equalsIgnoreCase("trace")); + } + + public void testGetAndSetConfigFileRootLoggerLevel() throws Exception + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 0); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + //retrieve the current value + String level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved before update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("info")); + + //try an invalid level + assertFalse("Use of an invalid RootLogger level was successfull", lm.setConfigFileRootLoggerLevel("made.up.level")); + + //change the level to warn + assertTrue("Failed to set new RootLogger level", lm.setConfigFileRootLoggerLevel("warn")); + + //retrieve the current value + level = lm.getConfigFileRootLoggerLevel(); + + //check the value was successfully retrieved after update + assertTrue("Retrieved RootLogger level was incorrect", level.equalsIgnoreCase("warn")); + } + + public void testGetLog4jLogWatchInterval() + { + LoggingManagementMBean lm =null; + try + { + lm = new LoggingManagementMBean(_testConfigFile.getAbsolutePath(), 5000); + } + catch (JMException e) + { + fail("Could not create test LoggingManagementMBean"); + } + + assertTrue("Wrong value returned for logWatch period", lm.getLog4jLogWatchInterval() == 5000); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java new file mode 100644 index 0000000000..9599848dde --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/MockPluginManager.java @@ -0,0 +1,51 @@ +/* + * 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.plugins; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.ACLPluginFactory; +import org.apache.qpid.server.security.access.QueueDenier; + +public class MockPluginManager extends PluginManager +{ + + private Map<String, ACLPluginFactory> _securityPlugins = new HashMap<String, ACLPluginFactory>(); + + public MockPluginManager(String plugindir) throws Exception + { + super(plugindir); + _securityPlugins.put("org.apache.qpid.server.security.access.QueueDenier", QueueDenier.FACTORY); + } + + @Override + public Map<String, ExchangeType<?>> getExchanges() + { + return null; + } + + @Override + public Map<String, ACLPluginFactory> getSecurityPlugins() + { + return _securityPlugins; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.java new file mode 100644 index 0000000000..11d6105704 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/plugins/PluginTest.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.plugins; + +import java.util.Map; + +import org.apache.qpid.server.exchange.ExchangeType; + +import junit.framework.TestCase; + +public class PluginTest extends TestCase +{ + + private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; + private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); + + public void testLoadExchanges() throws Exception + { + PluginManager manager = new PluginManager(PLUGIN_DIRECTORY); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertNotNull("No exchanges found in "+PLUGIN_DIRECTORY, exchanges); + assertEquals("Wrong number of exchanges found in "+PLUGIN_DIRECTORY, + 2, exchanges.size()); + assertNotNull("Wrong exchange found in "+PLUGIN_DIRECTORY, + exchanges.get(TEST_EXCHANGE_CLASS)); + } + + public void testNoExchanges() throws Exception + { + PluginManager manager = new PluginManager("/path/to/nowhere"); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertEquals("Exchanges found", 0, exchanges.size()); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java new file mode 100644 index 0000000000..d5db87350b --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java @@ -0,0 +1,124 @@ +/* + * + * 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.protocol; + +import junit.framework.TestCase; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; + +import javax.management.JMException; + +/** + * Test class to test MBean operations for AMQMinaProtocolSession. + */ +public class AMQProtocolSessionMBeanTest extends TestCase +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(AMQProtocolSessionMBeanTest.class); + + private MessageStore _messageStore = new SkeletonMessageStore(); + private AMQMinaProtocolSession _protocolSession; + private AMQChannel _channel; + private AMQProtocolSessionMBean _mbean; + + public void testChannels() throws Exception + { + // check the channel count is correct + int channelCount = _mbean.channels().size(); + assertTrue(channelCount == 1); + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue_" + System.currentTimeMillis()), + false, + new AMQShortString("test"), + true, + _protocolSession.getVirtualHost(), null); + AMQChannel channel = new AMQChannel(_protocolSession,2, _messageStore); + channel.setDefaultQueue(queue); + _protocolSession.addChannel(channel); + channelCount = _mbean.channels().size(); + assertTrue(channelCount == 2); + + // general properties test + _mbean.setMaximumNumberOfChannels(1000L); + assertTrue(_mbean.getMaximumNumberOfChannels() == 1000L); + + // check APIs + AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _messageStore); + channel3.setLocalTransactional(); + _protocolSession.addChannel(channel3); + _mbean.rollbackTransactions(2); + _mbean.rollbackTransactions(3); + _mbean.commitTransactions(2); + _mbean.commitTransactions(3); + + // This should throw exception, because the channel does't exist + try + { + _mbean.commitTransactions(4); + fail(); + } + catch (JMException ex) + { + log.debug("expected exception is thrown :" + ex.getMessage()); + } + + // check if closing of session works + _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _messageStore)); + _mbean.closeConnection(); + try + { + channelCount = _mbean.channels().size(); + assertTrue(channelCount == 0); + // session is now closed so adding another channel should throw an exception + _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _messageStore)); + fail(); + } + catch (AMQException ex) + { + log.debug("expected exception is thrown :" + ex.getMessage()); + } + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + _protocolSession = + new AMQMinaProtocolSession(new TestIoSession(), appRegistry.getVirtualHostRegistry(), new AMQCodecFactory(true), + null); + _protocolSession.setVirtualHost(appRegistry.getVirtualHostRegistry().getVirtualHost("test")); + _channel = new AMQChannel(_protocolSession, 1, _messageStore); + _protocolSession.addChannel(_channel); + _mbean = (AMQProtocolSessionMBean) _protocolSession.getManagedObject(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java new file mode 100644 index 0000000000..da35ddc594 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/InternalTestProtocolSession.java @@ -0,0 +1,180 @@ +/* + * + * 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.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.AMQChannel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class InternalTestProtocolSession extends AMQMinaProtocolSession implements ProtocolOutputConverter +{ + // ChannelID(LIST) -> LinkedList<Pair> + final Map<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>> _channelDelivers; + private AtomicInteger _deliveryCount = new AtomicInteger(0); + + public InternalTestProtocolSession() throws AMQException + { + super(new TestIoSession(), + ApplicationRegistry.getInstance().getVirtualHostRegistry(), + new AMQCodecFactory(true)); + + _channelDelivers = new HashMap<Integer, Map<AMQShortString, LinkedList<DeliveryPair>>>(); + + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return this; + } + + public byte getProtocolMajorVersion() + { + return (byte) 8; + } + + public byte getProtocolMinorVersion() + { + return (byte) 0; + } + + // *** + + public List<DeliveryPair> getDelivers(int channelId, AMQShortString consumerTag, int count) + { + synchronized (_channelDelivers) + { + List<DeliveryPair> all =_channelDelivers.get(channelId).get(consumerTag); + + if (all == null) + { + return new ArrayList<DeliveryPair>(0); + } + + List<DeliveryPair> msgs = all.subList(0, count); + + List<DeliveryPair> response = new ArrayList<DeliveryPair>(msgs); + + //Remove the msgs from the receivedList. + msgs.clear(); + + return response; + } + } + + // *** ProtocolOutputConverter Implementation + public void writeReturn(AMQMessage message, int channelId, int replyCode, AMQShortString replyText) throws AMQException + { + } + + public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag) + { + } + + public void writeDeliver(AMQMessage message, int channelId, long deliveryTag, AMQShortString consumerTag) throws AMQException + { + _deliveryCount.incrementAndGet(); + + synchronized (_channelDelivers) + { + Map<AMQShortString, LinkedList<DeliveryPair>> consumers = _channelDelivers.get(channelId); + + if (consumers == null) + { + consumers = new HashMap<AMQShortString, LinkedList<DeliveryPair>>(); + _channelDelivers.put(channelId, consumers); + } + + LinkedList<DeliveryPair> consumerDelivers = consumers.get(consumerTag); + + if (consumerDelivers == null) + { + consumerDelivers = new LinkedList<DeliveryPair>(); + consumers.put(consumerTag, consumerDelivers); + } + + consumerDelivers.add(new DeliveryPair(deliveryTag, message)); + } + } + + public void writeGetOk(AMQMessage message, int channelId, long deliveryTag, int queueSize) throws AMQException + { + } + + public void awaitDelivery(int msgs) + { + while (msgs > _deliveryCount.get()) + { + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + } + + public class DeliveryPair + { + private long _deliveryTag; + private AMQMessage _message; + + public DeliveryPair(long deliveryTag, AMQMessage message) + { + _deliveryTag = deliveryTag; + _message = message; + } + + public AMQMessage getMessage() + { + return _message; + } + + public long getDeliveryTag() + { + return _deliveryTag; + } + } + + public boolean isClosed() + { + return _closed; + } + + public void closeProtocolSession(boolean waitLast) + { + // Override as we don't have a real IOSession to close. + // The alternative is to fully implement the TestIOSession to return a CloseFuture from close(); + // Then the AMQMinaProtocolSession can join on the returning future without a NPE. + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java new file mode 100644 index 0000000000..1bdabf345b --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/MaxChannelsTest.java @@ -0,0 +1,86 @@ +/* + * + * 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.protocol; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +/** Test class to test MBean operations for AMQMinaProtocolSession. */ +public class MaxChannelsTest extends TestCase +{ + private IApplicationRegistry _appRegistry; + private AMQMinaProtocolSession _session; + + public void testChannels() throws Exception + { + _session = new AMQMinaProtocolSession(new TestIoSession(), _appRegistry + .getVirtualHostRegistry(), new AMQCodecFactory(true), null); + _session.setVirtualHost(_appRegistry.getVirtualHostRegistry().getVirtualHost("test")); + + // check the channel count is correct + int channelCount = _session.getChannels().size(); + assertEquals("Initial channel count wrong", 0, channelCount); + + long maxChannels = 10L; + _session.setMaximumNumberOfChannels(maxChannels); + assertEquals("Number of channels not correctly set.", new Long(maxChannels), _session.getMaximumNumberOfChannels()); + + + try + { + for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++) + { + _session.addChannel(new AMQChannel(_session, (int) currentChannel, null)); + } + } + catch (AMQException e) + { + assertEquals("Wrong exception recevied.", e.getErrorCode(), AMQConstant.NOT_ALLOWED); + } + assertEquals("Maximum number of channels not set.", new Long(maxChannels), new Long(_session.getChannels().size())); + } + + @Override + public void setUp() + { + _appRegistry = ApplicationRegistry.getInstance(1); + } + + @Override + public void tearDown() + { + try { + _session.closeSession(); + } catch (AMQException e) { + // Yikes + fail(e.getMessage()); + } + ApplicationRegistry.remove(1); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java new file mode 100644 index 0000000000..211f491867 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/protocol/TestIoSession.java @@ -0,0 +1,328 @@ +/* + * + * 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.protocol; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoService; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.ThreadModel; +import org.apache.mina.common.TrafficMask; +import org.apache.mina.common.TransportType; +import org.apache.mina.common.WriteFuture; +import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; +import org.apache.qpid.pool.ReadWriteThreadModel; + +/** + * Test implementation of IoSession, which is required for some tests. Methods not being used are not implemented, + * so if this class is being used and some methods are to be used, then please update those. + */ +public class TestIoSession implements IoSession +{ + private final ConcurrentMap attributes = new ConcurrentHashMap(); + private String _address = "127.0.0.1"; + private int _port = 1; + + public TestIoSession() + { + } + + public IoService getService() + { + return null; + } + + public IoServiceConfig getServiceConfig() + { + return new TestIoConfig(); + } + + public IoHandler getHandler() + { + return null; + } + + public IoSessionConfig getConfig() + { + return null; + } + + public IoFilterChain getFilterChain() + { + return null; + } + + public WriteFuture write(Object message) + { + return null; + } + + public CloseFuture close() + { + return null; + } + + public Object getAttachment() + { + return getAttribute(""); + } + + public Object setAttachment(Object attachment) + { + return setAttribute("",attachment); + } + + public Object getAttribute(String key) + { + return attributes.get(key); + } + + public Object setAttribute(String key, Object value) + { + return attributes.put(key,value); + } + + public Object setAttribute(String key) + { + return attributes.put(key, Boolean.TRUE); + } + + public Object removeAttribute(String key) + { + return attributes.remove(key); + } + + public boolean containsAttribute(String key) + { + return attributes.containsKey(key); + } + + public Set getAttributeKeys() + { + return attributes.keySet(); + } + + public TransportType getTransportType() + { + return null; + } + + public boolean isConnected() + { + return false; + } + + public boolean isClosing() + { + return false; + } + + public CloseFuture getCloseFuture() + { + return null; + } + + public SocketAddress getRemoteAddress() + { + return new InetSocketAddress(getAddress(), getPort()); + } + + public SocketAddress getLocalAddress() + { + return null; + } + + public SocketAddress getServiceAddress() + { + return null; + } + + public int getIdleTime(IdleStatus status) + { + return 0; + } + + public long getIdleTimeInMillis(IdleStatus status) + { + return 0; + } + + public void setIdleTime(IdleStatus status, int idleTime) + { + + } + + public int getWriteTimeout() + { + return 0; + } + + public long getWriteTimeoutInMillis() + { + return 0; + } + + public void setWriteTimeout(int writeTimeout) + { + + } + + public TrafficMask getTrafficMask() + { + return null; + } + + public void setTrafficMask(TrafficMask trafficMask) + { + + } + + public void suspendRead() + { + + } + + public void suspendWrite() + { + + } + + public void resumeRead() + { + + } + + public void resumeWrite() + { + + } + + public long getReadBytes() + { + return 0; + } + + public long getWrittenBytes() + { + return 0; + } + + public long getReadMessages() + { + return 0; + } + + public long getWrittenMessages() + { + return 0; + } + + public long getWrittenWriteRequests() + { + return 0; + } + + public int getScheduledWriteRequests() + { + return 0; + } + + public int getScheduledWriteBytes() + { + return 0; + } + + public long getCreationTime() + { + return 0; + } + + public long getLastIoTime() + { + return 0; + } + + public long getLastReadTime() + { + return 0; + } + + public long getLastWriteTime() + { + return 0; + } + + public boolean isIdle(IdleStatus status) + { + return false; + } + + public int getIdleCount(IdleStatus status) + { + return 0; + } + + public long getLastIdleTime(IdleStatus status) + { + return 0; + } + + public void setAddress(String string) + { + this._address = string; + } + + public String getAddress() + { + return _address; + } + + public void setPort(int _port) + { + this._port = _port; + } + + public int getPort() + { + return _port; + } + + /** + * Test implementation of IoServiceConfig + */ + private class TestIoConfig extends SocketAcceptorConfig + { + public ThreadModel getThreadModel() + { + return ReadWriteThreadModel.getInstance(); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java new file mode 100644 index 0000000000..aff7af6952 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java @@ -0,0 +1,107 @@ +package org.apache.qpid.server.queue; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import java.util.ArrayList; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import junit.framework.AssertionFailedError; + +public class AMQPriorityQueueTest extends SimpleAMQQueueTest +{ + + @Override + protected void setUp() throws Exception + { + _arguments = new FieldTable(); + _arguments.put(AMQQueueFactory.X_QPID_PRIORITIES, 3); + super.setUp(); + } + + public void testPriorityOrdering() throws AMQException, InterruptedException + { + + // Enqueue messages in order + _queue.enqueue(null, createMessage(1L, (byte) 10)); + _queue.enqueue(null, createMessage(2L, (byte) 4)); + _queue.enqueue(null, createMessage(3L, (byte) 0)); + + // Enqueue messages in reverse order + _queue.enqueue(null, createMessage(4L, (byte) 0)); + _queue.enqueue(null, createMessage(5L, (byte) 4)); + _queue.enqueue(null, createMessage(6L, (byte) 10)); + + // Enqueue messages out of order + _queue.enqueue(null, createMessage(7L, (byte) 4)); + _queue.enqueue(null, createMessage(8L, (byte) 10)); + _queue.enqueue(null, createMessage(9L, (byte) 0)); + + // Register subscriber + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + + ArrayList<QueueEntry> msgs = _subscription.getMessages(); + try + { + assertEquals(new Long(1L), msgs.get(0).getMessage().getMessageId()); + assertEquals(new Long(6L), msgs.get(1).getMessage().getMessageId()); + assertEquals(new Long(8L), msgs.get(2).getMessage().getMessageId()); + + assertEquals(new Long(2L), msgs.get(3).getMessage().getMessageId()); + assertEquals(new Long(5L), msgs.get(4).getMessage().getMessageId()); + assertEquals(new Long(7L), msgs.get(5).getMessage().getMessageId()); + + assertEquals(new Long(3L), msgs.get(6).getMessage().getMessageId()); + assertEquals(new Long(4L), msgs.get(7).getMessage().getMessageId()); + assertEquals(new Long(9L), msgs.get(8).getMessage().getMessageId()); + } + catch (AssertionFailedError afe) + { + // Show message order on failure. + int index = 1; + for (QueueEntry qe : msgs) + { + System.err.println(index + ":" + qe.getMessage().getMessageId()); + index++; + } + + throw afe; + } + + } + + protected AMQMessage createMessage(Long id, byte i) throws AMQException + { + AMQMessage msg = super.createMessage(id); + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + props.setPriority(i); + msg.getContentHeaderBody().properties = props; + return msg; + } + + protected AMQMessage createMessage(Long id) throws AMQException + { + return createMessage(id, (byte) 0); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java new file mode 100644 index 0000000000..6589f46c7b --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueAlertTest.java @@ -0,0 +1,354 @@ +/* + * + * 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.queue; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.mina.common.ByteBuffer; + +import javax.management.Notification; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Collections; +import java.util.Set; + +/** This class tests all the alerts an AMQQueue can throw based on threshold values of different parameters */ +public class AMQQueueAlertTest extends TestCase +{ + private final static long MAX_MESSAGE_COUNT = 50; + private final static long MAX_MESSAGE_AGE = 250; // 0.25 sec + private final static long MAX_MESSAGE_SIZE = 2000; // 2 KB + private final static long MAX_QUEUE_DEPTH = 10000; // 10 KB + private AMQQueue _queue; + private AMQQueueMBean _queueMBean; + private VirtualHost _virtualHost; + private AMQMinaProtocolSession _protocolSession; + private MessageStore _messageStore = new MemoryMessageStore(); + private StoreContext _storeContext = new StoreContext(); + private TransactionalContext _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext, + null, + new LinkedList<RequiredDeliveryException>() + ); + private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE; + + /** + * Tests if the alert gets thrown when message count increases the threshold limit + * + * @throws Exception + */ + public void testMessageCountAlert() throws Exception + { + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue1"), false, new AMQShortString("AMQueueAlertTest"), + false, _virtualHost, + null); + _queueMBean = (AMQQueueMBean) _queue.getManagedObject(); + + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + + sendMessages(MAX_MESSAGE_COUNT, 256l); + assertTrue(_queueMBean.getMessageCount() == MAX_MESSAGE_COUNT); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_COUNT_ALERT.name())); + } + + /** + * Tests if the Message Size alert gets thrown when message of higher than threshold limit is sent + * + * @throws Exception + */ + public void testMessageSizeAlert() throws Exception + { + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue2"), false, new AMQShortString("AMQueueAlertTest"), + false, _virtualHost, + null); + _queueMBean = (AMQQueueMBean) _queue.getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumMessageSize(MAX_MESSAGE_SIZE); + + sendMessages(1, MAX_MESSAGE_SIZE * 2); + assertTrue(_queueMBean.getMessageCount() == 1); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_SIZE_ALERT.name())); + } + + /** + * Tests if Queue Depth alert is thrown when queue depth reaches the threshold value + * + * Based on FT-402 subbmitted by client + * + * @throws Exception + */ + public void testQueueDepthAlertNoSubscriber() throws Exception + { + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue3"), false, new AMQShortString("AMQueueAlertTest"), + false, _virtualHost, + null); + _queueMBean = (AMQQueueMBean) _queue.getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH); + + while (_queue.getQueueDepth() < MAX_QUEUE_DEPTH) + { + sendMessages(1, MAX_MESSAGE_SIZE); + } + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name())); + } + + /** + * Tests if MESSAGE AGE alert is thrown, when a message is in the queue for time higher than threshold value of + * message age + * + * Alternative test to FT-401 provided by client + * + * @throws Exception + */ + public void testMessageAgeAlert() throws Exception + { + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue4"), false, new AMQShortString("AMQueueAlertTest"), + false, _virtualHost, + null); + _queueMBean = (AMQQueueMBean) _queue.getManagedObject(); + _queueMBean.setMaximumMessageCount(MAX_MESSAGE_COUNT); + _queueMBean.setMaximumMessageAge(MAX_MESSAGE_AGE); + + sendMessages(1, MAX_MESSAGE_SIZE); + + // Ensure message sits on queue long enough to age. + Thread.sleep(MAX_MESSAGE_AGE * 2); + + Notification lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.MESSAGE_AGE_ALERT.name())); + } + + /* + This test sends some messages to the queue with subscribers needing message to be acknowledged. + The messages will not be acknowledged and will be required twice. Why we are checking this is because + the bug reported said that the queueDepth keeps increasing when messages are requeued. + // TODO - queue depth now includes unacknowledged messages so does not go down when messages are delivered + + The QueueDepth should decrease when messages are delivered from the queue (QPID-408) + */ + public void testQueueDepthAlertWithSubscribers() throws Exception + { + _protocolSession = new InternalTestProtocolSession(); + AMQChannel channel = new AMQChannel(_protocolSession, 2, _messageStore); + _protocolSession.addChannel(channel); + + // Create queue + _queue = getNewQueue(); + Subscription subscription = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), _protocolSession, new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager()); + + _queue.registerSubscription( + subscription, false); + + _queueMBean = (AMQQueueMBean) _queue.getManagedObject(); + _queueMBean.setMaximumMessageCount(9999l); // Set a high value, because this is not being tested + _queueMBean.setMaximumQueueDepth(MAX_QUEUE_DEPTH); + + // Send messages(no of message to be little more than what can cause a Queue_Depth alert) + int messageCount = Math.round(MAX_QUEUE_DEPTH / MAX_MESSAGE_SIZE) + 10; + long totalSize = (messageCount * MAX_MESSAGE_SIZE); + sendMessages(messageCount, MAX_MESSAGE_SIZE); + + // Check queueDepth. There should be no messages on the queue and as the subscriber is listening + // so there should be no Queue_Deoth alert raised + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + Notification lastNotification = _queueMBean.getLastNotification(); +// assertNull(lastNotification); + + // Kill the subscriber and check for the queue depth values. + // Messages are unacknowledged, so those should get requeued. All messages should be on the Queue + _queue.unregisterSubscription(subscription); + channel.requeue(); + + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + + lastNotification = _queueMBean.getLastNotification(); + assertNotNull(lastNotification); + String notificationMsg = lastNotification.getMessage(); + assertTrue(notificationMsg.startsWith(NotificationCheck.QUEUE_DEPTH_ALERT.name())); + + // Connect a consumer again and check QueueDepth values. The queue should get emptied. + // Messages will get delivered but still are unacknowledged. + Subscription subscription2 = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), _protocolSession, new AMQShortString("consumer_tag"), true, null, false, channel.getCreditManager()); + + _queue.registerSubscription( + subscription2, false); + + while (_queue.getUndeliveredMessageCount()!= 0) + { + Thread.sleep(100); + } +// assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth())); + + // Kill the subscriber again. Now those messages should get requeued again. Check if the queue depth + // value is correct. + _queue.unregisterSubscription(subscription2); + channel.requeue(); + + assertEquals(new Long(totalSize), new Long(_queueMBean.getQueueDepth())); + _protocolSession.closeSession(); + + // Check the clear queue + _queueMBean.clearQueue(); + assertEquals(new Long(0), new Long(_queueMBean.getQueueDepth())); + } + + protected IncomingMessage message(final boolean immediate, long size) throws AMQException + { + MessagePublishInfo publish = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return immediate; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + contentHeaderBody.bodySize = size; // in bytes + IncomingMessage message = new IncomingMessage(_messageStore.getNewMessageId(), publish, _transactionalContext, _protocolSession); + message.setContentHeaderBody(contentHeaderBody); + + return message; + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1); + _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test"); + _protocolSession = new InternalTestProtocolSession(); + + } + + protected void tearDown() + { + ApplicationRegistry.remove(1); + } + + + private void sendMessages(long messageCount, final long size) throws AMQException + { + IncomingMessage[] messages = new IncomingMessage[(int) messageCount]; + for (int i = 0; i < messages.length; i++) + { + messages[i] = message(false, size); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + messages[i].enqueue(qs); + messages[i].routingComplete(_messageStore, new MessageHandleFactory()); + + } + + for (int i = 0; i < messageCount; i++) + { + messages[i].addContentBodyFrame(new ContentChunk(){ + + ByteBuffer _data = ByteBuffer.allocate((int)size); + + public int getSize() + { + return (int) size; + } + + public ByteBuffer getData() + { + return _data; + } + + public void reduceToFit() + { + + } + }); + messages[i].deliverToQueues(); + } + } + + private AMQQueue getNewQueue() throws AMQException + { + return AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue" + Math.random()), + false, + new AMQShortString("AMQueueAlertTest"), + false, + _virtualHost, null); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java new file mode 100644 index 0000000000..520e49c56a --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java @@ -0,0 +1,85 @@ +/* + * + * 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.queue; + +import junit.framework.TestCase; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; + +public class AMQQueueFactoryTest extends TestCase +{ + QueueRegistry _queueRegistry; + VirtualHost _virtualHost; + + public void setUp() + { + ApplicationRegistry registry = (ApplicationRegistry) ApplicationRegistry.getInstance(); + + _virtualHost = registry.getVirtualHostRegistry().getVirtualHost("test"); + + _queueRegistry = _virtualHost.getQueueRegistry(); + + assertEquals("Queues registered on an empty virtualhost", 0, _queueRegistry.getQueues().size()); + } + + public void tearDown() + { + assertEquals("Queue was not registered in virtualhost", 1, _queueRegistry.getQueues().size()); + ApplicationRegistry.remove(1); + } + + + public void testPriorityQueueRegistration() + { + FieldTable fieldTable = new FieldTable(); + fieldTable.put(new AMQShortString(AMQQueueFactory.X_QPID_PRIORITIES), 5); + + try + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testPriorityQueue"), false, new AMQShortString("owner"), false, + _virtualHost, fieldTable); + + assertEquals("Queue not a priorty queue", AMQPriorityQueue.class, queue.getClass()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + + public void testSimpleQueueRegistration() + { + try + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("owner"), false, + _virtualHost, null); + assertEquals("Queue not a simple queue", SimpleAMQQueue.class, queue.getClass()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java new file mode 100644 index 0000000000..ce986cf55b --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueMBeanTest.java @@ -0,0 +1,349 @@ +/* + * + * 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.queue; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactory; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.mina.common.ByteBuffer; + +import javax.management.JMException; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Collections; + +/** + * Test class to test AMQQueueMBean attribtues and operations + */ +public class AMQQueueMBeanTest extends TestCase +{ + private static long MESSAGE_SIZE = 1000; + private AMQQueue _queue; + private AMQQueueMBean _queueMBean; + private MessageStore _messageStore; + private StoreContext _storeContext = new StoreContext(); + private TransactionalContext _transactionalContext; + private VirtualHost _virtualHost; + private AMQProtocolSession _protocolSession; + private static final SubscriptionFactoryImpl SUBSCRIPTION_FACTORY = SubscriptionFactoryImpl.INSTANCE; + + public void testMessageCountTransient() throws Exception + { + int messageCount = 10; + sendMessages(messageCount, false); + assertTrue(_queueMBean.getMessageCount() == messageCount); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + long queueDepth = (messageCount * MESSAGE_SIZE); + assertTrue(_queueMBean.getQueueDepth() == queueDepth); + + _queueMBean.deleteMessageFromTop(); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 1)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + _queueMBean.clearQueue(); + assertEquals(0,(int)_queueMBean.getMessageCount()); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + //Ensure that the data has been removed from the Store + verifyBrokerState(); + } + + public void testMessageCountPersistent() throws Exception + { + int messageCount = 10; + sendMessages(messageCount, true); + assertEquals("", messageCount, _queueMBean.getMessageCount().intValue()); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + long queueDepth = (messageCount * MESSAGE_SIZE); + assertTrue(_queueMBean.getQueueDepth() == queueDepth); + + _queueMBean.deleteMessageFromTop(); + assertTrue(_queueMBean.getMessageCount() == (messageCount - 1)); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + _queueMBean.clearQueue(); + assertTrue(_queueMBean.getMessageCount() == 0); + assertTrue(_queueMBean.getReceivedMessageCount() == messageCount); + + //Ensure that the data has been removed from the Store + verifyBrokerState(); + } + + // todo: collect to a general testing class -duplicated from Systest/MessageReturntest + private void verifyBrokerState() + { + + TestableMemoryMessageStore store = new TestableMemoryMessageStore((MemoryMessageStore) _virtualHost.getMessageStore()); + + // Unlike MessageReturnTest there is no need for a delay as there this thread does the clean up. + assertNotNull("ContentBodyMap should not be null", store.getContentBodyMap()); + assertEquals("Expected the store to have no content:" + store.getContentBodyMap(), 0, store.getContentBodyMap().size()); + assertNotNull("MessageMetaDataMap should not be null", store.getMessageMetaDataMap()); + assertEquals("Expected the store to have no metadata:" + store.getMessageMetaDataMap(), 0, store.getMessageMetaDataMap().size()); + } + + public void testConsumerCount() throws AMQException + { + + assertTrue(_queue.getActiveConsumerCount() == 0); + assertTrue(_queueMBean.getActiveConsumerCount() == 0); + + + InternalTestProtocolSession protocolSession = new InternalTestProtocolSession(); + AMQChannel channel = new AMQChannel(protocolSession, 1, _messageStore); + protocolSession.addChannel(channel); + + Subscription subscription = + SUBSCRIPTION_FACTORY.createSubscription(channel.getChannelId(), protocolSession, new AMQShortString("test"), false, null, false, channel.getCreditManager()); + + _queue.registerSubscription(subscription, false); + assertEquals(1,(int)_queueMBean.getActiveConsumerCount()); + + + SubscriptionFactory subscriptionFactory = SUBSCRIPTION_FACTORY; + Subscription s1 = subscriptionFactory.createSubscription(channel.getChannelId(), + protocolSession, + new AMQShortString("S1"), + false, + null, + true, + channel.getCreditManager()); + + Subscription s2 = subscriptionFactory.createSubscription(channel.getChannelId(), + protocolSession, + new AMQShortString("S2"), + false, + null, + true, + channel.getCreditManager()); + _queue.registerSubscription(s1,false); + _queue.registerSubscription(s2,false); + assertTrue(_queueMBean.getActiveConsumerCount() == 3); + assertTrue(_queueMBean.getConsumerCount() == 3); + + s1.close(); + assertEquals(2, (int) _queueMBean.getActiveConsumerCount()); + assertTrue(_queueMBean.getConsumerCount() == 3); + } + + public void testGeneralProperties() + { + long maxQueueDepth = 1000; // in bytes + _queueMBean.setMaximumMessageCount(50000l); + _queueMBean.setMaximumMessageSize(2000l); + _queueMBean.setMaximumQueueDepth(maxQueueDepth); + + assertTrue(_queueMBean.getMaximumMessageCount() == 50000); + assertTrue(_queueMBean.getMaximumMessageSize() == 2000); + assertTrue(_queueMBean.getMaximumQueueDepth() == (maxQueueDepth)); + + assertTrue(_queueMBean.getName().equals("testQueue")); + assertTrue(_queueMBean.getOwner().equals("AMQueueMBeanTest")); + assertFalse(_queueMBean.isAutoDelete()); + assertFalse(_queueMBean.isDurable()); + } + + public void testExceptions() throws Exception + { + try + { + _queueMBean.viewMessages(0, 3); + fail(); + } + catch (JMException ex) + { + + } + + try + { + _queueMBean.viewMessages(2, 1); + fail(); + } + catch (JMException ex) + { + + } + + try + { + _queueMBean.viewMessages(-1, 1); + fail(); + } + catch (JMException ex) + { + + } + + IncomingMessage msg = message(false, false); + long id = msg.getMessageId(); + _queue.clearQueue(_storeContext); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + msg.enqueue(qs); + msg.routingComplete(_messageStore, new MessageHandleFactory()); + + msg.addContentBodyFrame(new ContentChunk() + { + ByteBuffer _data = ByteBuffer.allocate((int)MESSAGE_SIZE); + + public int getSize() + { + return (int) MESSAGE_SIZE; + } + + public ByteBuffer getData() + { + return _data; + } + + public void reduceToFit() + { + + } + }); + msg.deliverToQueues(); +// _queue.process(_storeContext, new QueueEntry(_queue, msg), false); + _queueMBean.viewMessageContent(id); + try + { + _queueMBean.viewMessageContent(id + 1); + fail(); + } + catch (JMException ex) + { + + } + } + + private IncomingMessage message(final boolean immediate, boolean persistent) throws AMQException + { + MessagePublishInfo publish = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return immediate; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + contentHeaderBody.bodySize = MESSAGE_SIZE; // in bytes + contentHeaderBody.properties = new BasicContentHeaderProperties(); + ((BasicContentHeaderProperties) contentHeaderBody.properties).setDeliveryMode((byte) (persistent ? 2 : 1)); + IncomingMessage msg = new IncomingMessage(_messageStore.getNewMessageId(), publish, _transactionalContext, _protocolSession); + msg.setContentHeaderBody(contentHeaderBody); + return msg; + + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + IApplicationRegistry applicationRegistry = ApplicationRegistry.getInstance(1); + _virtualHost = applicationRegistry.getVirtualHostRegistry().getVirtualHost("test"); + _messageStore = _virtualHost.getMessageStore(); + + _transactionalContext = new NonTransactionalContext(_messageStore, _storeContext, + null, + new LinkedList<RequiredDeliveryException>() + ); + + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("testQueue"), false, new AMQShortString("AMQueueMBeanTest"), false, _virtualHost, + null); + _queueMBean = new AMQQueueMBean(_queue); + + _protocolSession = new InternalTestProtocolSession(); + } + + public void tearDown() + { + ApplicationRegistry.remove(1); + } + + private void sendMessages(int messageCount, boolean persistent) throws AMQException + { + for (int i = 0; i < messageCount; i++) + { + IncomingMessage currentMessage = message(false, persistent); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + currentMessage.enqueue(qs); + + // route header + currentMessage.routingComplete(_messageStore, new MessageHandleFactory()); + + // Add the body so we have somthing to test later + currentMessage.addContentBodyFrame( + _protocolSession.getMethodRegistry() + .getProtocolVersionMethodConverter() + .convertToContentChunk( + new ContentBody(ByteBuffer.allocate((int) MESSAGE_SIZE), + MESSAGE_SIZE))); + currentMessage.deliverToQueues(); + + + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java new file mode 100644 index 0000000000..9c2932c5e2 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AckTest.java @@ -0,0 +1,420 @@ +/* + * + * 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.queue; + +import junit.framework.TestCase; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.flow.LimitlessCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.txn.TransactionalContext; +import org.apache.qpid.server.util.NullApplicationRegistry; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Set; +import java.util.Collections; + +/** + * Tests that acknowledgements are handled correctly. + */ +public class AckTest extends TestCase +{ + private static final Logger _log = Logger.getLogger(AckTest.class); + + private Subscription _subscription; + + private MockProtocolSession _protocolSession; + + private TestMemoryMessageStore _messageStore; + + private StoreContext _storeContext = new StoreContext(); + + private AMQChannel _channel; + + private AMQQueue _queue; + + private static final AMQShortString DEFAULT_CONSUMER_TAG = new AMQShortString("conTag"); + + protected void setUp() throws Exception + { + super.setUp(); + ApplicationRegistry.initialise(new NullApplicationRegistry(), 1); + + _messageStore = new TestMemoryMessageStore(); + _protocolSession = new MockProtocolSession(_messageStore); + _channel = new AMQChannel(_protocolSession,5, _messageStore /*dont need exchange registry*/); + + _protocolSession.addChannel(_channel); + + _queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString("myQ"), false, new AMQShortString("guest"), true, ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost("test"), + null); + } + + protected void tearDown() + { + ApplicationRegistry.remove(1); + } + + private void publishMessages(int count) throws AMQException + { + publishMessages(count, false); + } + + private void publishMessages(int count, boolean persistent) throws AMQException + { + TransactionalContext txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, + new LinkedList<RequiredDeliveryException>() + ); + _queue.registerSubscription(_subscription,false); + MessageHandleFactory factory = new MessageHandleFactory(); + for (int i = 1; i <= count; i++) + { + // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) + // TODO: Establish some way to determine the version for the test. + MessagePublishInfo publishBody = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return new AMQShortString("someExchange"); + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return new AMQShortString("rk"); + } + }; + IncomingMessage msg = new IncomingMessage(_messageStore.getNewMessageId(), publishBody, txnContext,_protocolSession); + //IncomingMessage msg2 = null; + if (persistent) + { + BasicContentHeaderProperties b = new BasicContentHeaderProperties(); + //This is DeliveryMode.PERSISTENT + b.setDeliveryMode((byte) 2); + ContentHeaderBody cb = new ContentHeaderBody(); + cb.properties = b; + msg.setContentHeaderBody(cb); + } + else + { + msg.setContentHeaderBody(new ContentHeaderBody()); + } + // we increment the reference here since we are not delivering the messaging to any queues, which is where + // the reference is normally incremented. The test is easier to construct if we have direct access to the + // subscription + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + qs.add(_queue); + msg.enqueue(qs); + msg.routingComplete(_messageStore, factory); + if(msg.allContentReceived()) + { + msg.deliverToQueues(); + } + // we manually send the message to the subscription + //_subscription.send(new QueueEntry(_queue,msg), _queue); + } + } + + /** + * Tests that the acknowledgements are correctly associated with a channel and + * order is preserved when acks are enabled + */ + public void testAckChannelAssociationTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount); + assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + i++; + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + } + + assertTrue(map.size() == msgCount); + assertTrue(_messageStore.getMessageMetaDataMap().size() == msgCount); + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false, null, false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageMetaDataMap().size() == 0); + assertTrue(_messageStore.getContentBodyMap().size() == 0); + + } + + /** + * Tests that in no-ack mode no messages are retained + */ + public void testPersistentNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, false,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount, true); + + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageMetaDataMap().size() == 0); + assertTrue(_messageStore.getContentBodyMap().size() == 0); + + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testSingleAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, false); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount - 1); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + // 5 is the delivery tag of the message that *should* be removed + if (++i == 5) + { + ++i; + } + } + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + public void testMultiAckReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs. + */ + public void testMultiAckAllReceivedTest() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(0, true); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + + Set<Long> deliveryTagSet = map.getDeliveryTags(); + int i = 1; + for (long deliveryTag : deliveryTagSet) + { + assertTrue(deliveryTag == i + 5); + QueueEntry unackedMsg = map.get(deliveryTag); + assertTrue(unackedMsg.getQueue() == _queue); + ++i; + } + } + + /** + * A regression fixing QPID-1136 showed this up + * + * @throws Exception + */ + public void testMessageDequeueRestoresCreditTest() throws Exception + { + // Send 10 messages + Pre0_10CreditManager creditManager = new Pre0_10CreditManager(0l, 1); + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, + DEFAULT_CONSUMER_TAG, true, null, false, creditManager); + final int msgCount = 1; + publishMessages(msgCount); + + _queue.deliverAsync(_subscription); + + _channel.acknowledgeMessage(1, false); + + // Check credit available + assertTrue("No credit available", creditManager.hasCredit()); + + } + + +/* + public void testPrefetchHighLow() throws AMQException + { + int lowMark = 5; + int highMark = 10; + + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setPrefetchLowMarkCount(lowMark); + _channel.setPrefetchHighMarkCount(highMark); + + assertTrue(_channel.getPrefetchLowMarkCount() == lowMark); + assertTrue(_channel.getPrefetchHighMarkCount() == highMark); + + publishMessages(highMark); + + // at this point we should have sent out only highMark messages + // which have not bee received so will be queued up in the channel + // which should be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == highMark); + + //acknowledge messages so we are just above lowMark + _channel.acknowledgeMessage(lowMark - 1, true); + + //we should still be suspended + assertTrue(_subscription.isSuspended()); + assertTrue(map.size() == lowMark + 1); + + //acknowledge one more message + _channel.acknowledgeMessage(lowMark, true); + + //and suspension should be lifted + assertTrue(!_subscription.isSuspended()); + + //pubilsh more msgs so we are just below the limit + publishMessages(lowMark - 1); + + //we should not be suspended + assertTrue(!_subscription.isSuspended()); + + //acknowledge all messages + _channel.acknowledgeMessage(0, true); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + //map will be empty + assertTrue(map.size() == 0); + } + +*/ +/* + public void testPrefetch() throws AMQException + { + _subscription = SubscriptionFactoryImpl.INSTANCE.createSubscription(5, _protocolSession, DEFAULT_CONSUMER_TAG, true,null,false, new LimitlessCreditManager()); + _channel.setMessageCredit(5); + + assertTrue(_channel.getPrefetchCount() == 5); + + final int msgCount = 5; + publishMessages(msgCount); + + // at this point we should have sent out only 5 messages with a further 5 queued + // up in the channel which should now be suspended + assertTrue(_subscription.isSuspended()); + UnacknowledgedMessageMap map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + _channel.acknowledgeMessage(5, true); + assertTrue(!_subscription.isSuspended()); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + assertTrue(map.size() == 0); + } + +*/ + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(AckTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.java new file mode 100644 index 0000000000..355ba6a362 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessage.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.queue; + +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; + +public class MockAMQMessage extends AMQMessage +{ + public MockAMQMessage(long messageId) + throws AMQException + { + super(new MockAMQMessageHandle(messageId) , + (StoreContext)null, + (MessagePublishInfo)new MockMessagePublishInfo()); + } + + protected MockAMQMessage(AMQMessage msg) + throws AMQException + { + super(msg); + } + + + @Override + public long getSize() + { + return 0l; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java new file mode 100644 index 0000000000..bdb0707c27 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQMessageHandle.java @@ -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. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.store.StoreContext; + +public class MockAMQMessageHandle extends InMemoryMessageHandle +{ + public MockAMQMessageHandle(final Long messageId) + { + super(messageId); + } + + @Override + public long getBodySize(StoreContext store) + { + return 0l; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java new file mode 100644 index 0000000000..20503bf15c --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java @@ -0,0 +1,335 @@ +/* + * + * 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.queue; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.commons.configuration.Configuration; + +import java.util.List; +import java.util.Set; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedList; + +public class MockAMQQueue implements AMQQueue +{ + private boolean _deleted = false; + private AMQShortString _name; + + public MockAMQQueue(String name) + { + _name = new AMQShortString(name); + } + + public MockAMQQueue() + { + + } + + public AMQShortString getName() + { + return _name; + } + + public boolean isDurable() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isAutoDelete() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQShortString getOwner() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public VirtualHost getVirtualHost() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void bind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void unBind(Exchange exchange, AMQShortString routingKey, FieldTable arguments) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public List<ExchangeBinding> getExchangeBindings() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void registerSubscription(Subscription subscription, boolean exclusive) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void unregisterSubscription(Subscription subscription) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public int getConsumerCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getActiveConsumerCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isUnused() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isEmpty() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getUndeliveredMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getQueueDepth() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReceivedMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getOldestMessageArrivalTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isDeleted() + { + return _deleted; + } + + public int delete() throws AMQException + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public QueueEntry enqueue(StoreContext storeContext, AMQMessage message) throws AMQException + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void requeue(StoreContext storeContext, QueueEntry entry) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeue(StoreContext storeContext, QueueEntry entry) throws FailedDequeueException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean resend(QueueEntry entry, Subscription subscription) throws AMQException + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void addQueueDeleteTask(Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public List<QueueEntry> getMessagesOnTheQueue() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<Long> getMessagesOnTheQueue(int num) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public List<Long> getMessagesOnTheQueue(int num, int offest) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public QueueEntry getMessageOnTheQueue(long messageId) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, StoreContext storeContext) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeMessagesFromQueue(long fromMessageId, long toMessageId, StoreContext storeContext) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageSize() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageSize(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageCount() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageCount(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumQueueDepth() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumQueueDepth(long value) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMaximumMessageAge() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getMinimumAlertRepeatGap() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void deleteMessageFromTop(StoreContext storeContext) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long clearQueue(StoreContext storeContext) throws AMQException + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void checkMessageStatus() throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Set<NotificationCheck> getNotificationChecks() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void flushSubscription(Subscription sub) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void deliverAsync(Subscription sub) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void deliverAsync() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void stop() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public ManagedObject getManagedObject() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public int compareTo(AMQQueue o) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public void setMinimumAlertRepeatGap(long value) + { + + } + + public void configure(QueueConfiguration config) + { + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.java new file mode 100644 index 0000000000..5a5ffaa14d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockMessagePublishInfo.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.queue; + +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.AMQShortString; + +public class MockMessagePublishInfo implements MessagePublishInfo +{ + public AMQShortString getExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isMandatory() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public AMQShortString getRoutingKey() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java new file mode 100644 index 0000000000..99c88fac3e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockProtocolSession.java @@ -0,0 +1,262 @@ +/* + * + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.transport.Sender; + +import javax.security.sasl.SaslServer; +import java.util.HashMap; +import java.util.Map; +import java.security.Principal; + +/** + * A protocol session that can be used for testing purposes. + */ +public class MockProtocolSession implements AMQProtocolSession +{ + private MessageStore _messageStore; + + private Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + public MockProtocolSession(MessageStore messageStore) + { + _messageStore = messageStore; + } + + public void dataBlockReceived(AMQDataBlock message) throws Exception + { + } + + public void writeFrame(AMQDataBlock frame) + { + } + + public AMQShortString getContextKey() + { + return null; + } + + public void setContextKey(AMQShortString contextKey) + { + } + + public AMQChannel getChannel(int channelId) + { + AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Invalid channel id: " + channelId); + } + else + { + return channel; + } + } + + public void addChannel(AMQChannel channel) + { + if (channel == null) + { + throw new IllegalArgumentException("Channel must not be null"); + } + else + { + _channelMap.put(channel.getChannelId(), channel); + } + } + + public void closeChannel(int channelId) throws AMQException + { + } + + public void closeChannelOk(int channelId) + { + + } + + public boolean channelAwaitingClosure(int channelId) + { + return false; + } + + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + } + + public void initHeartbeats(int delay) + { + } + + public void closeSession() throws AMQException + { + } + + public void closeConnection(int channelId, AMQConnectionException e, boolean closeIoSession) throws AMQException + { + } + + public Object getKey() + { + return null; + } + + public String getLocalFQDN() + { + return null; + } + + public SaslServer getSaslServer() + { + return null; + } + + public void setSaslServer(SaslServer saslServer) + { + } + + public FieldTable getClientProperties() + { + return null; + } + + public void setClientProperties(FieldTable clientProperties) + { + } + + public Object getClientIdentifier() + { + return null; + } + + public VirtualHost getVirtualHost() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setVirtualHost(VirtualHost virtualHost) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void addSessionCloseTask(Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeSessionCloseTask(Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return ProtocolOutputConverterRegistry.getConverter(this); + } + + public void setAuthorizedID(Principal authorizedID) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Principal getAuthorizedID() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public MethodRegistry getMethodRegistry() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void methodFrameReceived(int channelId, AMQMethodBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentBodyReceived(int channelId, ContentBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public MethodDispatcher getMethodDispatcher() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public ProtocolSessionIdentifier getSessionIdentifier() + { + return null; + } + + public byte getProtocolMajorVersion() + { + return getProtocolVersion().getMajorVersion(); + } + + public byte getProtocolMinorVersion() + { + return getProtocolVersion().getMinorVersion(); + } + + + public ProtocolVersion getProtocolVersion() + { + return ProtocolVersion.getLatestSupportedVersion(); //To change body of implemented methods use File | Settings | File Templates. + } + + + public VersionSpecificRegistry getRegistry() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setSender(Sender<java.nio.ByteBuffer> sender) + { + // FIXME AS TODO + + } + + public void init() + { + // TODO Auto-generated method stub + + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java new file mode 100644 index 0000000000..37f91e7464 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java @@ -0,0 +1,197 @@ +/* +* + * 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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.subscription.Subscription; + +public class MockQueueEntry implements QueueEntry +{ + + private AMQMessage _message; + + public boolean acquire() + { + return false; + } + + public boolean acquire(Subscription sub) + { + return false; + } + + public boolean acquiredBySubscription() + { + return false; + } + + public void addStateChangeListener(StateChangeListener listener) + { + + } + + public String debugIdentity() + { + return null; + } + + public boolean delete() + { + return false; + } + + public void dequeue(StoreContext storeContext) throws FailedDequeueException + { + + } + + public void discard(StoreContext storeContext) throws FailedDequeueException, MessageCleanupException + { + + } + + public void dispose(StoreContext storeContext) throws MessageCleanupException + { + + } + + public boolean expired() throws AMQException + { + return false; + } + + public Subscription getDeliveredSubscription() + { + return null; + } + + public boolean getDeliveredToConsumer() + { + return false; + } + + public AMQMessage getMessage() + { + return _message; + } + + public AMQQueue getQueue() + { + return null; + } + + public long getSize() + { + return 0; + } + + public boolean immediateAndNotDelivered() + { + return false; + } + + public boolean isAcquired() + { + return false; + } + + public boolean isDeleted() + { + return false; + } + + + public boolean isQueueDeleted() + { + + return false; + } + + + public boolean isRejectedBy(Subscription subscription) + { + + return false; + } + + + public void reject() + { + + + } + + + public void reject(Subscription subscription) + { + + + } + + + public void release() + { + + + } + + + public boolean removeStateChangeListener(StateChangeListener listener) + { + + return false; + } + + + public void requeue(StoreContext storeContext) throws AMQException + { + + + } + + + public void setDeliveredToSubscription() + { + + + } + + + public void setRedelivered(boolean b) + { + + + } + + + public int compareTo(QueueEntry o) + { + + return 0; + } + + public void setMessage(AMQMessage msg) + { + _message = msg; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java new file mode 100644 index 0000000000..3084dc7fa1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java @@ -0,0 +1,435 @@ +package org.apache.qpid.server.queue; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionImpl; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class SimpleAMQQueueTest extends TestCase +{ + + protected SimpleAMQQueue _queue; + protected VirtualHost _virtualHost; + protected TestableMemoryMessageStore _store = new TestableMemoryMessageStore(); + protected AMQShortString _qname = new AMQShortString("qname"); + protected AMQShortString _owner = new AMQShortString("owner"); + protected AMQShortString _routingKey = new AMQShortString("routing key"); + protected DirectExchange _exchange = new DirectExchange(); + protected MockSubscription _subscription = new MockSubscription(); + protected FieldTable _arguments = null; + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + //Create Application Registry for test + ApplicationRegistry applicationRegistry = (ApplicationRegistry)ApplicationRegistry.getInstance(1); + + PropertiesConfiguration env = new PropertiesConfiguration(); + _virtualHost = new VirtualHost(new VirtualHostConfiguration(getClass().getName(), env), _store); + applicationRegistry.getVirtualHostRegistry().registerVirtualHost(_virtualHost); + + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false, _virtualHost, _arguments); + } + + @Override + protected void tearDown() + { + _queue.stop(); + ApplicationRegistry.remove(1); + } + + public void testCreateQueue() throws AMQException + { + _queue.stop(); + try { + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(null, false, _owner, false, _virtualHost, _arguments ); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing name", + e.getMessage().contains("name")); + } + + try { + _queue = new SimpleAMQQueue(_qname, false, _owner, false, null); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing vhost", + e.getMessage().contains("Host")); + } + + _queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(_qname, false, _owner, false, + _virtualHost, _arguments); + assertNotNull("Queue was not created", _queue); + } + + public void testGetVirtualHost() + { + assertEquals("Virtual host was wrong", _virtualHost, _queue.getVirtualHost()); + } + + public void testBinding() + { + try + { + _queue.bind(_exchange, _routingKey, null); + assertTrue("Routing key was not bound", + _exchange.getBindings().containsKey(_routingKey)); + assertEquals("Queue was not bound to key", + _exchange.getBindings().get(_routingKey).get(0), + _queue); + assertEquals("Exchange binding count", 1, + _queue.getExchangeBindings().size()); + assertEquals("Wrong exchange bound", _routingKey, + _queue.getExchangeBindings().get(0).getRoutingKey()); + assertEquals("Wrong exchange bound", _exchange, + _queue.getExchangeBindings().get(0).getExchange()); + + _queue.unBind(_exchange, _routingKey, null); + assertFalse("Routing key was still bound", + _exchange.getBindings().containsKey(_routingKey)); + assertNull("Routing key was not empty", + _exchange.getBindings().get(_routingKey)); + } + catch (AMQException e) + { + assertNull("Unexpected exception", e); + } + } + + public void testSubscription() throws AMQException + { + // Check adding a subscription adds it to the queue + _queue.registerSubscription(_subscription, false); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(null, messageA); + assertEquals(messageA, _subscription.getLastSeenEntry().getMessage()); + + // Check removing the subscription removes it's information from the queue + _queue.unregisterSubscription(_subscription); + assertTrue("Subscription still had queue", _subscription.isClosed()); + assertFalse("Queue still has consumer", 1 == _queue.getConsumerCount()); + assertFalse("Queue still has active consumer", + 1 == _queue.getActiveConsumerCount()); + + AMQMessage messageB = createMessage(new Long (25)); + _queue.enqueue(null, messageB); + QueueEntry entry = _subscription.getLastSeenEntry(); + assertNull(entry); + } + + public void testQueueNoSubscriber() throws AMQException, InterruptedException + { + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(null, messageA); + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + assertEquals(messageA, _subscription.getLastSeenEntry().getMessage()); + } + + public void testExclusiveConsumer() throws AMQException + { + // Check adding an exclusive subscription adds it to the queue + _queue.registerSubscription(_subscription, true); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + AMQMessage messageA = createMessage(new Long(24)); + _queue.enqueue(null, messageA); + assertEquals(messageA, _subscription.getLastSeenEntry().getMessage()); + + // Check we cannot add a second subscriber to the queue + Subscription subB = new MockSubscription(); + Exception ex = null; + try + { + _queue.registerSubscription(subB, false); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + assertTrue(ex instanceof AMQException); + + // Check we cannot add an exclusive subscriber to a queue with an + // existing subscription + _queue.unregisterSubscription(_subscription); + _queue.registerSubscription(_subscription, false); + try + { + _queue.registerSubscription(subB, true); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + } + + public void testAutoDeleteQueue() throws Exception + { + _queue.stop(); + _queue = new SimpleAMQQueue(_qname, false, _owner, true, _virtualHost); + _queue.registerSubscription(_subscription, false); + AMQMessage message = createMessage(new Long(25)); + _queue.enqueue(null, message); + _queue.unregisterSubscription(_subscription); + assertTrue("Queue was not deleted when subscription was removed", + _queue.isDeleted()); + } + + public void testResend() throws Exception + { + _queue.registerSubscription(_subscription, false); + Long id = new Long(26); + AMQMessage message = createMessage(id); + _queue.enqueue(null, message); + QueueEntry entry = _subscription.getLastSeenEntry(); + entry.setRedelivered(true); + _queue.resend(entry, _subscription); + + } + + public void testGetFirstMessageId() throws Exception + { + // Create message + Long messageId = new Long(23); + AMQMessage message = createMessage(messageId); + + // Put message on queue + _queue.enqueue(null, message); + // Get message id + Long testmsgid = _queue.getMessagesOnTheQueue(1).get(0); + + // Check message id + assertEquals("Message ID was wrong", messageId, testmsgid); + } + + public void testGetFirstFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 5; i++) + { + // Create message + Long messageId = new Long(i); + AMQMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(null, message); + } + // Get message ids + List<Long> msgids = _queue.getMessagesOnTheQueue(5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testGetLastFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 10; i++) + { + // Create message + Long messageId = new Long(i); + AMQMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(null, message); + } + // Get message ids + List<Long> msgids = _queue.getMessagesOnTheQueue(5, 5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i+5); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testEnqueueDequeueOfPersistentMessageToNonDurableQueue() throws AMQException + { + // Create IncomingMessage and nondurable queue + NonTransactionalContext txnContext = new NonTransactionalContext(_store, null, null, null); + IncomingMessage msg = new IncomingMessage(1L, info, txnContext, null); + ContentHeaderBody contentHeaderBody = new ContentHeaderBody(); + contentHeaderBody.properties = new BasicContentHeaderProperties(); + ((BasicContentHeaderProperties) contentHeaderBody.properties).setDeliveryMode((byte) 2); + msg.setContentHeaderBody(contentHeaderBody); + ArrayList<AMQQueue> qs = new ArrayList<AMQQueue>(); + + // Send persistent message + qs.add(_queue); + msg.enqueue(qs); + msg.routingComplete(_store, new MessageHandleFactory()); + _store.storeMessageMetaData(null, new Long(1L), new MessageMetaData(info, contentHeaderBody, 1)); + + // Check that it is enqueued + AMQQueue data = _store.getMessages().get(1L); + assertNotNull(data); + + // Dequeue message + MockQueueEntry entry = new MockQueueEntry(); + AMQMessage amqmsg = new AMQMessage(1L, _store, new MessageHandleFactory(), txnContext); + + entry.setMessage(amqmsg); + _queue.dequeue(null, entry); + + // Check that it is dequeued + data = _store.getMessages().get(1L); + assertNull(data); + } + + + // FIXME: move this to somewhere useful + private static AMQMessageHandle createMessageHandle(final long messageId, final MessagePublishInfo publishBody) + { + final AMQMessageHandle amqMessageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, + null, + false); + try + { + amqMessageHandle.setPublishAndContentHeaderBody(new StoreContext(), + publishBody, + new ContentHeaderBody() + { + public int getSize() + { + return 1; + } + }); + } + catch (AMQException e) + { + // won't happen + } + + + return amqMessageHandle; + } + + public class TestMessage extends AMQMessage + { + private final long _tag; + private int _count; + + TestMessage(long tag, long messageId, MessagePublishInfo publishBody, StoreContext storeContext) + throws AMQException + { + super(createMessageHandle(messageId, publishBody), storeContext, publishBody); + _tag = tag; + } + + + public boolean incrementReference() + { + _count++; + return true; + } + + public void decrementReference(StoreContext context) + { + _count--; + } + + void assertCountEquals(int expected) + { + assertEquals("Wrong count for message with tag " + _tag, expected, _count); + } + } + + protected AMQMessage createMessage(Long id) throws AMQException + { + AMQMessage messageA = new TestMessage(id, id, info, new StoreContext()); + return messageA; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java new file mode 100644 index 0000000000..832df80004 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java @@ -0,0 +1,60 @@ +/* + * + * 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.queue; + +import junit.framework.TestCase; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SimpleAMQQueueThreadPoolTest extends TestCase +{ + + public void test() throws AMQException + { + int initialCount = ReferenceCountingExecutorService.getInstance().getReferenceCount(); + VirtualHost test = ApplicationRegistry.getInstance(1).getVirtualHostRegistry().getVirtualHost("test"); + + try + { + SimpleAMQQueue queue = (SimpleAMQQueue) AMQQueueFactory.createAMQQueueImpl(new AMQShortString("test"), false, + new AMQShortString("owner"), + false, test, null); + + assertFalse("Creation did not start Pool.", ReferenceCountingExecutorService.getInstance().getPool().isShutdown()); + + assertEquals("References not increased", initialCount + 1, ReferenceCountingExecutorService.getInstance().getReferenceCount()); + + queue.stop(); + + assertEquals("References not decreased", initialCount , ReferenceCountingExecutorService.getInstance().getReferenceCount()); + } + finally + { + ApplicationRegistry.remove(1); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.java new file mode 100644 index 0000000000..939e3436a5 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/registry/ApplicationRegistryShutdownTest.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.registry; + +import junit.framework.TestCase; +import org.apache.qpid.server.util.TestApplicationRegistry; + +import java.security.Security; +import java.security.Provider; +import java.util.List; +import java.util.LinkedList; + +/** + * QPID-1390 : Test to validate that the AuthenticationManger succesfully unregisters any new SASL providers when + * The ApplicationRegistry is closed. + * + * This should be expanded as QPID-1399 is implemented. + */ +public class ApplicationRegistryShutdownTest extends TestCase +{ + + ApplicationRegistry _registry; + + public void setUp() throws Exception + { + _registry = new TestApplicationRegistry(); + } + + /** + * QPID-1399 : Ensure that the Authentiction manager unregisters any SASL providers created during + * ApplicationRegistry initialisation. + * + */ + public void testAuthenticationMangerCleansUp() + { + // Get default providers + Provider[] defaultProviders = Security.getProviders(); + + // Register new providers + try + { + _registry.initialise(); + } + catch (Exception e) + { + fail(e.getMessage()); + } + + // Get the providers after initialisation + Provider[] providersAfterInitialisation = Security.getProviders(); + + // Find the additions + List additions = new LinkedList(); + for (Provider afterInit : providersAfterInitialisation) + { + boolean found = false; + for (Provider defaultProvider : defaultProviders) + { + if (defaultProvider == afterInit) + { + found=true; + break; + } + } + + // Record added registies + if (!found) + { + additions.add(afterInit); + } + } + + // Not using isEmpty as that is not in Java 5 + assertTrue("No new SASL mechanisms added by initialisation.", additions.size() != 0 ); + + //Close the registry which will perform the close the AuthenticationManager + try + { + _registry.close(); + } + catch (Exception e) + { + fail(e.getMessage()); + } + + //Validate that the SASL plugins have been removed. + Provider[] providersAfterClose = Security.getProviders(); + + assertTrue("No providers unregistered", providersAfterInitialisation.length > providersAfterClose.length); + + //Ensure that the additions are not still present after close(). + for (Provider afterClose : providersAfterClose) + { + assertFalse("Added provider not unregistered", additions.contains(afterClose)); + } + } + + + + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java new file mode 100644 index 0000000000..6c6835ccca --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ACLManagerTest.java @@ -0,0 +1,99 @@ +/* + * 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.security.access; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.configuration.SecurityConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugins.MockPluginManager; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.MockProtocolSession; +import org.apache.qpid.server.store.TestableMemoryMessageStore; + +public class ACLManagerTest extends TestCase +{ + + private ACLManager _authzManager; + private AMQProtocolSession _session; + private SecurityConfiguration _conf; + private PluginManager _pluginManager; + + @Override + public void setUp() throws Exception + { + File tmpFile = File.createTempFile(getClass().getName(), "testconfig"); + tmpFile.deleteOnExit(); + BufferedWriter out = new BufferedWriter(new FileWriter(tmpFile)); + out.write("<security><queueDenier>notyet</queueDenier><exchangeDenier>yes</exchangeDenier></security>"); + out.close(); + + _conf = new SecurityConfiguration(new XMLConfiguration(tmpFile)); + + // Create ACLManager + + _pluginManager = new MockPluginManager(""); + _authzManager = new ACLManager(_conf, _pluginManager); + + _session = new MockProtocolSession(new TestableMemoryMessageStore()); + } + + public void testACLManagerConfigurationPluginManager() throws Exception + { + AMQQueue queue = new MockAMQQueue("notyet"); + AMQQueue otherQueue = new MockAMQQueue("other"); + + assertFalse(_authzManager.authoriseDelete(_session, queue)); + + // This should only be denied if the config hasn't been correctly passed in + assertTrue(_authzManager.authoriseDelete(_session, otherQueue)); + assertTrue(_authzManager.authorisePurge(_session, queue)); + } + + public void testACLManagerConfigurationPluginManagerACLPlugin() throws ConfigurationException + { + _authzManager = new ACLManager(_conf, _pluginManager, ExchangeDenier.FACTORY); + + Exchange exchange = null; + assertFalse(_authzManager.authoriseDelete(_session, exchange)); + } + + public void testConfigurePlugins() throws ConfigurationException + { + Configuration hostConfig = new PropertiesConfiguration(); + hostConfig.setProperty("queueDenier", "thisoneneither"); + _authzManager.configureHostPlugins(new SecurityConfiguration(hostConfig)); + AMQQueue queue = new MockAMQQueue("thisoneneither"); + assertFalse(_authzManager.authoriseDelete(_session, queue)); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.java new file mode 100644 index 0000000000..f62b0c6241 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/ExchangeDenier.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.security.access; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.access.plugins.AllowAll; + +public class ExchangeDenier extends AllowAll +{ + + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return name.startsWith("exchangeDenier"); + } + + public ACLPlugin newInstance(Configuration config) + { + return new ExchangeDenier(); + } + }; + + @Override + public AuthzResult authoriseDelete(AMQProtocolSession session, Exchange exchange) + { + return AuthzResult.DENIED; + } + + @Override + public String getPluginName() + { + return getClass().getSimpleName(); + } + + @Override + public boolean supportsTag(String name) + { + return name.equals("exchangeDenier"); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java new file mode 100644 index 0000000000..e1e93ae9cb --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/PrincipalPermissionsTest.java @@ -0,0 +1,156 @@ +/* + * + * 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.security.access; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.amqp_0_9.ExchangeDeclareBodyImpl; +import org.apache.qpid.framing.amqp_0_9.QueueDeclareBodyImpl; +import org.apache.qpid.framing.amqp_8_0.QueueBindBodyImpl; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class PrincipalPermissionsTest extends TestCase +{ + + private String _user = "user"; + private PrincipalPermissions _perms; + + // Common things that are passed to frame constructors + private AMQShortString _queueName = new AMQShortString(this.getClass().getName()+"queue"); + private AMQShortString _exchangeName = new AMQShortString("amq.direct"); + private AMQShortString _routingKey = new AMQShortString(this.getClass().getName()+"route"); + private int _ticket = 1; + private FieldTable _arguments = null; + private boolean _nowait = false; + private boolean _passive = false; + private boolean _durable = false; + private boolean _autoDelete = false; + private AMQShortString _exchangeType = new AMQShortString("direct"); + private boolean _internal = false; + + private DirectExchange _exchange; + private VirtualHost _virtualHost; + private AMQShortString _owner = new AMQShortString(this.getClass().getName()+"owner"); + private AMQQueue _queue; + private Boolean _temporary = false; + + @Override + public void setUp() + { + _perms = new PrincipalPermissions(_user); + try + { + PropertiesConfiguration env = new PropertiesConfiguration(); + _virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env)); + _exchange = DirectExchange.TYPE.newInstance(_virtualHost, _exchangeName, _durable, _ticket, _autoDelete); + _queue = AMQQueueFactory.createAMQQueueImpl(_queueName, false, _owner , false, _virtualHost, _arguments); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + public void testPrincipalPermissions() + { + assertNotNull(_perms); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.ACCESS, (Object[]) null)); + } + + // FIXME: test has been disabled since the permissions assume that the user has tried to create + // the queue first. QPID-1597 + public void disableTestBind() throws Exception + { + QueueBindBodyImpl bind = new QueueBindBodyImpl(_ticket, _queueName, _exchangeName, _routingKey, _nowait, _arguments); + Object[] args = new Object[]{bind, _exchange, _queue, _routingKey}; + + assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.BIND, args)); + _perms.grant(Permission.BIND, (Object[]) null); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.BIND, args)); + } + + public void testQueueCreate() + { + Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, _routingKey}; + Object[] authArgs = new Object[]{_autoDelete, _queueName}; + + assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + _perms.grant(Permission.CREATEQUEUE, grantArgs); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + } + + public void testQueueCreateWithNullRoutingKey() + { + Object[] grantArgs = new Object[]{_temporary , _queueName, _exchangeName, null}; + Object[] authArgs = new Object[]{_autoDelete, _queueName}; + + assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + _perms.grant(Permission.CREATEQUEUE, grantArgs); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEQUEUE, authArgs)); + } + + // FIXME disabled, this fails due to grant putting the grant into the wrong map QPID-1598 + public void disableTestExchangeCreate() + { + ExchangeDeclareBodyImpl exchangeDeclare = + new ExchangeDeclareBodyImpl(_ticket, _exchangeName, _exchangeType, _passive, _durable, + _autoDelete, _internal, _nowait, _arguments); + Object[] authArgs = new Object[]{exchangeDeclare}; + Object[] grantArgs = new Object[]{_exchangeName, _exchangeType}; + + assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs)); + _perms.grant(Permission.CREATEEXCHANGE, grantArgs); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CREATEEXCHANGE, authArgs)); + } + + public void testConsume() + { + Object[] authArgs = new Object[]{_queue}; + Object[] grantArgs = new Object[]{_queueName, _temporary, _temporary}; + + /* FIXME: This throws a null pointer exception QPID-1599 + * assertFalse(_perms.authorise(Permission.CONSUME, authArgs)); + */ + _perms.grant(Permission.CONSUME, grantArgs); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.CONSUME, authArgs)); + } + + public void testPublish() + { + Object[] authArgs = new Object[]{_exchange, _routingKey}; + Object[] grantArgs = new Object[]{_exchange.getName(), _routingKey}; + + assertEquals(AuthzResult.DENIED, _perms.authorise(Permission.PUBLISH, authArgs)); + _perms.grant(Permission.PUBLISH, grantArgs); + assertEquals(AuthzResult.ALLOWED, _perms.authorise(Permission.PUBLISH, authArgs)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java new file mode 100644 index 0000000000..5497f0ae44 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/QueueDenier.java @@ -0,0 +1,68 @@ +/* + * 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.security.access; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.security.access.plugins.AllowAll; + +public class QueueDenier extends AllowAll +{ + + public static final ACLPluginFactory FACTORY = new ACLPluginFactory() + { + public boolean supportsTag(String name) + { + return name.equals("queueDenier"); + } + + public ACLPlugin newInstance(Configuration config) + { + QueueDenier plugin = new QueueDenier(); + plugin.setConfiguration(config); + return plugin; + } + }; + + private String _queueName = ""; + + + @Override + public AuthzResult authoriseDelete(AMQProtocolSession session, AMQQueue queue) + { + if (!(queue.getName().toString().equals(_queueName))) + { + return AuthzResult.ALLOWED; + } + else + { + return AuthzResult.DENIED; + } + } + + @Override + public void setConfiguration(Configuration config) + { + _queueName = config.getString("queueDenier"); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java new file mode 100644 index 0000000000..958ee35476 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/management/AMQUserManagementMBeanTest.java @@ -0,0 +1,271 @@ +/* + * + * 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.security.access.management; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.management.MBeanInvocationHandlerImpl; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; + +import junit.framework.TestCase; + +/* Note: The main purpose is to test the jmx access rights file manipulation + * within AMQUserManagementMBean. The Principal Databases are tested by their own tests, + * this test just exercises their usage in AMQUserManagementMBean. + */ +public class AMQUserManagementMBeanTest extends TestCase +{ + private PlainPasswordFilePrincipalDatabase _database; + private AMQUserManagementMBean _amqumMBean; + + private File _passwordFile; + private File _accessFile; + + private static final String TEST_USERNAME = "testuser"; + private static final String TEST_PASSWORD = "password"; + + @Override + protected void setUp() throws Exception + { + _database = new PlainPasswordFilePrincipalDatabase(); + _amqumMBean = new AMQUserManagementMBean(); + loadFreshTestPasswordFile(); + loadFreshTestAccessFile(); + } + + @Override + protected void tearDown() throws Exception + { + _passwordFile.delete(); + _accessFile.delete(); + } + + public void testDeleteUser() + { + loadFreshTestPasswordFile(); + loadFreshTestAccessFile(); + + //try deleting a non existant user + assertFalse(_amqumMBean.deleteUser("made.up.username")); + + assertTrue(_amqumMBean.deleteUser(TEST_USERNAME)); + } + + public void testDeleteUserIsSavedToAccessFile() + { + loadFreshTestPasswordFile(); + loadFreshTestAccessFile(); + + assertTrue(_amqumMBean.deleteUser(TEST_USERNAME)); + + //check the access rights were actually deleted from the file + try{ + BufferedReader reader = new BufferedReader(new FileReader(_accessFile)); + + //check the 'generated by' comment line is present + assertTrue("File has no content", reader.ready()); + assertTrue("'Generated by' comment line was missing",reader.readLine().contains("Generated by " + + "AMQUserManagementMBean Console : Last edited by user:")); + + //there should also be a modified date/time comment line + assertTrue("File has no modified date/time comment line", reader.ready()); + assertTrue("Modification date/time comment line was missing",reader.readLine().startsWith("#")); + + //the access file should not contain any further data now as we just deleted the only user + assertFalse("User access data was present when it should have been deleted", reader.ready()); + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + + } + + public void testSetRights() + { + loadFreshTestPasswordFile(); + loadFreshTestAccessFile(); + + assertFalse(_amqumMBean.setRights("made.up.username", true, false, false)); + + assertTrue(_amqumMBean.setRights(TEST_USERNAME, true, false, false)); + assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, true, false)); + assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, false, true)); + } + + public void testSetRightsIsSavedToAccessFile() + { + loadFreshTestPasswordFile(); + loadFreshTestAccessFile(); + + assertTrue(_amqumMBean.setRights(TEST_USERNAME, false, false, true)); + + //check the access rights were actually updated in the file + try{ + BufferedReader reader = new BufferedReader(new FileReader(_accessFile)); + + //check the 'generated by' comment line is present + assertTrue("File has no content", reader.ready()); + assertTrue("'Generated by' comment line was missing",reader.readLine().contains("Generated by " + + "AMQUserManagementMBean Console : Last edited by user:")); + + //there should also be a modified date/time comment line + assertTrue("File has no modified date/time comment line", reader.ready()); + assertTrue("Modification date/time comment line was missing",reader.readLine().startsWith("#")); + + //the access file should not contain any further data now as we just deleted the only user + assertTrue("User access data was not updated in the access file", + reader.readLine().equals(TEST_USERNAME + "=" + MBeanInvocationHandlerImpl.ADMIN)); + + //the access file should not contain any further data now as we just deleted the only user + assertFalse("Additional user access data was present when there should be no more", reader.ready()); + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + } + + public void testMBeanVersion() + { + try + { + ObjectName name = _amqumMBean.getObjectName(); + assertEquals(AMQUserManagementMBean.VERSION, Integer.parseInt(name.getKeyProperty("version"))); + } + catch (MalformedObjectNameException e) + { + fail(e.getMessage()); + } + } + + public void testSetAccessFileWithMissingFile() + { + try + { + _amqumMBean.setAccessFile("made.up.filename"); + } + catch (IOException e) + { + fail("Should not have been an IOE." + e.getMessage()); + } + catch (ConfigurationException e) + { + assertTrue(e.getMessage(), e.getMessage().endsWith("does not exist")); + } + } + + public void testSetAccessFileWithReadOnlyFile() + { + File testFile = null; + try + { + testFile = File.createTempFile(this.getClass().getName(),".access.readonly"); + BufferedWriter passwordWriter = new BufferedWriter(new FileWriter(testFile, false)); + passwordWriter.write(TEST_USERNAME + ":" + TEST_PASSWORD); + passwordWriter.newLine(); + passwordWriter.flush(); + passwordWriter.close(); + + testFile.setReadOnly(); + _amqumMBean.setAccessFile(testFile.getPath()); + } + catch (IOException e) + { + fail("Access file was not created." + e.getMessage()); + } + catch (ConfigurationException e) + { + fail("There should not have been a configuration exception." + e.getMessage()); + } + + testFile.delete(); + } + + // ============================ Utility methods ========================= + + private void loadFreshTestPasswordFile() + { + try + { + if(_passwordFile == null) + { + _passwordFile = File.createTempFile(this.getClass().getName(),".password"); + } + + BufferedWriter passwordWriter = new BufferedWriter(new FileWriter(_passwordFile, false)); + passwordWriter.write(TEST_USERNAME + ":" + TEST_PASSWORD); + passwordWriter.newLine(); + passwordWriter.flush(); + passwordWriter.close(); + _database.setPasswordFile(_passwordFile.toString()); + _amqumMBean.setPrincipalDatabase(_database); + } + catch (IOException e) + { + fail("Unable to create test password file: " + e.getMessage()); + } + } + + private void loadFreshTestAccessFile() + { + try + { + if(_accessFile == null) + { + _accessFile = File.createTempFile(this.getClass().getName(),".access"); + } + + BufferedWriter accessWriter = new BufferedWriter(new FileWriter(_accessFile,false)); + accessWriter.write("#Last Updated By comment"); + accessWriter.newLine(); + accessWriter.write("#Date/time comment"); + accessWriter.newLine(); + accessWriter.write(TEST_USERNAME + "=" + MBeanInvocationHandlerImpl.READONLY); + accessWriter.newLine(); + accessWriter.flush(); + accessWriter.close(); + } + catch (IOException e) + { + fail("Unable to create test access file: " + e.getMessage()); + } + + try{ + _amqumMBean.setAccessFile(_accessFile.toString()); + } + catch (Exception e) + { + fail("Unable to set access file: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java new file mode 100644 index 0000000000..ff1fb8c97d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/access/plugins/network/FirewallPluginTest.java @@ -0,0 +1,295 @@ +/* + * + * 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.security.access.plugins.network; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.TestIoSession; +import org.apache.qpid.server.security.access.ACLPlugin.AuthzResult; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class FirewallPluginTest extends TestCase +{ + + public class RuleInfo + { + private String _access; + private String _network; + private String _hostname; + + public void setAccess(String _access) + { + this._access = _access; + } + + public String getAccess() + { + return _access; + } + + public void setNetwork(String _network) + { + this._network = _network; + } + + public String getNetwork() + { + return _network; + } + + public void setHostname(String _hostname) + { + this._hostname = _hostname; + } + + public String getHostname() + { + return _hostname; + } + } + + private TestableMemoryMessageStore _store; + private VirtualHost _virtualHost; + private AMQMinaProtocolSession _session; + + @Override + public void setUp() throws Exception + { + _store = new TestableMemoryMessageStore(); + PropertiesConfiguration env = new PropertiesConfiguration(); + _virtualHost = new VirtualHost(new VirtualHostConfiguration("test", env)); + TestIoSession iosession = new TestIoSession(); + iosession.setAddress("127.0.0.1"); + VirtualHostRegistry virtualHostRegistry = null; + AMQCodecFactory codecFactory = new AMQCodecFactory(true); + _session = new AMQMinaProtocolSession(iosession, virtualHostRegistry, codecFactory); + } + + private FirewallPlugin initialisePlugin(String defaultAction, RuleInfo[] rules) throws IOException, ConfigurationException + { + // Create sample config file + File confFile = File.createTempFile(getClass().getSimpleName()+"conffile", null); + confFile.deleteOnExit(); + BufferedWriter buf = new BufferedWriter(new FileWriter(confFile)); + buf.write("<firewall default-action=\""+defaultAction+"\">\n"); + if (rules != null) + { + for (RuleInfo rule : rules) + { + buf.write("<rule"); + buf.write(" access=\""+rule.getAccess()+"\""); + if (rule.getHostname() != null) + { + buf.write(" hostname=\""+rule.getHostname()+"\""); + } + if (rule.getNetwork() != null) + { + buf.write(" network=\""+rule.getNetwork()+"\""); + } + buf.write("/>\n"); + } + } + buf.write("</firewall>"); + buf.close(); + + // Configure plugin + FirewallPlugin plugin = new FirewallPlugin(); + plugin.setConfiguration(new XMLConfiguration(confFile)); + return plugin; + } + + private FirewallPlugin initialisePlugin(String string) throws ConfigurationException, IOException + { + return initialisePlugin(string, null); + } + + public void testDefaultAction() throws Exception + { + // Test simple deny + FirewallPlugin plugin = initialisePlugin("deny"); + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Test simple allow + plugin = initialisePlugin("allow"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + + public void testSingleIPRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setNetwork("192.168.23.23"); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testSingleNetworkRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setNetwork("192.168.23.0/24"); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testSingleHostRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setHostname(new InetSocketAddress("127.0.0.1", 5672).getHostName()); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testSingleHostWilcardRule() throws Exception + { + RuleInfo rule = new RuleInfo(); + rule.setAccess("allow"); + rule.setHostname(".*ocal.*"); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{rule}); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testSeveralFirstAllowsAccess() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("192.168.23.23"); + + RuleInfo secondRule = new RuleInfo(); + secondRule.setAccess("deny"); + secondRule.setNetwork("192.168.42.42"); + + RuleInfo thirdRule = new RuleInfo(); + thirdRule.setAccess("deny"); + thirdRule.setHostname("localhost"); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testSeveralLastAllowsAccess() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("deny"); + firstRule.setHostname("localhost"); + + RuleInfo secondRule = new RuleInfo(); + secondRule.setAccess("deny"); + secondRule.setNetwork("192.168.42.42"); + + RuleInfo thirdRule = new RuleInfo(); + thirdRule.setAccess("allow"); + thirdRule.setNetwork("192.168.23.23"); + + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule, secondRule, thirdRule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testNetmask() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("192.168.23.0/24"); + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testCommaSeperatedNetmask() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setNetwork("10.1.1.1/8, 192.168.23.0/24"); + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("192.168.23.23"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + + public void testCommaSeperatedHostnames() throws Exception + { + RuleInfo firstRule = new RuleInfo(); + firstRule.setAccess("allow"); + firstRule.setHostname("foo, bar, "+new InetSocketAddress("127.0.0.1", 5672).getHostName()); + FirewallPlugin plugin = initialisePlugin("deny", new RuleInfo[]{firstRule}); + ((TestIoSession) _session.getIOSession()).setAddress("10.0.0.1"); + assertEquals(AuthzResult.DENIED, plugin.authoriseConnect(_session, _virtualHost)); + + // Set session IP so that we're connected from the right address + ((TestIoSession) _session.getIOSession()).setAddress("127.0.0.1"); + assertEquals(AuthzResult.ALLOWED, plugin.authoriseConnect(_session, _virtualHost)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..413b974986 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,447 @@ +/* + * + * 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.security.auth.database; + +import junit.framework.TestCase; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.commons.codec.binary.Base64; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + + private static final String USERNAME = "testUser"; + private static final String PASSWORD = "guest"; + private static final String PASSWORD_B64MD5HASHED = "CE4DQ6BIb/BVMN9scFyLtA=="; + private static char[] PASSWORD_MD5_CHARS; + private static final String PRINCIPAL_USERNAME = "testUserPrincipal"; + private static final Principal PRINCIPAL = new UsernamePrincipal(PRINCIPAL_USERNAME); + private Base64MD5PasswordFilePrincipalDatabase _database; + private File _pwdFile; + + static + { + try + { + Base64 b64 = new Base64(); + byte[] md5passBytes = PASSWORD_B64MD5HASHED.getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + byte[] decoded = b64.decode(md5passBytes); + + PASSWORD_MD5_CHARS = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + PASSWORD_MD5_CHARS[index++] = (char) c; + } + } + catch (UnsupportedEncodingException e) + { + fail("Unable to perform B64 decode to get the md5 char[] password"); + } + } + + + public void setUp() throws Exception + { + _database = new Base64MD5PasswordFilePrincipalDatabase(); + _pwdFile = File.createTempFile(this.getClass().getName(), "pwd"); + _pwdFile.deleteOnExit(); + _database.setPasswordFile(_pwdFile.getAbsolutePath()); + } + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile("Base64MD5PDPDTest","tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(USERNAME + i + ":Password"); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.setPasswordFile(file.toString()); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + /** **** Test Methods ************** */ + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + + Principal principal = new Principal() + { + public String getName() + { + return USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + + PasswordCallback callback = new PasswordCallback("prompt",false); + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + loadPasswordFile(testFile); + + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + assertNotNull("Created User was not saved", _database.getUser(USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + + testFile.delete(); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_B64MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray()); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", CREATED_USERNAME, result[0]); + assertEquals("Password not correct,", CREATED_B64MD5HASHED_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(USERNAME + "0")); + + testFile.delete(); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List<Principal> users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + + testFile.delete(); + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "guest"; + String NEW_PASSWORD_HASH = "CE4DQ6BIb/BVMN9scFyLtA=="; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD_HASH, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.setPasswordFile("DoesntExist"); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.setPasswordFile(testFile.toString()); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + testFile.delete(); + } + + public void testCreateUserPrincipal() throws IOException + { + _database.createPrincipal(PRINCIPAL, PASSWORD.toCharArray()); + Principal newPrincipal = _database.getUser(PRINCIPAL_USERNAME); + assertNotNull(newPrincipal); + assertEquals(PRINCIPAL.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + //assertFalse(_pwdDB.verifyPassword(_username, null)); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, (PASSWORD+"z").toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + + try + { + _database.verifyPassword("made.up.username", PASSWORD.toCharArray()); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(PRINCIPAL, newPwd); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, newPwd)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java new file mode 100644 index 0000000000..aa85cac758 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java @@ -0,0 +1,95 @@ +/* + * + * 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.security.auth.database; + +import junit.framework.TestCase; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; + +/* + Note User is mainly tested by Base64MD5PFPDTest this is just to catch the extra methods + */ +public class HashedUserTest extends TestCase +{ + + String USERNAME = "username"; + String PASSWORD = "password"; + String B64_ENCODED_PASSWORD = "cGFzc3dvcmQ="; + + public void testToLongArrayConstructor() + { + try + { + HashedUser user = new HashedUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } + + public void testArrayConstructor() + { + try + { + HashedUser user = new HashedUser(new String[]{USERNAME, B64_ENCODED_PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] hash = B64_ENCODED_PASSWORD.toCharArray(); + + try + { + for (byte c : user.getEncodedPassword()) + { + assertEquals("Password incorrect", hash[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + hash = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", hash[index], c); + index++; + } + + } + catch (UnsupportedEncodingException e) + { + fail(e.getMessage()); + } + } +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..20b8d0a7b4 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,396 @@ +/* + * + * 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.security.auth.database; + +import junit.framework.TestCase; + +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.regex.Pattern; + +public class PlainPasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + private static final String TEST_PASSWORD = "testPassword"; + private static final char[] TEST_PASSWORD_CHARS = TEST_PASSWORD.toCharArray(); + private static final String TEST_USERNAME = "testUser"; + + private Principal _principal = new UsernamePrincipal(TEST_USERNAME); + private PlainPasswordFilePrincipalDatabase _database; + + public void setUp() throws Exception + { + _database = new PlainPasswordFilePrincipalDatabase(); + } + + // ******* Test Methods ********** // + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + loadPasswordFile(testFile); + + assertNotNull("Created User was not saved", _database.getUser(CREATED_USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + testFile.delete(); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + Principal principal = new Principal() + { + public String getName() + { + return TEST_USERNAME; + } + }; + + _database.createPrincipal(principal, TEST_PASSWORD_CHARS); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME, result[0]); + assertEquals("Password not correct,", TEST_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(TEST_USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(TEST_USERNAME + "0")); + + testFile.delete(); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List<Principal> users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(TEST_USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + + testFile.delete(); + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(TEST_USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "NewPassword"; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.setPasswordFile("DoesntExist"); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.setPasswordFile(testFile.toString()); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + testFile.delete(); + } + + private void createUserPrincipal() throws IOException + { + File testFile = createPasswordFile(0, 0); + loadPasswordFile(testFile); + + _database.createPrincipal(_principal, TEST_PASSWORD_CHARS); + Principal newPrincipal = _database.getUser(TEST_USERNAME); + assertNotNull(newPrincipal); + assertEquals(_principal.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + assertFalse(_database.verifyPassword(TEST_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(TEST_USERNAME, "massword".toCharArray())); + assertTrue(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + + try + { + _database.verifyPassword("made.up.username", TEST_PASSWORD_CHARS); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(_principal, newPwd); + assertFalse(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + assertTrue(_database.verifyPassword(TEST_USERNAME, newPwd)); + } + + + + // *********** Utility Methods ******** // + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile(this.getClass().getName(),"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(TEST_USERNAME + i + ":" + TEST_PASSWORD); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.setPasswordFile(file.toString()); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java new file mode 100644 index 0000000000..7f0843d46e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java @@ -0,0 +1,78 @@ +/* + * + * 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.security.auth.database; + +import junit.framework.TestCase; + +/* + Note PlainUser is mainly tested by PlainPFPDTest, this is just to catch the extra methods + */ +public class PlainUserTest extends TestCase +{ + + String USERNAME = "username"; + String PASSWORD = "password"; + + public void testTooLongArrayConstructor() + { + try + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + } + + public void testStringArrayConstructor() + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] password = PASSWORD.toCharArray(); + + try + { + for (byte c : user.getPasswordBytes()) + { + assertEquals("Password incorrect", password[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + password = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", password[index], c); + index++; + } + } +} + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java new file mode 100644 index 0000000000..e8c24da68d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticatorTest.java @@ -0,0 +1,267 @@ +/* + * + * 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.security.auth.rmi; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collections; + +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; + +import junit.framework.TestCase; + +public class RMIPasswordAuthenticatorTest extends TestCase +{ + private final String USERNAME = "guest"; + private final String PASSWORD = "guest"; + private final String B64_MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + private RMIPasswordAuthenticator _rmipa; + + private Base64MD5PasswordFilePrincipalDatabase _md5Pd; + private File _md5PwdFile; + + private PlainPasswordFilePrincipalDatabase _plainPd; + private File _plainPwdFile; + + private Subject testSubject; + + protected void setUp() throws Exception + { + _rmipa = new RMIPasswordAuthenticator(); + + _md5Pd = new Base64MD5PasswordFilePrincipalDatabase(); + _md5PwdFile = createTempPasswordFile(this.getClass().getName()+"md5pwd", USERNAME, B64_MD5HASHED_PASSWORD); + _md5Pd.setPasswordFile(_md5PwdFile.getAbsolutePath()); + + _plainPd = new PlainPasswordFilePrincipalDatabase(); + _plainPwdFile = createTempPasswordFile(this.getClass().getName()+"plainpwd", USERNAME, PASSWORD); + _plainPd.setPasswordFile(_plainPwdFile.getAbsolutePath()); + + testSubject = new Subject(true, + Collections.singleton(new JMXPrincipal(USERNAME)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + + private File createTempPasswordFile(String filenamePrefix, String user, String password) + { + try + { + File testFile = File.createTempFile(filenamePrefix,"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + writer.write(user + ":" + password); + writer.newLine(); + + writer.flush(); + writer.close(); + + return testFile; + } + catch (IOException e) + { + fail("Unable to create temporary test password file." + e.getMessage()); + } + + return null; + } + + + //********** Test Methods *********// + + + public void testAuthenticate() + { + String[] credentials; + Subject newSubject; + + // Test when no PD has been set + try + { + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to lack of principal database"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.UNABLE_TO_LOOKUP, se.getMessage()); + } + + //The PrincipalDatabase's are tested primarily by their own tests, but + //minimal tests are done here to exercise their usage in this area. + + // Test correct passwords are verified with an MD5 PD + try + { + _rmipa.setPrincipalDatabase(_md5Pd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception:" + e.getMessage()); + } + + // Test incorrect passwords are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an MD5 PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non-existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test correct passwords are verified with a Plain PD + try + { + _rmipa.setPrincipalDatabase(_plainPd); + credentials = new String[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + assertTrue("Returned subject does not equal expected value", + newSubject.equals(testSubject)); + } + catch (Exception e) + { + fail("Unexpected Exception"); + } + + // Test incorrect passwords are not verified with a Plain PD + try + { + credentials = new String[]{USERNAME, PASSWORD+"incorrect"}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to incorrect password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test non-existent accounts are not verified with an Plain PD + try + { + credentials = new String[]{USERNAME+"invalid", PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to non existant account"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + + // Test handling of non-string credential's + try + { + Object[] objCredentials = new Object[]{USERNAME, PASSWORD}; + newSubject = _rmipa.authenticate(objCredentials); + fail("SecurityException expected due to non string[] credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_STRING_ARRAY, se.getMessage()); + } + + // Test handling of incorrect number of credential's + try + { + credentials = new String[]{USERNAME, PASSWORD, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to supplying wrong number of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_HAVE_2_ELEMENTS, se.getMessage()); + } + + // Test handling of null credential's + try + { + //send a null array + credentials = null; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to not supplying an array of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.CREDENTIALS_REQUIRED, se.getMessage()); + } + + try + { + //send a null password + credentials = new String[]{USERNAME, null}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + + try + { + //send a null username + credentials = new String[]{null, PASSWORD}; + newSubject = _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null username"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + RMIPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java new file mode 100644 index 0000000000..f80413d4f8 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.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.security.auth.sasl; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import junit.framework.TestCase; + +public abstract class SaslServerTestCase extends TestCase +{ + protected SaslServer server; + protected String username = "u"; + protected String password = "p"; + protected String notpassword = "a"; + protected PrincipalDatabase db = new TestPrincipalDatabase(); + + protected byte[] correctresponse; + protected byte[] wrongresponse; + + public void testSucessfulAuth() throws SaslException + { + byte[] resp = this.server.evaluateResponse(correctresponse); + assertNull(resp); + } + + public void testFailAuth() + { + boolean exceptionCaught = false; + try + { + byte[] resp = this.server.evaluateResponse(wrongresponse); + } + catch (SaslException e) + { + assertEquals("Authentication failed", e.getCause().getMessage()); + exceptionCaught = true; + } + if (!exceptionCaught) + { + fail("Should have thrown SaslException"); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java new file mode 100644 index 0000000000..8507e49e17 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java @@ -0,0 +1,91 @@ +/* + * + * 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.security.auth.sasl; + +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.Map; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +public class TestPrincipalDatabase implements PrincipalDatabase +{ + + public boolean createPrincipal(Principal principal, char[] password) + { + // TODO Auto-generated method stub + return false; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + // TODO Auto-generated method stub + return null; + } + + public Principal getUser(String username) + { + // TODO Auto-generated method stub + return null; + } + + public List<Principal> getUsers() + { + // TODO Auto-generated method stub + return null; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + callback.setPassword("p".toCharArray()); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public void reload() throws IOException + { + // TODO Auto-generated method stub + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java new file mode 100644 index 0000000000..6245064bf7 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.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.security.auth.sasl.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AMQPlainSaslServerTest extends SaslServerTestCase +{ + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new AmqPlainInitialiser(); + handler.initialise(db); + this.server = new AmqPlainSaslServer(handler.getCallbackHandler()); + FieldTable table = FieldTableFactory.newFieldTable(); + table.setString("LOGIN", username); + table.setString("PASSWORD", password); + correctresponse = table.getDataAsBytes(); + table.setString("PASSWORD", notpassword); + wrongresponse = table.getDataAsBytes(); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java new file mode 100644 index 0000000000..5dd51250dc --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java @@ -0,0 +1,39 @@ +/* + * + * 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.security.auth.sasl.plain; + +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainSaslServerTest extends SaslServerTestCase +{ + + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new PlainInitialiser(); + handler.initialise(db); + this.server = new PlainSaslServer(handler.getCallbackHandler()); + correctresponse = new byte[]{0x0, (byte) username.charAt(0), 0x0, (byte) password.charAt(0)}; + wrongresponse = new byte[]{0x0,(byte) username.charAt(0), 0x0, (byte) notpassword.charAt(0)}; + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java new file mode 100644 index 0000000000..a695a67eea --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreShutdownTest.java @@ -0,0 +1,81 @@ +/* + * + * 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; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +import java.util.List; + +public class MessageStoreShutdownTest extends InternalBrokerBaseCase +{ + + public void test() + { + subscribe(_session, _channel, _queue); + + try + { + publishMessages(_session, _channel, 1); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail(e.getMessage()); + } + + try + { + _registry.close(); + } + catch (Exception e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail(e.getMessage()); + } + + assertTrue("Session should now be closed", _session.isClosed()); + + + //Test attempting to modify the broker state after session has been closed. + + //The Message should have been removed from the unacked list. + + //Ack Messages + List<InternalTestProtocolSession.DeliveryPair> list = _session.getDelivers(_channel.getChannelId(), new AMQShortString("sgen_1"), 1); + + InternalTestProtocolSession.DeliveryPair pair = list.get(0); + + try + { + // The message should now be requeued and so unable to ack it. + _channel.acknowledgeMessage(pair.getDeliveryTag(), false); + } + catch (AMQException e) + { + assertEquals("Incorrect exception thrown", "Single ack on delivery tag 1 not known for channel:1", e.getMessage()); + } + + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java new file mode 100644 index 0000000000..4317a501ed --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTest.java @@ -0,0 +1,646 @@ +/* + * + * 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; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQPriorityQueue; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.queue.ExchangeBinding; +import org.apache.qpid.server.txn.NonTransactionalContext; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.List; + +/** + * This tests the MessageStores by using the available interfaces. + * + * This test validates that Exchanges, Queues, Bindings and Messages are persisted correctly. + */ +public class MessageStoreTest extends TestCase +{ + + private static final int DEFAULT_PRIORTY_LEVEL = 5; + private static final Logger _logger = LoggerFactory.getLogger(MessageStoreTest.class); + + public void testMemoryMessageStore() + { + + PropertiesConfiguration config = new PropertiesConfiguration(); + + config.addProperty("store.class", "org.apache.qpid.server.store.MemoryMessageStore"); + + runTestWithStore(config); + } + + public void DISABLE_testDerbyMessageStore() + { + PropertiesConfiguration config = new PropertiesConfiguration(); + + config.addProperty("store.environment-path", "derbyDB_MST"); + config.addProperty("store.class", "org.apache.qpid.server.store.DerbyMessageStore"); + + runTestWithStore(config); + } + + private void reload(Configuration configuration) + { + if (_virtualHost != null) + { + try + { + _virtualHost.close(); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + try + { + _virtualHost = new VirtualHost(new VirtualHostConfiguration(getClass().getName(), configuration)); + ApplicationRegistry.getInstance().getVirtualHostRegistry().registerVirtualHost(_virtualHost); + } + catch (Exception e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + VirtualHost _virtualHost = null; + String virtualHostName = "MessageStoreTest"; + + AMQShortString nonDurableExchangeName = new AMQShortString("MST-NonDurableDirectExchange"); + AMQShortString directExchangeName = new AMQShortString("MST-DirectExchange"); + AMQShortString topicExchangeName = new AMQShortString("MST-TopicExchange"); + AMQShortString queueOwner = new AMQShortString("MST"); + + AMQShortString durablePriorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue-Durable"); + AMQShortString durableTopicQueueName = new AMQShortString("MST-TopicQueue-Durable"); + AMQShortString priorityTopicQueueName = new AMQShortString("MST-PriorityTopicQueue"); + AMQShortString topicQueueName = new AMQShortString("MST-TopicQueue"); + + AMQShortString durablePriorityQueueName = new AMQShortString("MST-PriorityQueue-Durable"); + AMQShortString durableQueueName = new AMQShortString("MST-Queue-Durable"); + AMQShortString priorityQueueName = new AMQShortString("MST-PriorityQueue"); + AMQShortString queueName = new AMQShortString("MST-Queue"); + + AMQShortString directRouting = new AMQShortString("MST-direct"); + AMQShortString topicRouting = new AMQShortString("MST-topic"); + + protected void setUp() + { + ApplicationRegistry.getInstance(1); + } + + protected void tearDown() + { + ApplicationRegistry.remove(1); + } + + protected void runTestWithStore(Configuration configuration) + { + //Ensure Environment Path is empty + cleanup(configuration); + + //Load the Virtualhost with the required MessageStore + reload(configuration); + + MessageStore messageStore = _virtualHost.getMessageStore(); + + createAllQueues(); + createAllTopicQueues(); + + //Register Non-Durable DirectExchange + Exchange nonDurableExchange = createExchange(DirectExchange.TYPE, nonDurableExchangeName, false); + bindAllQueuesToExchange(nonDurableExchange, directRouting); + + //Register DirectExchange + Exchange directExchange = createExchange(DirectExchange.TYPE, directExchangeName, true); + bindAllQueuesToExchange(directExchange, directRouting); + + //Register TopicExchange + Exchange topicExchange = createExchange(TopicExchange.TYPE, topicExchangeName, true); + bindAllTopicQueuesToExchange(topicExchange, topicRouting); + + //Send Message To NonDurable direct Exchange = persistent + sendMessageOnExchange(nonDurableExchange, directRouting, true); + // and non-persistent + sendMessageOnExchange(nonDurableExchange, directRouting, false); + + //Send Message To direct Exchange = persistent + sendMessageOnExchange(directExchange, directRouting, true); + // and non-persistent + sendMessageOnExchange(directExchange, directRouting, false); + + //Send Message To topic Exchange = persistent + sendMessageOnExchange(topicExchange, topicRouting, true); + // and non-persistent + sendMessageOnExchange(topicExchange, topicRouting, false); + + //Ensure all the Queues have four messages (one transient, one persistent) x 2 exchange routings + validateMessageOnQueues(4, true); + //Ensure all the topics have two messages (one transient, one persistent) + validateMessageOnTopics(2, true); + + assertEquals("Not all queues correctly registered", 8, _virtualHost.getQueueRegistry().getQueues().size()); + + if (!messageStore.isPersistent()) + { + _logger.warn("Unable to test Persistent capabilities of messages store(" + messageStore.getClass() + ") as it is not capable of peristence."); + return; + } + + //Reload the Virtualhost to test persistence + _logger.info("Reloading Virtualhost"); + + VirtualHost original = _virtualHost; + + reload(configuration); + + assertTrue("Virtualhost has not been reloaded", original != _virtualHost); + + validateExchanges(); + + //Validate Durable Queues still have the persistentn message + validateMessageOnQueues(2, false); + //Validate Durable Queues still have the persistentn message + validateMessageOnTopics(1, false); + + //Validate Properties of Binding + validateBindingProperties(); + + //Validate Properties of Queues + validateQueueProperties(); + + //Validate Non-Durable Queues are gone. + assertNull("Non-Durable queue still registered:" + priorityQueueName, _virtualHost.getQueueRegistry().getQueue(priorityQueueName)); + assertNull("Non-Durable queue still registered:" + queueName, _virtualHost.getQueueRegistry().getQueue(queueName)); + assertNull("Non-Durable queue still registered:" + priorityTopicQueueName, _virtualHost.getQueueRegistry().getQueue(priorityTopicQueueName)); + assertNull("Non-Durable queue still registered:" + topicQueueName, _virtualHost.getQueueRegistry().getQueue(topicQueueName)); + + assertEquals("Not all queues correctly registered", 4, _virtualHost.getQueueRegistry().getQueues().size()); + } + + private void validateExchanges() + { + ExchangeRegistry registry = _virtualHost.getExchangeRegistry(); + + assertTrue(directExchangeName + " exchange NOT reloaded after failover", + registry.getExchangeNames().contains(directExchangeName)); + assertTrue(topicExchangeName + " exchange NOT reloaded after failover", + registry.getExchangeNames().contains(topicExchangeName)); + assertTrue(nonDurableExchangeName + " exchange reloaded after failover", + !registry.getExchangeNames().contains(nonDurableExchangeName)); + + // There are 5 required exchanges + our 2 durable queues + assertEquals("Incorrect number of exchanges available", 5 + 2, registry.getExchangeNames().size()); + } + + /** Validates that the Durable queues */ + private void validateBindingProperties() + { + QueueRegistry queueRegistry = _virtualHost.getQueueRegistry(); + + validateBindingProperties(queueRegistry.getQueue(durablePriorityQueueName).getExchangeBindings(), false); + validateBindingProperties(queueRegistry.getQueue(durablePriorityTopicQueueName).getExchangeBindings(), true); + validateBindingProperties(queueRegistry.getQueue(durableQueueName).getExchangeBindings(), false); + validateBindingProperties(queueRegistry.getQueue(durableTopicQueueName).getExchangeBindings(), true); + } + + /** + * Validate that each queue is bound once. + * + * @param bindings the set of bindings to validate + * @param useSelectors if set validate that the binding has a JMS_SELECTOR argument + */ + private void validateBindingProperties(List<ExchangeBinding> bindings, boolean useSelectors) + { + assertEquals("Each queue should only be bound once.", 1, bindings.size()); + + ExchangeBinding binding = bindings.get(0); + + if (useSelectors) + { + assertTrue("Binding does not contain a Selector argument.", + binding.getArguments().containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue())); + } + } + + private void validateQueueProperties() + { + QueueRegistry queueRegistry = _virtualHost.getQueueRegistry(); + + validateQueueProperties(queueRegistry.getQueue(durablePriorityQueueName), true); + validateQueueProperties(queueRegistry.getQueue(durablePriorityTopicQueueName), true); + validateQueueProperties(queueRegistry.getQueue(durableQueueName), false); + validateQueueProperties(queueRegistry.getQueue(durableTopicQueueName), false); + + } + + private void validateQueueProperties(AMQQueue queue, boolean usePriority) + { + if (usePriority) + { + assertEquals("Queue is no longer a Priority Queue", AMQPriorityQueue.class, queue.getClass()); + assertEquals("Priority Queue does not have set priorities", DEFAULT_PRIORTY_LEVEL, ((AMQPriorityQueue) queue).getPriorities()); + } + else + { + assertEquals("Queue is no longer a Priority Queue", SimpleAMQQueue.class, queue.getClass()); + } + } + + /** + * Delete the Store Environment path + * + * @param configuration The configuration that contains the store environment path. + */ + private void cleanup(Configuration configuration) + { + + String environment = configuration.getString("store.environment-path"); + + if (environment != null) + { + File environmentPath = new File(environment); + + if (environmentPath.exists()) + { + deleteDirectory(environmentPath); + } + } + } + + private void deleteDirectory(File path) + { + if (path.isDirectory()) + { + for (File file : path.listFiles()) + { + deleteDirectory(file); + } + } + else + { + path.delete(); + } + } + + private void sendMessageOnExchange(Exchange directExchange, AMQShortString routingKey, boolean deliveryMode) + { + //Set MessagePersustebce + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setDeliveryMode(deliveryMode ? Integer.valueOf(2).byteValue() : Integer.valueOf(1).byteValue()); + FieldTable headers = properties.getHeaders(); + headers.setString("Test", "MST"); + properties.setHeaders(headers); + + MessagePublishInfo messageInfo = new TestMessagePublishInfo(directExchange, false, false, routingKey); + + IncomingMessage currentMessage = null; + + try + { + currentMessage = new IncomingMessage(_virtualHost.getMessageStore().getNewMessageId(), + messageInfo, + new NonTransactionalContext(_virtualHost.getMessageStore(), + new StoreContext(), null, null), + new InternalTestProtocolSession()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + currentMessage.setMessageStore(_virtualHost.getMessageStore()); + currentMessage.setExchange(directExchange); + + ContentHeaderBody headerBody = new ContentHeaderBody(); + headerBody.classId = BasicConsumeBodyImpl.CLASS_ID; + headerBody.bodySize = 0; + + headerBody.properties = properties; + + try + { + currentMessage.setContentHeaderBody(headerBody); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + currentMessage.setExpiration(); + + try + { + currentMessage.route(); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + try + { + currentMessage.routingComplete(_virtualHost.getMessageStore(), new MessageHandleFactory()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + // check and deliver if header says body length is zero + if (currentMessage.allContentReceived()) + { + try + { + currentMessage.deliverToQueues(); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + } + + private void createAllQueues() + { + //Register Durable Priority Queue + createQueue(durablePriorityQueueName, true, true); + + //Register Durable Simple Queue + createQueue(durableQueueName, false, true); + + //Register NON-Durable Priority Queue + createQueue(priorityQueueName, true, false); + + //Register NON-Durable Simple Queue + createQueue(queueName, false, false); + } + + private void createAllTopicQueues() + { + //Register Durable Priority Queue + createQueue(durablePriorityTopicQueueName, true, true); + + //Register Durable Simple Queue + createQueue(durableTopicQueueName, false, true); + + //Register NON-Durable Priority Queue + createQueue(priorityTopicQueueName, true, false); + + //Register NON-Durable Simple Queue + createQueue(topicQueueName, false, false); + } + + private Exchange createExchange(ExchangeType type, AMQShortString name, boolean durable) + { + Exchange exchange = null; + + try + { + exchange = type.newInstance(_virtualHost, name, durable, 0, false); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + try + { + _virtualHost.getExchangeRegistry().registerExchange(exchange); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + return exchange; + } + + private void createQueue(AMQShortString queueName, boolean usePriority, boolean durable) + { + + FieldTable queueArguments = null; + + if (usePriority) + { + queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + } + + AMQQueue queue = null; + + //Ideally we would be able to use the QueueDeclareHandler here. + try + { + queue = AMQQueueFactory.createAMQQueueImpl(queueName, durable, queueOwner, false, _virtualHost, + queueArguments); + + validateQueueProperties(queue, usePriority); + + if (queue.isDurable() && !queue.isAutoDelete()) + { + _virtualHost.getMessageStore().createQueue(queue, queueArguments); + } + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + try + { + _virtualHost.getQueueRegistry().registerQueue(queue); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + } + + private void bindAllQueuesToExchange(Exchange exchange, AMQShortString routingKey) + { + FieldTable queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + + QueueRegistry queueRegistry = _virtualHost.getQueueRegistry(); + + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityQueueName), false, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableQueueName), false, null); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityQueueName), false, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(queueName), false, null); + } + + private void bindAllTopicQueuesToExchange(Exchange exchange, AMQShortString routingKey) + { + FieldTable queueArguments = new FieldTable(); + queueArguments.put(AMQQueueFactory.X_QPID_PRIORITIES, DEFAULT_PRIORTY_LEVEL); + + QueueRegistry queueRegistry = _virtualHost.getQueueRegistry(); + + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durablePriorityTopicQueueName), true, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(durableTopicQueueName), true, null); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(priorityTopicQueueName), true, queueArguments); + bindQueueToExchange(exchange, routingKey, queueRegistry.getQueue(topicQueueName), true, null); + } + + + protected void bindQueueToExchange(Exchange exchange, AMQShortString routingKey, AMQQueue queue, boolean useSelector, FieldTable queueArguments) + { + try + { + exchange.registerQueue(queueName, queue, queueArguments); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + + FieldTable bindArguments = null; + + if (useSelector) + { + bindArguments = new FieldTable(); + bindArguments.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), "Test = 'MST'"); + } + + try + { + queue.bind(exchange, routingKey, bindArguments); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + private void validateMessage(long messageCount, boolean allQueues) + { + validateMessageOnTopics(messageCount, allQueues); + validateMessageOnQueues(messageCount, allQueues); + } + + private void validateMessageOnTopics(long messageCount, boolean allQueues) + { + validateMessageOnQueue(durablePriorityTopicQueueName, messageCount); + validateMessageOnQueue(durableTopicQueueName, messageCount); + + if (allQueues) + { + validateMessageOnQueue(priorityTopicQueueName, messageCount); + validateMessageOnQueue(topicQueueName, messageCount); + } + } + + private void validateMessageOnQueues(long messageCount, boolean allQueues) + { + validateMessageOnQueue(durablePriorityQueueName, messageCount); + validateMessageOnQueue(durableQueueName, messageCount); + + if (allQueues) + { + validateMessageOnQueue(priorityQueueName, messageCount); + validateMessageOnQueue(queueName, messageCount); + } + } + + private void validateMessageOnQueue(AMQShortString queueName, long messageCount) + { + AMQQueue queue = _virtualHost.getQueueRegistry().getQueue(queueName); + + assertNotNull("Queue(" + queueName + ") not correctly registered:", queue); + + assertEquals("Incorrect Message count on queue:" + queueName, messageCount, queue.getMessageCount()); + } + + private class TestMessagePublishInfo implements MessagePublishInfo + { + + Exchange _exchange; + boolean _immediate; + boolean _mandatory; + AMQShortString _routingKey; + + TestMessagePublishInfo(Exchange exchange, boolean immediate, boolean mandatory, AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange.getName(); + } + + public void setExchange(AMQShortString exchange) + { + //no-op + } + + public boolean isImmediate() + { + return _immediate; + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java new file mode 100644 index 0000000000..fd6789f5ce --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/SkeletonMessageStore.java @@ -0,0 +1,156 @@ +/* + * + * 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; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.Exchange; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A message store that does nothing. Designed to be used in tests that do not want to use any message store + * functionality. + */ +public class SkeletonMessageStore implements MessageStore +{ + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure(String base, Configuration config) throws Exception + { + } + + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void close() throws Exception + { + } + + public void removeMessage(StoreContext s, Long messageId) + { + } + + public void createExchange(Exchange exchange) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeExchange(Exchange exchange) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void createQueue(AMQQueue queue) throws AMQException + { + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException + { + } + + public void beginTran(StoreContext s) throws AMQException + { + } + + public boolean inTran(StoreContext sc) + { + return false; + } + + public void commitTran(StoreContext storeContext) throws AMQException + { + } + + public void abortTran(StoreContext storeContext) throws AMQException + { + } + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public Long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public void storeContentBodyChunk(StoreContext sc, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException + { + + } + + public void storeMessageMetaData(StoreContext sc, Long messageId, MessageMetaData messageMetaData) throws AMQException + { + + } + + public MessageMetaData getMessageMetaData(StoreContext s,Long messageId) throws AMQException + { + return null; + } + + public ContentChunk getContentBodyChunk(StoreContext s,Long messageId, int index) throws AMQException + { + return null; + } + + public boolean isPersistent() + { + return false; + } + + public void removeQueue(final AMQQueue queue) throws AMQException + { + + } + + public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + + } + + public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java new file mode 100644 index 0000000000..4e48435962 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java @@ -0,0 +1,51 @@ +/* + * + * 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; + +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.abstraction.ContentChunk; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.List; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestMemoryMessageStore extends MemoryMessageStore +{ + public TestMemoryMessageStore() + { + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>(); + } + + public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap() + { + return _metaDataMap; + } + + public ConcurrentMap<Long, List<ContentChunk>> getContentBodyMap() + { + return _contentBodyMap; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java new file mode 100644 index 0000000000..2346660d25 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestReferenceCounting.java @@ -0,0 +1,169 @@ +/* + * + * 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; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.AMQMessageHandle; + +/** + * Tests that reference counting works correctly with AMQMessage and the message store + */ +public class TestReferenceCounting extends TestCase +{ + private TestMemoryMessageStore _store; + + private StoreContext _storeContext = new StoreContext(); + + + protected void setUp() throws Exception + { + super.setUp(); + _store = new TestMemoryMessageStore(); + } + + /** + * Check that when the reference count is decremented the message removes itself from the store + */ + public void testMessageGetsRemoved() throws AMQException + { + ContentHeaderBody chb = createPersistentContentHeader(); + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + + final long messageId = _store.getNewMessageId(); + AMQMessageHandle messageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, _store, true); + messageHandle.setPublishAndContentHeaderBody(_storeContext,info, chb); + AMQMessage message = new AMQMessage(messageHandle, + _storeContext,info); + + message = message.takeReference(); + + // we call routing complete to set up the handle + // message.routingComplete(_store, _storeContext, new MessageHandleFactory()); + + + assertEquals(1, _store.getMessageMetaDataMap().size()); + message.decrementReference(_storeContext); + assertEquals(1, _store.getMessageMetaDataMap().size()); + } + + private ContentHeaderBody createPersistentContentHeader() + { + ContentHeaderBody chb = new ContentHeaderBody(); + BasicContentHeaderProperties bchp = new BasicContentHeaderProperties(); + bchp.setDeliveryMode((byte)2); + chb.properties = bchp; + return chb; + } + + public void testMessageRemains() throws AMQException + { + + MessagePublishInfo info = new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return null; + } + + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return null; + } + }; + + final Long messageId = _store.getNewMessageId(); + final ContentHeaderBody chb = createPersistentContentHeader(); + AMQMessageHandle messageHandle = (new MessageHandleFactory()).createMessageHandle(messageId, _store, true); + messageHandle.setPublishAndContentHeaderBody(_storeContext,info,chb); + AMQMessage message = new AMQMessage(messageHandle, + _storeContext, + info); + + + message = message.takeReference(); + // we call routing complete to set up the handle + // message.routingComplete(_store, _storeContext, new MessageHandleFactory()); + + + + assertEquals(1, _store.getMessageMetaDataMap().size()); + message = message.takeReference(); + message.decrementReference(_storeContext); + assertEquals(1, _store.getMessageMetaDataMap().size()); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(TestReferenceCounting.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java new file mode 100644 index 0000000000..9146fe88ae --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java @@ -0,0 +1,92 @@ +/* + * + * 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; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MessageMetaData; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.abstraction.ContentChunk; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.HashMap; +import java.util.List; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestableMemoryMessageStore extends MemoryMessageStore +{ + + MemoryMessageStore _mms = null; + private HashMap<Long, AMQQueue> _messages = new HashMap<Long, AMQQueue>(); + + public TestableMemoryMessageStore(MemoryMessageStore mms) + { + _mms = mms; + } + + public TestableMemoryMessageStore() + { + _metaDataMap = new ConcurrentHashMap<Long, MessageMetaData>(); + _contentBodyMap = new ConcurrentHashMap<Long, List<ContentChunk>>(); + } + + public ConcurrentMap<Long, MessageMetaData> getMessageMetaDataMap() + { + if (_mms != null) + { + return _mms._metaDataMap; + } + else + { + return _metaDataMap; + } + } + + public ConcurrentMap<Long, List<ContentChunk>> getContentBodyMap() + { + if (_mms != null) + { + return _mms._contentBodyMap; + } + else + { + return _contentBodyMap; + } + } + + public void enqueueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + getMessages().put(messageId, queue); + } + + public void dequeueMessage(StoreContext context, final AMQQueue queue, Long messageId) throws AMQException + { + getMessages().remove(messageId); + } + + public HashMap<Long, AMQQueue> getMessages() + { + return _messages; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java new file mode 100644 index 0000000000..33fd669d5c --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java @@ -0,0 +1,180 @@ +package org.apache.qpid.server.subscription; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +import java.util.ArrayList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntry.SubscriptionAcquiredState; + +public class MockSubscription implements Subscription +{ + + private boolean _closed = false; + private AMQShortString tag = new AMQShortString("mocktag"); + private AMQQueue queue = null; + private StateListener _listener = null; + private QueueEntry lastSeen = null; + private State _state = State.ACTIVE; + private ArrayList<QueueEntry> messages = new ArrayList<QueueEntry>(); + private final Lock _stateChangeLock = new ReentrantLock(); + + public void close() + { + _closed = true; + if (_listener != null) + { + _listener.stateChange(this, _state, State.CLOSED); + } + _state = State.CLOSED; + } + + public boolean filtersMessages() + { + return false; + } + + public AMQChannel getChannel() + { + return null; + } + + public AMQShortString getConsumerTag() + { + return tag ; + } + + public QueueEntry getLastSeenEntry() + { + return lastSeen; + } + + public SubscriptionAcquiredState getOwningState() + { + return new QueueEntry.SubscriptionAcquiredState(this); + } + + public AMQQueue getQueue() + { + return queue; + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public boolean hasInterest(QueueEntry msg) + { + return true; + } + + public boolean isActive() + { + return true; + } + + public boolean isAutoClose() + { + return false; + } + + public boolean isBrowser() + { + return false; + } + + public boolean isClosed() + { + return _closed; + } + + public boolean isSuspended() + { + return false; + } + + public void queueDeleted(AMQQueue queue) + { + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void resend(QueueEntry entry) throws AMQException + { + } + + public void restoreCredit(QueueEntry queueEntry) + { + } + + public void send(QueueEntry msg) throws AMQException + { + lastSeen = msg; + messages.add(msg); + } + + public boolean setLastSeenEntry(QueueEntry expected, QueueEntry newValue) + { + boolean result = false; + if (expected != null) + { + result = (expected.equals(lastSeen)); + } + lastSeen = newValue; + return result; + } + + public void setQueue(AMQQueue queue) + { + this.queue = queue; + } + + public void setStateListener(StateListener listener) + { + this._listener = listener; + } + + public State getState() + { + return _state; + } + + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + public ArrayList<QueueEntry> getMessages() + { + return messages; + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.java new file mode 100644 index 0000000000..d0db4ebd38 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/QueueBrowserUsesNoAckTest.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.subscription; + +import org.apache.qpid.server.util.InternalBrokerBaseCase; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.AMQException; + +import java.util.List; + +public class QueueBrowserUsesNoAckTest extends InternalBrokerBaseCase +{ + + public void testQueueBrowserUsesNoAck() throws AMQException + { + int sendMessageCount = 2; + int prefetch = 1; + + //Check store is empty + checkStoreContents(0); + + //Send required messsages to the queue + publishMessages(_session, _channel, sendMessageCount); + + //Ensure they are stored + checkStoreContents(sendMessageCount); + + //Check that there are no unacked messages + assertEquals("Channel should have no unacked msgs ", 0, + _channel.getUnacknowledgedMessageMap().size()); + + //Set the prefetch on the session to be less than the sent messages + _channel.setCredit(0, prefetch); + + //browse the queue + AMQShortString browser = browse(_channel, _queue); + + _queue.deliverAsync(); + + //Wait for messages to fill the prefetch + _session.awaitDelivery(prefetch); + + //Get those messages + List<InternalTestProtocolSession.DeliveryPair> messages = + _session.getDelivers(_channel.getChannelId(), browser, + prefetch); + + //Ensure we recevied the prefetched messages + assertEquals(prefetch, messages.size()); + + //Check the process didn't suspend the subscription as this would + // indicate we are using the prefetch credit. i.e. using acks not No-Ack + assertTrue("The subscription has been suspended", + !_channel.getSubscription(browser).getState() + .equals(Subscription.State.SUSPENDED)); + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java new file mode 100644 index 0000000000..84d3d313d1 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/txn/TxnBufferTest.java @@ -0,0 +1,306 @@ +/* + * + * 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.txn; + +import junit.framework.TestCase; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.store.StoreContext; + +import java.util.LinkedList; +import java.util.NoSuchElementException; + +public class TxnBufferTest extends TestCase +{ + private final LinkedList<MockOp> ops = new LinkedList<MockOp>(); + + public void testCommit() throws AMQException + { + MockStore store = new MockStore(); + + TxnBuffer buffer = new TxnBuffer(); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + //check relative ordering + MockOp op = new MockOp().expectPrepare().expectPrepare().expectCommit().expectCommit(); + buffer.enlist(op); + buffer.enlist(op); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + + buffer.commit(null); + + validateOps(); + store.validate(); + } + + public void testRollback() throws AMQException + { + MockStore store = new MockStore(); + + TxnBuffer buffer = new TxnBuffer(); + buffer.enlist(new MockOp().expectRollback()); + buffer.enlist(new MockOp().expectRollback()); + buffer.enlist(new MockOp().expectRollback()); + + buffer.rollback(null); + + validateOps(); + store.validate(); + } + + public void testCommitWithFailureDuringPrepare() throws AMQException + { + MockStore store = new MockStore(); + store.beginTran(null); + + TxnBuffer buffer = new TxnBuffer(); + buffer.enlist(new StoreMessageOperation(store)); + buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare()); + buffer.enlist(new TxnTester(store)); + buffer.enlist(new MockOp().expectPrepare().expectUndoPrepare()); + buffer.enlist(new FailedPrepare()); + buffer.enlist(new MockOp()); + + try + { + buffer.commit(null); + } + catch (NoSuchElementException e) + { + + } + + validateOps(); + store.validate(); + } + + public void testCommitWithPersistance() throws AMQException + { + MockStore store = new MockStore(); + store.beginTran(null); + store.expectCommit(); + + TxnBuffer buffer = new TxnBuffer(); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new MockOp().expectPrepare().expectCommit()); + buffer.enlist(new StoreMessageOperation(store)); + buffer.enlist(new TxnTester(store)); + + buffer.commit(null); + validateOps(); + store.validate(); + } + + private void validateOps() + { + for (MockOp op : ops) + { + op.validate(); + } + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(TxnBufferTest.class); + } + + class MockOp implements TxnOp + { + final Object PREPARE = "PREPARE"; + final Object COMMIT = "COMMIT"; + final Object UNDO_PREPARE = "UNDO_PREPARE"; + final Object ROLLBACK = "ROLLBACK"; + + private final LinkedList expected = new LinkedList(); + + MockOp() + { + ops.add(this); + } + + public void prepare(StoreContext context) + { + assertEquals(expected.removeLast(), PREPARE); + } + + public void commit(StoreContext context) + { + assertEquals(expected.removeLast(), COMMIT); + } + + public void undoPrepare() + { + assertEquals(expected.removeLast(), UNDO_PREPARE); + } + + public void rollback(StoreContext context) + { + assertEquals(expected.removeLast(), ROLLBACK); + } + + private MockOp expect(Object optype) + { + expected.addFirst(optype); + return this; + } + + MockOp expectPrepare() + { + return expect(PREPARE); + } + + MockOp expectCommit() + { + return expect(COMMIT); + } + + MockOp expectUndoPrepare() + { + return expect(UNDO_PREPARE); + } + + MockOp expectRollback() + { + return expect(ROLLBACK); + } + + void validate() + { + assertEquals("Expected ops were not all invoked", new LinkedList(), expected); + } + + void clear() + { + expected.clear(); + } + } + + class MockStore extends TestMemoryMessageStore + { + final Object BEGIN = "BEGIN"; + final Object ABORT = "ABORT"; + final Object COMMIT = "COMMIT"; + + private final LinkedList expected = new LinkedList(); + private boolean inTran; + + public void beginTran(StoreContext context) throws AMQException + { + inTran = true; + } + + public void commitTran(StoreContext context) throws AMQException + { + assertEquals(expected.removeLast(), COMMIT); + inTran = false; + } + + public void abortTran(StoreContext context) throws AMQException + { + assertEquals(expected.removeLast(), ABORT); + inTran = false; + } + + public boolean inTran(StoreContext context) + { + return inTran; + } + + private MockStore expect(Object optype) + { + expected.addFirst(optype); + return this; + } + + MockStore expectBegin() + { + return expect(BEGIN); + } + + MockStore expectCommit() + { + return expect(COMMIT); + } + + MockStore expectAbort() + { + return expect(ABORT); + } + + void clear() + { + expected.clear(); + } + + void validate() + { + assertEquals("Expected ops were not all invoked", new LinkedList(), expected); + } + } + + class NullOp implements TxnOp + { + public void prepare(StoreContext context) throws AMQException + { + } + public void commit(StoreContext context) + { + } + public void undoPrepare() + { + } + public void rollback(StoreContext context) + { + } + } + + class FailedPrepare extends NullOp + { + public void prepare() throws AMQException + { + throw new AMQException(null, "Fail!", null); + } + } + + class TxnTester extends NullOp + { + private final MessageStore store; + + private final StoreContext context = new StoreContext(); + + TxnTester(MessageStore store) + { + this.store = store; + } + + public void prepare() throws AMQException + { + assertTrue("Expected prepare to be performed under txn", store.inTran(context)); + } + + public void commit() + { + assertTrue("Expected commit not to be performed under txn", !store.inTran(context)); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java new file mode 100644 index 0000000000..101c33043d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/InternalBrokerBaseCase.java @@ -0,0 +1,214 @@ +/* + * + * 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.util; + +import junit.framework.TestCase; + +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.InternalTestProtocolSession; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ConsumerTagNotUniqueException; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreContext; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.AMQException; +import org.apache.qpid.util.MockChannel; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.exchange.ExchangeDefaults; + +public class InternalBrokerBaseCase extends TestCase +{ + protected IApplicationRegistry _registry; + protected MessageStore _messageStore; + protected MockChannel _channel; + protected InternalTestProtocolSession _session; + protected VirtualHost _virtualHost; + protected StoreContext _storeContext = new StoreContext(); + protected AMQQueue _queue; + protected AMQShortString QUEUE_NAME; + + public void setUp() throws Exception + { + super.setUp(); + PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.setProperty("virtualhosts.virtualhost.test.store.class", TestableMemoryMessageStore.class.getName()); + _registry = new TestApplicationRegistry(new ServerConfiguration(configuration)); + ApplicationRegistry.initialise(_registry); + _virtualHost = _registry.getVirtualHostRegistry().getVirtualHost("test"); + + _messageStore = _virtualHost.getMessageStore(); + + QUEUE_NAME = new AMQShortString("test"); + _queue = AMQQueueFactory.createAMQQueueImpl(QUEUE_NAME, false, new AMQShortString("testowner"), + false, _virtualHost, null); + + _virtualHost.getQueueRegistry().registerQueue(_queue); + + Exchange defaultExchange = _virtualHost.getExchangeRegistry().getDefaultExchange(); + + _queue.bind(defaultExchange, QUEUE_NAME, null); + + _session = new InternalTestProtocolSession(); + + _session.setVirtualHost(_virtualHost); + + _channel = new MockChannel(_session, 1, _messageStore); + + _session.addChannel(_channel); + } + + public void tearDown() throws Exception + { + ApplicationRegistry.remove(1); + super.tearDown(); + } + + protected void checkStoreContents(int messageCount) + { + assertEquals("Message header count incorrect in the MetaDataMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getMessageMetaDataMap().size()); + + //The above publish message is sufficiently small not to fit in the header so no Body is required. + //assertEquals("Message body count incorrect in the ContentBodyMap", messageCount, ((TestableMemoryMessageStore) _messageStore).getContentBodyMap().size()); + } + + protected AMQShortString subscribe(InternalTestProtocolSession session, AMQChannel channel, AMQQueue queue) + { + try + { + return channel.subscribeToQueue(null, queue, true, null, false, true); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + catch (ConsumerTagNotUniqueException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + //Keep the compiler happy + return null; + } + + protected AMQShortString browse(AMQChannel channel, AMQQueue queue) + { + try + { + FieldTable filters = new FieldTable(); + filters.put(AMQPFilterTypes.NO_CONSUME.getValue(), true); + + return channel.subscribeToQueue(null, queue, true, filters, false, true); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + catch (ConsumerTagNotUniqueException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + //Keep the compiler happy + return null; + } + + public void publishMessages(InternalTestProtocolSession session, AMQChannel channel, int messages) throws AMQException + { + MessagePublishInfo info = new MessagePublishInfo() + { + public AMQShortString getExchange() + { + return ExchangeDefaults.DEFAULT_EXCHANGE_NAME; + } + + public void setExchange(AMQShortString exchange) + { + + } + + public boolean isImmediate() + { + return false; + } + + public boolean isMandatory() + { + return false; + } + + public AMQShortString getRoutingKey() + { + return QUEUE_NAME; + } + }; + + for (int count = 0; count < messages; count++) + { + channel.setPublishFrame(info, _virtualHost.getExchangeRegistry().getExchange(info.getExchange())); + + //Set the body size + ContentHeaderBody _headerBody = new ContentHeaderBody(); + _headerBody.bodySize = 0; + + //Set Minimum properties + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + + properties.setExpiration(0L); + properties.setTimestamp(System.currentTimeMillis()); + + //Make Message Persistent + properties.setDeliveryMode((byte) 2); + + _headerBody.properties = properties; + + channel.publishContentHeader(_headerBody); + } + + } + + public void acknowledge(AMQChannel channel, long deliveryTag) + { + try + { + channel.acknowledgeMessage(deliveryTag, false); + } + catch (AMQException e) + { + e.printStackTrace(); + fail(e.getMessage()); + } + } + +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.java new file mode 100644 index 0000000000..c7db51016e --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/LoggingProxyTest.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.util; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import junit.framework.TestCase; + +public class LoggingProxyTest extends TestCase +{ + static interface IFoo { + void foo(); + void foo(int i, Collection c); + String bar(); + String bar(String s, List l); + } + + static class Foo implements IFoo { + public void foo() + { + } + + public void foo(int i, Collection c) + { + } + + public String bar() + { + return null; + } + + public String bar(String s, List l) + { + return "ha"; + } + } + + public void testSimple() { + LoggingProxy proxy = new LoggingProxy(new Foo(), 20); + IFoo foo = (IFoo)proxy.getProxy(IFoo.class); + foo.foo(); + assertEquals(2, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$")); + assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$")); + + foo.foo(3, Arrays.asList(0, 1, 2)); + assertEquals(4, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$")); + assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$")); + + foo.bar(); + assertEquals(6, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$")); + assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$")); + + foo.bar("hello", Arrays.asList(1, 2, 3)); + assertEquals(8, proxy.getBufferSize()); + assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$")); + assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$")); + + proxy.dump(); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(LoggingProxyTest.class); + } +} diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java new file mode 100644 index 0000000000..c6ecac6a01 --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -0,0 +1,137 @@ +/* + * + * 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.util; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.access.ACLManager; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.plugins.AllowAll; +import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Properties; +import java.util.Arrays; + +public class TestApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private MessageStore _messageStore; + + private VirtualHost _vHost; + + + private ServerConfiguration _config; + + public TestApplicationRegistry() throws ConfigurationException + { + super(new ServerConfiguration(new PropertiesConfiguration())); + } + + public TestApplicationRegistry(ServerConfiguration config) throws ConfigurationException + { + super(config); + _config = config; + } + + public void initialise() throws Exception + { + Properties users = new Properties(); + + users.put("guest", "guest"); + + _databaseManager = new PropertiesPrincipalDatabaseManager("default", users); + + _accessManager = new ACLManager(_configuration.getSecurityConfiguration(), _pluginManager, AllowAll.FACTORY); + + _authenticationManager = new PrincipalDatabaseAuthenticationManager(null, null); + + _managedObjectRegistry = new NoopManagedObjectRegistry(); + + _messageStore = new TestableMemoryMessageStore(); + + _virtualHostRegistry = new VirtualHostRegistry(); + + PropertiesConfiguration vhostProps = new PropertiesConfiguration(); + VirtualHostConfiguration hostConfig = new VirtualHostConfiguration("test", vhostProps); + _vHost = new VirtualHost(hostConfig, _messageStore); + + _virtualHostRegistry.registerVirtualHost(_vHost); + + _queueRegistry = _vHost.getQueueRegistry(); + _exchangeFactory = _vHost.getExchangeFactory(); + _exchangeRegistry = _vHost.getExchangeRegistry(); + + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public Collection<String> getVirtualHostNames() + { + String[] hosts = {"test"}; + return Arrays.asList(hosts); + } + + public void setAccessManager(ACLManager newManager) + { + _accessManager = newManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + +} + + diff --git a/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.java new file mode 100644 index 0000000000..447d09429d --- /dev/null +++ b/qpid/java/broker/src/test/java/org/apache/qpid/util/MockChannel.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.util; + +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +public class MockChannel extends AMQChannel +{ + public MockChannel(AMQProtocolSession session, int channelId, MessageStore messageStore) + throws AMQException + { + super(session, channelId, messageStore); + } + + public Subscription getSubscription(AMQShortString subscription) + { + return _tag2SubscriptionMap.get(subscription); + } + +} diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java index 662f04b3c9..7d8c81f4d5 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java @@ -31,62 +31,54 @@ import org.apache.qpid.server.configuration.VirtualHostConfiguration; import org.apache.qpid.server.exchange.Exchange; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.queue.MessageMetaData; -import org.apache.qpid.server.transactionlog.TransactionLog; -import org.apache.qpid.server.routing.RoutingTable; import java.util.HashMap; import java.util.Iterator; -import java.util.ArrayList; -public class SlowMessageStore implements TransactionLog, RoutingTable +public class SlowMessageStore implements MessageStore { private static final Logger _logger = Logger.getLogger(SlowMessageStore.class); private static final String DELAYS = "delays"; private HashMap<String, Long> _preDelays = new HashMap<String, Long>(); private HashMap<String, Long> _postDelays = new HashMap<String, Long>(); private long _defaultDelay = 0L; - private TransactionLog _realTransactionLog = new MemoryMessageStore(); - private RoutingTable _realRoutingTable = (RoutingTable)_realTransactionLog; + private MessageStore _realStore = new MemoryMessageStore(); private static final String PRE = "pre"; private static final String POST = "post"; private String DEFAULT_DELAY = "default"; - public Object configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception { - _logger.warn("Starting SlowMessageStore on Virtualhost:" + virtualHost.getName()); + _logger.info("Starting SlowMessageStore on Virtualhost:" + virtualHost.getName()); Configuration delays = config.getStoreConfiguration().subset(DELAYS); configureDelays(delays); - String transactionLogClass = config.getTransactionLogClass(); + String messageStoreClass = config.getStoreConfiguration().getString("realStore"); if (delays.containsKey(DEFAULT_DELAY)) { _defaultDelay = delays.getLong(DEFAULT_DELAY); - _logger.warn("Delay is:" + _defaultDelay); } - if (transactionLogClass != null) + if (messageStoreClass != null) { - Class clazz = Class.forName(transactionLogClass); - if (clazz != this.getClass()) - { + Class clazz = Class.forName(messageStoreClass); - Object o = clazz.newInstance(); + Object o = clazz.newInstance(); - if (!(o instanceof TransactionLog)) - { - throw new ClassCastException("TransactionLog class must implement " + TransactionLog.class + ". Class " + clazz + - " does not."); - } - _realTransactionLog = (TransactionLog) o; + if (!(o instanceof MessageStore)) + { + throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz + + " does not."); } + _realStore = (MessageStore) o; + _realStore.configure(virtualHost, base + ".store", config); + } + else + { + _realStore.configure(virtualHost, base + ".store", config); } - - // The call to configure may return a new transaction log - _realTransactionLog = (TransactionLog) _realTransactionLog.configure(virtualHost, base , config); - - return this; } private void configureDelays(Configuration config) @@ -159,35 +151,42 @@ public class SlowMessageStore implements TransactionLog, RoutingTable public void close() throws Exception { doPreDelay("close"); - _realTransactionLog.close(); + _realStore.close(); doPostDelay("close"); } + public void removeMessage(StoreContext storeContext, Long messageId) throws AMQException + { + doPreDelay("removeMessage"); + _realStore.removeMessage(storeContext, messageId); + doPostDelay("removeMessage"); + } + public void createExchange(Exchange exchange) throws AMQException { doPreDelay("createExchange"); - _realRoutingTable.createExchange(exchange); + _realStore.createExchange(exchange); doPostDelay("createExchange"); } public void removeExchange(Exchange exchange) throws AMQException { doPreDelay("removeExchange"); - _realRoutingTable.removeExchange(exchange); + _realStore.removeExchange(exchange); doPostDelay("removeExchange"); } public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException { doPreDelay("bindQueue"); - _realRoutingTable.bindQueue(exchange, routingKey, queue, args); + _realStore.bindQueue(exchange, routingKey, queue, args); doPostDelay("bindQueue"); } public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQException { doPreDelay("unbindQueue"); - _realRoutingTable.unbindQueue(exchange, routingKey, queue, args); + _realStore.unbindQueue(exchange, routingKey, queue, args); doPostDelay("unbindQueue"); } @@ -199,100 +198,101 @@ public class SlowMessageStore implements TransactionLog, RoutingTable public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQException { doPreDelay("createQueue"); - _realRoutingTable.createQueue(queue, arguments); + _realStore.createQueue(queue, arguments); doPostDelay("createQueue"); } public void removeQueue(AMQQueue queue) throws AMQException { doPreDelay("removeQueue"); - _realRoutingTable.removeQueue(queue); + _realStore.removeQueue(queue); doPostDelay("removeQueue"); } - public void enqueueMessage(StoreContext context, ArrayList<AMQQueue> queues, Long messageId) throws AMQException + public void enqueueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException { doPreDelay("enqueueMessage"); - _realTransactionLog.enqueueMessage(context, queues, messageId); + _realStore.enqueueMessage(context, queue, messageId); doPostDelay("enqueueMessage"); } public void dequeueMessage(StoreContext context, AMQQueue queue, Long messageId) throws AMQException { doPreDelay("dequeueMessage"); - _realTransactionLog.dequeueMessage(context, queue, messageId); - doPostDelay("dequeueMessage"); - } - - public void removeMessage(StoreContext context, Long messageId) throws AMQException - { - doPreDelay("dequeueMessage"); - _realTransactionLog.removeMessage(context, messageId); + _realStore.dequeueMessage(context, queue, messageId); doPostDelay("dequeueMessage"); } public void beginTran(StoreContext context) throws AMQException { doPreDelay("beginTran"); - _realTransactionLog.beginTran(context); + _realStore.beginTran(context); doPostDelay("beginTran"); } public void commitTran(StoreContext context) throws AMQException { doPreDelay("commitTran"); - _realTransactionLog.commitTran(context); + _realStore.commitTran(context); doPostDelay("commitTran"); } public void abortTran(StoreContext context) throws AMQException { doPreDelay("abortTran"); - _realTransactionLog.abortTran(context); + _realStore.abortTran(context); doPostDelay("abortTran"); } public boolean inTran(StoreContext context) { doPreDelay("inTran"); - boolean b = _realTransactionLog.inTran(context); + boolean b = _realStore.inTran(context); doPostDelay("inTran"); return b; } + public Long getNewMessageId() + { + doPreDelay("getNewMessageId"); + Long l = _realStore.getNewMessageId(); + doPostDelay("getNewMessageId"); + return l; + } + public void storeContentBodyChunk(StoreContext context, Long messageId, int index, ContentChunk contentBody, boolean lastContentBody) throws AMQException { doPreDelay("storeContentBodyChunk"); - _realTransactionLog.storeContentBodyChunk(context, messageId, index, contentBody, lastContentBody); + _realStore.storeContentBodyChunk(context, messageId, index, contentBody, lastContentBody); doPostDelay("storeContentBodyChunk"); } public void storeMessageMetaData(StoreContext context, Long messageId, MessageMetaData messageMetaData) throws AMQException { doPreDelay("storeMessageMetaData"); - _realTransactionLog.storeMessageMetaData(context, messageId, messageMetaData); + _realStore.storeMessageMetaData(context, messageId, messageMetaData); doPostDelay("storeMessageMetaData"); } -// public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException -// { -// doPreDelay("getMessageMetaData"); -// MessageMetaData mmd = _realTransactionLog.getMessageMetaData(context, messageId); -// doPostDelay("getMessageMetaData"); -// return mmd; -// } -// -// public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException -// { -// doPreDelay("getContentBodyChunk"); -// ContentChunk c = _realTransactionLog.getContentBodyChunk(context, messageId, index); -// doPostDelay("getContentBodyChunk"); -// return c; -// } -// + public MessageMetaData getMessageMetaData(StoreContext context, Long messageId) throws AMQException + { + doPreDelay("getMessageMetaData"); + MessageMetaData mmd = _realStore.getMessageMetaData(context, messageId); + doPostDelay("getMessageMetaData"); + return mmd; + } + + public ContentChunk getContentBodyChunk(StoreContext context, Long messageId, int index) throws AMQException + { + doPreDelay("getContentBodyChunk"); + ContentChunk c = _realStore.getContentBodyChunk(context, messageId, index); + doPostDelay("getContentBodyChunk"); + return c; + } + public boolean isPersistent() { - return _realTransactionLog.isPersistent(); + return _realStore.isPersistent(); } } |