diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..42bdaef55
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+/application.yml
+/application.properties
+asciidoctor.css
+*~
+.#*
+*#
+target/
+build/
+bin/
+_site/
+.classpath
+.project
+.settings
+.springBeans
+.DS_Store
+*.sw*
+*.iml
+*.ipr
+*.iws
+.idea/*
+.factorypath
+dump.rdb
+.apt_generated
+artifacts
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000..0e7dabeff
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1 @@
+-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
\ No newline at end of file
diff --git a/.mvn/maven.config b/.mvn/maven.config
new file mode 100644
index 000000000..3b8cf46e1
--- /dev/null
+++ b/.mvn/maven.config
@@ -0,0 +1 @@
+-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..5fd4d5023
Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..eb9194764
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip
\ No newline at end of file
diff --git a/.settings.xml b/.settings.xml
new file mode 100644
index 000000000..8b234ce74
--- /dev/null
+++ b/.settings.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ repo.spring.io
+ ${env.CI_DEPLOY_USERNAME}
+ ${env.CI_DEPLOY_PASSWORD}
+
+
+
+
+
+ spring
+ true
+
+
+ spring-snapshots
+ Spring Snapshots
+ http://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ http://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ http://repo.spring.io/release
+
+ false
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ http://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+
+ spring-milestones
+ Spring Milestones
+ http://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 000000000..815604b5d
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,3 @@
+# spring-cloud-stream-binder-rabbit
+
+Spring Cloud Stream Binder implementation for Rabbit
diff --git a/mvnw b/mvnw
new file mode 100755
index 000000000..e3511489f
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,234 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ #
+ # Look for the Apple JDKs first to preserve the existing behaviour, and then look
+ # for the new JDKs provided by Oracle.
+ #
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
+ #
+ # Oracle JDKs
+ #
+ export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=`/usr/libexec/java_home`
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ local basedir=$(pwd)
+ local wdir=$(pwd)
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ wdir=$(cd "$wdir/.."; pwd)
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} "$@"
+
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 000000000..e3511489f
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,234 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ #
+ # Look for the Apple JDKs first to preserve the existing behaviour, and then look
+ # for the new JDKs provided by Oracle.
+ #
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
+ #
+ # Oracle JDKs
+ #
+ export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=`/usr/libexec/java_home`
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ local basedir=$(pwd)
+ local wdir=$(pwd)
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ wdir=$(cd "$wdir/.."; pwd)
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} "$@"
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 000000000..02c1a740d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+ spring-cloud-stream-binder-rabbit-parent
+ 1.1.0.BUILD-SNAPSHOT
+ pom
+
+ org.springframework.cloud
+ spring-cloud-build
+ 1.1.1.RELEASE
+
+
+
+ 1.4.0.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream-dependencies
+ 1.1.0.BUILD-SNAPSHOT
+ pom
+ import
+
+
+
+
+ spring-cloud-stream-binder-rabbit
+ spring-cloud-starter-stream-rabbit
+
+
+
+ spring
+
+
+ spring-snapshots
+ Spring Snapshots
+ http://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ http://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ http://repo.spring.io/release
+
+ false
+
+
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ http://repo.spring.io/libs-snapshot-local
+
+ true
+
+
+ false
+
+
+
+ spring-milestones
+ Spring Milestones
+ http://repo.spring.io/libs-milestone-local
+
+ false
+
+
+
+ spring-releases
+ Spring Releases
+ http://repo.spring.io/libs-release-local
+
+ false
+
+
+
+
+
+
diff --git a/spring-cloud-starter-stream-rabbit/pom.xml b/spring-cloud-starter-stream-rabbit/pom.xml
new file mode 100644
index 000000000..44bcef2f7
--- /dev/null
+++ b/spring-cloud-starter-stream-rabbit/pom.xml
@@ -0,0 +1,25 @@
+
+
+ 4.0.0
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-rabbit-parent
+ 1.1.0.BUILD-SNAPSHOT
+
+ spring-cloud-starter-stream-rabbit
+ Spring Cloud Starter Stream Rabbit
+ http://projects.spring.io/spring-cloud
+
+ Pivotal Software, Inc.
+ http://www.spring.io
+
+
+ ${basedir}/../..
+
+
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-rabbit
+
+
+
diff --git a/spring-cloud-starter-stream-rabbit/src/main/resources/META-INF/spring.provides b/spring-cloud-starter-stream-rabbit/src/main/resources/META-INF/spring.provides
new file mode 100644
index 000000000..17bce3be1
--- /dev/null
+++ b/spring-cloud-starter-stream-rabbit/src/main/resources/META-INF/spring.provides
@@ -0,0 +1 @@
+provides: spring-cloud-starter-stream-rabbit
\ No newline at end of file
diff --git a/spring-cloud-stream-binder-rabbit/pom.xml b/spring-cloud-stream-binder-rabbit/pom.xml
new file mode 100644
index 000000000..9e4d6d404
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+
+ spring-cloud-stream-binder-rabbit
+ jar
+ spring-cloud-stream-binder-rabbit
+ RabbitMQ binder implementation
+
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-rabbit-parent
+ 1.1.0.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.cloud
+ spring-cloud-stream
+
+
+ org.springframework.cloud
+ spring-cloud-stream-codec
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+ true
+
+
+ org.springframework.cloud
+ spring-cloud-core
+ true
+
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-test
+ test
+
+
+ org.springframework.cloud
+ spring-cloud-stream-test-support-internal
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+
+ org.springframework.integration
+ spring-integration-amqp
+
+
+
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitAdminException.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitAdminException.java
new file mode 100644
index 000000000..191b2e2e1
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitAdminException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+
+/**
+ * Exceptions thrown while interfacing with the RabbitMQ admin plugin.
+ * @author Gary Russell
+ * @since 1.2
+ */
+@SuppressWarnings("serial")
+public class RabbitAdminException extends RuntimeException {
+
+ public RabbitAdminException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RabbitAdminException(String message) {
+ super(message);
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingCleaner.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingCleaner.java
new file mode 100644
index 000000000..250439595
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingCleaner.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.cloud.stream.binder.AbstractBinder;
+import org.springframework.cloud.stream.binder.BindingCleaner;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+
+/**
+ * Implementation of {@link org.springframework.cloud.stream.binder.BindingCleaner} for the {@code RabbitBinder}.
+ * @author Gary Russell
+ * @author David Turanski
+ * @since 1.2
+ */
+public class RabbitBindingCleaner implements BindingCleaner {
+
+ private static final Log logger = LogFactory.getLog(RabbitBindingCleaner.class);
+
+ private static final String PREFIX_DELIMITER = ".";
+
+ public static final String BINDER_PREFIX = "binder" + PREFIX_DELIMITER;
+
+ @Override
+ public Map> clean(String entity, boolean isJob) {
+ return clean("http://localhost:15672", "guest", "guest", "/", BINDER_PREFIX, entity, isJob);
+ }
+
+ public Map> clean(String adminUri, String user, String pw, String vhost,
+ String binderPrefix, String entity, boolean isJob) {
+ return doClean(
+ adminUri == null ? "http://localhost:15672" : adminUri,
+ user == null ? "guest" : user,
+ pw == null ? "guest" : pw,
+ vhost == null ? "/" : vhost,
+ binderPrefix == null ? BINDER_PREFIX : binderPrefix,
+ entity, isJob);
+ }
+
+ private Map> doClean(String adminUri, String user, String pw, String vhost,
+ String binderPrefix, String entity, boolean isJob) {
+ RestTemplate restTemplate = RabbitManagementUtils.buildRestTemplate(adminUri, user, pw);
+ List removedQueues = isJob
+ ? null
+ : findStreamQueues(adminUri, vhost, binderPrefix, entity, restTemplate);
+ List removedExchanges = findExchanges(adminUri, vhost, binderPrefix, entity, restTemplate);
+ // Delete the queues in reverse order to enable re-running after a partial success.
+ // The queue search above starts with 0 and terminates on a not found.
+ for (int i = removedQueues.size() - 1; i >= 0; i--) {
+ String queueName = removedQueues.get(i);
+ URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("queues", "{vhost}", "{stream}")
+ .buildAndExpand(vhost, queueName).encode().toUri();
+ restTemplate.delete(uri);
+ if (logger.isDebugEnabled()) {
+ logger.debug("deleted queue: " + queueName);
+ }
+ }
+ Map> results = new HashMap<>();
+ if (removedQueues.size() > 0) {
+ results.put("queues", removedQueues);
+ }
+ // Fanout exchanges for taps
+ for (String exchange : removedExchanges) {
+ URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("exchanges", "{vhost}", "{name}")
+ .buildAndExpand(vhost, exchange).encode().toUri();
+ restTemplate.delete(uri);
+ if (logger.isDebugEnabled()) {
+ logger.debug("deleted exchange: " + exchange);
+ }
+ }
+ if (removedExchanges.size() > 0) {
+ results.put("exchanges", removedExchanges);
+ }
+ return results;
+ }
+
+ private List findStreamQueues(String adminUri, String vhost, String binderPrefix, String stream,
+ RestTemplate restTemplate) {
+ String queueNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, stream));
+ List> queues = listAllQueues(adminUri, vhost, restTemplate);
+ List removedQueues = new ArrayList<>();
+ for (Map queue : queues) {
+ String queueName = (String) queue.get("name");
+ if (queueName.startsWith(queueNamePrefix)) {
+ checkNoConsumers(queueName, queue);
+ removedQueues.add(queueName);
+ }
+ }
+ return removedQueues;
+ }
+
+ private List> listAllQueues(String adminUri, String vhost, RestTemplate restTemplate) {
+ URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("queues", "{vhost}")
+ .buildAndExpand(vhost).encode().toUri();
+ @SuppressWarnings("unchecked")
+ List> queues = restTemplate.getForObject(uri, List.class);
+ return queues;
+ }
+
+ private String adjustPrefix(String prefix) {
+ if (prefix.endsWith("*")) {
+ return prefix.substring(0, prefix.length() - 1);
+ }
+ else {
+ return prefix + PREFIX_DELIMITER;
+ }
+ }
+
+ private void checkNoConsumers(String queueName, Map queue) {
+ if (!queue.get("consumers").equals(Integer.valueOf(0))) {
+ throw new RabbitAdminException("Queue " + queueName + " is in use");
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List findExchanges(String adminUri, String vhost, String binderPrefix, String entity,
+ RestTemplate restTemplate) {
+ List removedExchanges = new ArrayList<>();
+ URI uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("exchanges", "{vhost}")
+ .buildAndExpand(vhost).encode().toUri();
+ List> exchanges = restTemplate.getForObject(uri, List.class);
+ String exchangeNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, entity));
+ for (Map exchange : exchanges) {
+ String exchangeName = (String) exchange.get("name");
+ if (exchangeName.startsWith(exchangeNamePrefix)) {
+ uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("exchanges", "{vhost}", "{name}", "bindings", "source")
+ .buildAndExpand(vhost, exchangeName).encode().toUri();
+ List> bindings = restTemplate.getForObject(uri, List.class);
+ if (hasNoForeignBindings(bindings, exchangeNamePrefix)) {
+ uri = UriComponentsBuilder.fromUriString(adminUri + "/api")
+ .pathSegment("exchanges", "{vhost}", "{name}", "bindings", "destination")
+ .buildAndExpand(vhost, exchangeName).encode().toUri();
+ bindings = restTemplate.getForObject(uri, List.class);
+ if (bindings.size() == 0) {
+ removedExchanges.add((String) exchange.get("name"));
+ }
+ else {
+ throw new RabbitAdminException("Cannot delete exchange " + exchangeName
+ + "; it is a destination: " + bindings);
+ }
+ }
+ else {
+ throw new RabbitAdminException("Cannot delete exchange " + exchangeName + "; it has bindings: "
+ + bindings);
+ }
+ }
+ }
+ return removedExchanges;
+ }
+
+ private boolean hasNoForeignBindings(List> bindings, String exchangeNamePrefix) {
+ if (bindings.size() == 0) {
+ return true;
+ }
+ boolean noForeign = true;
+ for (Map binding : bindings) {
+ if (!("queue".equals(binding.get("destination_type")))
+ || !((String) binding.get("destination")).startsWith(exchangeNamePrefix)) {
+ noForeign = false;
+ break;
+ }
+ }
+ return noForeign;
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingProperties.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingProperties.java
new file mode 100644
index 000000000..5ed04136d
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitBindingProperties.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+/**
+ * @author Marius Bogoevici
+ */
+public class RabbitBindingProperties {
+
+ private RabbitConsumerProperties consumer = new RabbitConsumerProperties();
+
+ private RabbitProducerProperties producer = new RabbitProducerProperties();
+
+ public RabbitConsumerProperties getConsumer() {
+ return consumer;
+ }
+
+ public void setConsumer(RabbitConsumerProperties consumer) {
+ this.consumer = consumer;
+ }
+
+ public RabbitProducerProperties getProducer() {
+ return producer;
+ }
+
+ public void setProducer(RabbitProducerProperties producer) {
+ this.producer = producer;
+ }
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java
new file mode 100644
index 000000000..7e24b4a58
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitConsumerProperties.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import javax.validation.constraints.Min;
+
+import org.springframework.amqp.core.AcknowledgeMode;
+import org.springframework.util.Assert;
+
+/**
+ * @author Marius Bogoevici
+ */
+public class RabbitConsumerProperties {
+
+ private String prefix = "";
+
+ private boolean transacted;
+
+ private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
+
+ private int maxConcurrency = 1;
+
+ private int prefetch = 1;
+
+ private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
+
+ private int txSize = 1;
+
+ private boolean autoBindDlq;
+
+ private boolean durableSubscription = true;
+
+ private boolean republishToDlq;
+
+ private boolean requeueRejected = true;
+
+ private String[] replyHeaderPatterns = new String[] {"STANDARD_REPLY_HEADERS", "*"};
+
+ private long recoveryInterval = 5000;
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public boolean isTransacted() {
+ return transacted;
+ }
+
+ public void setTransacted(boolean transacted) {
+ this.transacted = transacted;
+ }
+
+ public AcknowledgeMode getAcknowledgeMode() {
+ return acknowledgeMode;
+ }
+
+ public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
+ Assert.notNull("Acknowledge mode cannot be null");
+ this.acknowledgeMode = acknowledgeMode;
+ }
+
+ @Min(value = 1, message = "Max Concurrency should be greater than zero.")
+ public int getMaxConcurrency() {
+ return maxConcurrency;
+ }
+
+ public void setMaxConcurrency(int maxConcurrency) {
+ this.maxConcurrency = maxConcurrency;
+ }
+
+ @Min(value = 1, message = "Prefetch should be greater than zero.")
+ public int getPrefetch() {
+ return prefetch;
+ }
+
+ public void setPrefetch(int prefetch) {
+ this.prefetch = prefetch;
+ }
+
+ public String[] getRequestHeaderPatterns() {
+ return requestHeaderPatterns;
+ }
+
+ public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
+ this.requestHeaderPatterns = requestHeaderPatterns;
+ }
+
+ @Min(value = 1, message = "Tx Size should be greater than zero.")
+ public int getTxSize() {
+ return txSize;
+ }
+
+ public void setTxSize(int txSize) {
+ this.txSize = txSize;
+ }
+
+ public boolean isAutoBindDlq() {
+ return autoBindDlq;
+ }
+
+ public void setAutoBindDlq(boolean autoBindDlq) {
+ this.autoBindDlq = autoBindDlq;
+ }
+
+ public boolean isDurableSubscription() {
+ return durableSubscription;
+ }
+
+ public void setDurableSubscription(boolean durableSubscription) {
+ this.durableSubscription = durableSubscription;
+ }
+
+ public boolean isRepublishToDlq() {
+ return republishToDlq;
+ }
+
+ public void setRepublishToDlq(boolean republishToDlq) {
+ this.republishToDlq = republishToDlq;
+ }
+
+ public boolean isRequeueRejected() {
+ return requeueRejected;
+ }
+
+ public void setRequeueRejected(boolean requeueRejected) {
+ this.requeueRejected = requeueRejected;
+ }
+
+ public String[] getReplyHeaderPatterns() {
+ return replyHeaderPatterns;
+ }
+
+ public void setReplyHeaderPatterns(String[] replyHeaderPatterns) {
+ this.replyHeaderPatterns = replyHeaderPatterns;
+ }
+
+ public long getRecoveryInterval() {
+ return recoveryInterval;
+ }
+
+ public void setRecoveryInterval(long recoveryInterval) {
+ this.recoveryInterval = recoveryInterval;
+ }
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitExtendedBindingProperties.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitExtendedBindingProperties.java
new file mode 100644
index 000000000..afc821c4f
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitExtendedBindingProperties.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.stream.binder.ExtendedBindingProperties;
+
+/**
+ * @author Marius Bogoevici
+ */
+@ConfigurationProperties("spring.cloud.stream.rabbit")
+public class RabbitExtendedBindingProperties implements ExtendedBindingProperties {
+
+ private Map bindings = new HashMap<>();
+
+ public Map getBindings() {
+ return bindings;
+ }
+
+ public void setBindings(Map bindings) {
+ this.bindings = bindings;
+ }
+
+ @Override
+ public RabbitConsumerProperties getExtendedConsumerProperties(String channelName) {
+ if (bindings.containsKey(channelName) && bindings.get(channelName).getConsumer() != null) {
+ return bindings.get(channelName).getConsumer();
+ }
+ else {
+ return new RabbitConsumerProperties();
+ }
+ }
+
+ @Override
+ public RabbitProducerProperties getExtendedProducerProperties(String channelName) {
+ if (bindings.containsKey(channelName) && bindings.get(channelName).getProducer() != null) {
+ return bindings.get(channelName).getProducer();
+ }
+ else {
+ return new RabbitProducerProperties();
+ }
+ }
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitManagementUtils.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitManagementUtils.java
new file mode 100644
index 000000000..6ed3dcecf
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitManagementUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.protocol.HttpContext;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * @author Gary Russell
+ * @since 1.2
+ */
+public abstract class RabbitManagementUtils {
+
+ public static RestTemplate buildRestTemplate(String adminUri, String user, String password) {
+ BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+ credsProvider.setCredentials(
+ new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
+ new UsernamePasswordCredentials(user, password));
+ HttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
+ // Set up pre-emptive basic Auth because the rabbit plugin doesn't currently support challenge/response for PUT
+ // Create AuthCache instance
+ AuthCache authCache = new BasicAuthCache();
+ // Generate BASIC scheme object and add it to the local; from the apache docs...
+ // auth cache
+ BasicScheme basicAuth = new BasicScheme();
+ URI uri;
+ try {
+ uri = new URI(adminUri);
+ }
+ catch (URISyntaxException e) {
+ throw new RabbitAdminException("Invalid URI", e);
+ }
+ authCache.put(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), basicAuth);
+ // Add AuthCache to the execution context
+ final HttpClientContext localContext = HttpClientContext.create();
+ localContext.setAuthCache(authCache);
+ RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient) {
+
+ @Override
+ protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) {
+ return localContext;
+ }
+
+ });
+ restTemplate.setMessageConverters(Collections.>singletonList(
+ new MappingJackson2HttpMessageConverter()));
+ return restTemplate;
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java
new file mode 100644
index 000000000..a996de14d
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java
@@ -0,0 +1,651 @@
+/*
+ * Copyright 2013-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+import com.rabbitmq.client.AMQP;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.Envelope;
+import org.aopalliance.aop.Advice;
+
+import org.springframework.amqp.AmqpConnectException;
+import org.springframework.amqp.core.AnonymousQueue;
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.DirectExchange;
+import org.springframework.amqp.core.Exchange;
+import org.springframework.amqp.core.MessagePostProcessor;
+import org.springframework.amqp.core.MessageProperties;
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.core.TopicExchange;
+import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.connection.LocalizedQueueConnectionFactory;
+import org.springframework.amqp.rabbit.core.BatchingRabbitTemplate;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.amqp.rabbit.core.support.BatchingStrategy;
+import org.springframework.amqp.rabbit.core.support.SimpleBatchingStrategy;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.rabbit.retry.MessageRecoverer;
+import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
+import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
+import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
+import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
+import org.springframework.amqp.support.AmqpHeaders;
+import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
+import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
+import org.springframework.cloud.stream.binder.AbstractBinder;
+import org.springframework.cloud.stream.binder.Binding;
+import org.springframework.cloud.stream.binder.DefaultBinding;
+import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
+import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
+import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
+import org.springframework.cloud.stream.binder.MessageValues;
+import org.springframework.cloud.stream.binder.PartitionHandler;
+import org.springframework.context.Lifecycle;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
+import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
+import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.context.IntegrationContextUtils;
+import org.springframework.integration.endpoint.EventDrivenConsumer;
+import org.springframework.integration.handler.AbstractMessageHandler;
+import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.retry.interceptor.RetryOperationsInterceptor;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ.
+ *
+ * @author Mark Fisher
+ * @author Gary Russell
+ * @author Jennifer Hickey
+ * @author Gunnar Hillert
+ * @author Ilayaperumal Gopinathan
+ * @author David Turanski
+ * @author Marius Bogoevici
+ */
+public class RabbitMessageChannelBinder extends AbstractBinder,
+ ExtendedProducerProperties> implements ExtendedPropertiesBinder {
+
+ public static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR
+ = new AnonymousQueue.Base64UrlNamingStrategy("anonymous.");
+
+ private static final String DEAD_LETTER_EXCHANGE = "DLX";
+
+ private static final MessagePropertiesConverter inboundMessagePropertiesConverter =
+ new DefaultMessagePropertiesConverter() {
+
+ @Override
+ public MessageProperties toMessageProperties(AMQP.BasicProperties source, Envelope envelope,
+ String charset) {
+ MessageProperties properties = super.toMessageProperties(source, envelope, charset);
+ properties.setDeliveryMode(null);
+ return properties;
+ }
+ };
+
+ private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
+
+ private final RabbitAdmin rabbitAdmin;
+
+ private final GenericApplicationContext autoDeclareContext = new GenericApplicationContext();
+
+ private final RabbitProperties rabbitProperties;
+
+ private ConnectionFactory connectionFactory;
+
+ private MessagePostProcessor decompressingPostProcessor = new DelegatingDecompressingPostProcessor();
+
+ private MessagePostProcessor compressingPostProcessor = new GZipPostProcessor();
+
+ private volatile String[] adminAddresses;
+
+ private volatile String[] nodes;
+
+ private volatile boolean clustered;
+
+ private RabbitExtendedBindingProperties extendedBindingProperties = new RabbitExtendedBindingProperties();
+
+ public RabbitMessageChannelBinder(ConnectionFactory connectionFactory, RabbitProperties rabbitProperties) {
+ Assert.notNull(connectionFactory, "connectionFactory must not be null");
+ Assert.notNull(rabbitProperties, "rabbitProperties must not be null");
+ this.connectionFactory = connectionFactory;
+ this.rabbitProperties = rabbitProperties;
+ this.rabbitAdmin = new RabbitAdmin(connectionFactory);
+ this.autoDeclareContext.refresh();
+ this.rabbitAdmin.setApplicationContext(this.autoDeclareContext);
+ this.rabbitAdmin.setIgnoreDeclarationExceptions(true);
+ this.rabbitAdmin.afterPropertiesSet();
+ }
+
+ /**
+ * Set a {@link MessagePostProcessor} to decompress messages. Defaults to a
+ * {@link DelegatingDecompressingPostProcessor} with its default delegates.
+ * @param decompressingPostProcessor the post processor.
+ */
+ public void setDecompressingPostProcessor(MessagePostProcessor decompressingPostProcessor) {
+ this.decompressingPostProcessor = decompressingPostProcessor;
+ }
+
+ /**
+ * Set a {@link org.springframework.amqp.core.MessagePostProcessor} to compress messages. Defaults to a
+ * {@link org.springframework.amqp.support.postprocessor.GZipPostProcessor}.
+ * @param compressingPostProcessor the post processor.
+ */
+ public void setCompressingPostProcessor(MessagePostProcessor compressingPostProcessor) {
+ this.compressingPostProcessor = compressingPostProcessor;
+ }
+
+ public void setAdminAddresses(String[] adminAddresses) {
+ this.adminAddresses = Arrays.copyOf(adminAddresses, adminAddresses.length);
+ }
+
+ public void setNodes(String[] nodes) {
+ this.nodes = Arrays.copyOf(nodes, nodes.length);
+ this.clustered = nodes.length > 1;
+ }
+
+ public void setExtendedBindingProperties(RabbitExtendedBindingProperties extendedBindingProperties) {
+ this.extendedBindingProperties = extendedBindingProperties;
+ }
+
+ @Override
+ public void onInit() {
+ if (this.clustered) {
+ String[] addresses = StringUtils.commaDelimitedListToStringArray(this.rabbitProperties.getAddresses());
+ Assert.state(addresses.length == this.adminAddresses.length
+ && addresses.length == this.nodes.length,
+ "'addresses', 'adminAddresses', and 'nodes' properties must have equal length");
+ this.connectionFactory = new LocalizedQueueConnectionFactory(this.connectionFactory, addresses,
+ this.adminAddresses, this.nodes, rabbitProperties.getVirtualHost(),
+ this.rabbitProperties.getUsername(), this.rabbitProperties.getPassword(),
+ this.rabbitProperties.getSsl().isEnabled(), this.rabbitProperties.getSsl().getKeyStore(),
+ this.rabbitProperties.getSsl().getTrustStore(), this.rabbitProperties.getSsl().getKeyStorePassword(),
+ this.rabbitProperties.getSsl().getTrustStorePassword());
+ }
+ }
+
+ @Override
+ public RabbitConsumerProperties getExtendedConsumerProperties(String channelName) {
+ return extendedBindingProperties.getExtendedConsumerProperties(channelName);
+ }
+
+
+ @Override
+ public RabbitProducerProperties getExtendedProducerProperties(String channelName) {
+ return extendedBindingProperties.getExtendedProducerProperties(channelName);
+ }
+
+ @Override
+ public Binding doBindConsumer(String name, String group, MessageChannel inputChannel,
+ ExtendedConsumerProperties properties) {
+ boolean anonymousConsumer = !StringUtils.hasText(group);
+ String baseQueueName = anonymousConsumer ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName())
+ : groupedName(name, group);
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name);
+ }
+ String prefix = properties.getExtension().getPrefix();
+ String exchangeName = applyPrefix(prefix, name);
+ TopicExchange exchange = new TopicExchange(exchangeName);
+ declareExchange(exchangeName, exchange);
+
+ String queueName = applyPrefix(prefix, baseQueueName);
+ boolean partitioned = !anonymousConsumer && properties.isPartitioned();
+ boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
+ Queue queue;
+
+ if (anonymousConsumer) {
+ queue = new Queue(queueName, false, true, true);
+ }
+ else {
+ if (partitioned) {
+ String partitionSuffix = "-" + properties.getInstanceIndex();
+ queueName += partitionSuffix;
+ }
+ if (durable) {
+ queue = new Queue(queueName, true, false, false,
+ queueArgs(queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq()));
+ }
+ else {
+ queue = new Queue(queueName, false, false, true);
+ }
+ }
+
+ declareQueue(queueName, queue);
+
+ if (partitioned) {
+ String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
+ declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
+ }
+ else {
+ declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
+ }
+ Binding binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
+ if (durable) {
+ autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
+ }
+ return binding;
+ }
+
+ private Map queueArgs(String queueName, String prefix, boolean bindDlq) {
+ Map args = new HashMap<>();
+ if (bindDlq) {
+ args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX"));
+ args.put("x-dead-letter-routing-key", queueName);
+ }
+ return args;
+ }
+
+ private Binding doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
+ final ExtendedConsumerProperties properties) {
+ DefaultBinding consumerBinding;
+ SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
+ this.connectionFactory);
+ listenerContainer.setAcknowledgeMode(properties.getExtension().getAcknowledgeMode());
+ listenerContainer.setChannelTransacted(properties.getExtension().isTransacted());
+ listenerContainer.setDefaultRequeueRejected(properties.getExtension().isRequeueRejected());
+ int concurrency = properties.getConcurrency();
+ concurrency = concurrency > 0 ? concurrency : 1;
+ listenerContainer.setConcurrentConsumers(concurrency);
+ int maxConcurrency = properties.getExtension().getMaxConcurrency();
+ if (maxConcurrency > concurrency) {
+ listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
+ }
+ listenerContainer.setPrefetchCount(properties.getExtension().getPrefetch());
+ listenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
+ listenerContainer.setTxSize(properties.getExtension().getTxSize());
+ listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
+ listenerContainer.setQueues(queue);
+ int maxAttempts = properties.getMaxAttempts();
+ if (maxAttempts > 1 || properties.getExtension().isRepublishToDlq()) {
+ RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
+ .maxAttempts(maxAttempts)
+ .backOffOptions(properties.getBackOffInitialInterval(),
+ properties.getBackOffMultiplier(),
+ properties.getBackOffMaxInterval())
+ .recoverer(determineRecoverer(name, properties.getExtension().getPrefix(), properties.getExtension().isRepublishToDlq()))
+ .build();
+ listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
+ }
+ listenerContainer.setAfterReceivePostProcessors(this.decompressingPostProcessor);
+ listenerContainer.setMessagePropertiesConverter(RabbitMessageChannelBinder.inboundMessagePropertiesConverter);
+ listenerContainer.afterPropertiesSet();
+ AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer);
+ adapter.setBeanFactory(this.getBeanFactory());
+ DirectChannel bridgeToModuleChannel = new DirectChannel();
+ bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
+ bridgeToModuleChannel.setBeanName(name + ".bridge");
+ adapter.setOutputChannel(bridgeToModuleChannel);
+ adapter.setBeanName("inbound." + name);
+ DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
+ mapper.setRequestHeaderNames(properties.getExtension().getRequestHeaderPatterns());
+ mapper.setReplyHeaderNames(properties.getExtension().getReplyHeaderPatterns());
+ adapter.setHeaderMapper(mapper);
+ adapter.afterPropertiesSet();
+ consumerBinding = new DefaultBinding(name, group, moduleInputChannel, adapter) {
+ @Override
+ protected void afterUnbind() {
+ cleanAutoDeclareContext(properties.getExtension().getPrefix(), name);
+ }
+ };
+ ReceivingHandler convertingBridge = new ReceivingHandler();
+ convertingBridge.setOutputChannel(moduleInputChannel);
+ convertingBridge.setBeanName(name + ".convert.bridge");
+ convertingBridge.afterPropertiesSet();
+ bridgeToModuleChannel.subscribe(convertingBridge);
+ adapter.start();
+ return consumerBinding;
+ }
+
+ private MessageRecoverer determineRecoverer(String name, String prefix, boolean republish) {
+ if (republish) {
+ RabbitTemplate errorTemplate = new RabbitTemplate(this.connectionFactory);
+ RepublishMessageRecoverer republishMessageRecoverer = new RepublishMessageRecoverer(errorTemplate,
+ deadLetterExchangeName(prefix),
+ applyPrefix(prefix, name));
+ return republishMessageRecoverer;
+ }
+ else {
+ return new RejectAndDontRequeueRecoverer();
+ }
+ }
+
+ private AmqpOutboundEndpoint buildOutboundEndpoint(final String name,
+ ExtendedProducerProperties properties,
+ RabbitTemplate rabbitTemplate) {
+ String prefix = properties.getExtension().getPrefix();
+ String exchangeName = applyPrefix(prefix, name);
+ TopicExchange exchange = new TopicExchange(exchangeName);
+ declareExchange(exchangeName, exchange);
+ AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
+ endpoint.setExchangeName(exchange.getName());
+ if (!properties.isPartitioned()) {
+ endpoint.setRoutingKey(name);
+ }
+ else {
+ endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name)));
+ }
+ for (String requiredGroupName : properties.getRequiredGroups()) {
+ String baseQueueName = exchangeName + "." + requiredGroupName;
+ if (!properties.isPartitioned()) {
+ Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(baseQueueName, prefix, properties.getExtension().isAutoBindDlq()));
+ declareQueue(baseQueueName, queue);
+ autoBindDLQ(baseQueueName, baseQueueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
+ org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(name);
+ declareBinding(baseQueueName, binding);
+ }
+ else {
+ // if the stream is partitioned, create one queue for each target partition for the default group
+ for (int i = 0; i < properties.getPartitionCount(); i++) {
+ String partitionSuffix = "-" + i;
+ String partitionQueueName = baseQueueName + partitionSuffix;
+ Queue queue = new Queue(partitionQueueName, true, false, false,
+ queueArgs(partitionQueueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq()));
+ declareQueue(queue.getName(), queue);
+ autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
+ declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(name + partitionSuffix));
+ }
+ }
+ }
+ configureOutboundHandler(endpoint, properties);
+ return endpoint;
+ }
+
+ private void configureOutboundHandler(AmqpOutboundEndpoint handler,
+ ExtendedProducerProperties producerProperties) {
+ DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
+ mapper.setRequestHeaderNames(producerProperties.getExtension().getRequestHeaderPatterns());
+ mapper.setReplyHeaderNames(producerProperties.getExtension().getReplyHeaderPatterns());
+ handler.setHeaderMapper(mapper);
+ handler.setDefaultDeliveryMode(producerProperties.getExtension().getDeliveryMode());
+ handler.setBeanFactory(this.getBeanFactory());
+ handler.afterPropertiesSet();
+ }
+
+ @Override
+ public Binding doBindProducer(String name, MessageChannel outputChannel,
+ ExtendedProducerProperties producerProperties) {
+ String exchangeName = applyPrefix(producerProperties.getExtension().getPrefix(), name);
+ TopicExchange exchange = new TopicExchange(exchangeName);
+ declareExchange(exchangeName, exchange);
+ AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, producerProperties,
+ buildRabbitTemplate(producerProperties.getExtension()));
+ return doRegisterProducer(name, outputChannel, endpoint, producerProperties);
+ }
+
+ private RabbitTemplate buildRabbitTemplate(RabbitProducerProperties properties) {
+ RabbitTemplate rabbitTemplate = null;
+ if (properties.isBatchingEnabled()) {
+ BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(
+ properties.getBatchSize(),
+ properties.getBatchBufferLimit(),
+ properties.getBatchTimeout());
+ rabbitTemplate = new BatchingRabbitTemplate(batchingStrategy,
+ getApplicationContext().getBean(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME,
+ TaskScheduler.class));
+ }
+ else {
+ rabbitTemplate = new RabbitTemplate();
+ }
+ rabbitTemplate.setConnectionFactory(this.connectionFactory);
+ if (properties.isCompress()) {
+ rabbitTemplate.setBeforePublishPostProcessors(this.compressingPostProcessor);
+ }
+ rabbitTemplate.setChannelTransacted(properties.isTransacted());
+ rabbitTemplate.afterPropertiesSet();
+ return rabbitTemplate;
+ }
+
+ private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
+ AmqpOutboundEndpoint delegate, ExtendedProducerProperties properties) {
+ return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
+ }
+
+ private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
+ AmqpOutboundEndpoint delegate, String replyTo,
+ ExtendedProducerProperties properties) {
+ Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
+ MessageHandler handler = new SendingHandler(delegate, replyTo, properties);
+ EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler);
+ consumer.setBeanFactory(getBeanFactory());
+ consumer.setBeanName("outbound." + name);
+ consumer.afterPropertiesSet();
+ DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
+ consumer.start();
+ return producerBinding;
+ }
+
+ /**
+ * If so requested, declare the DLX/DLQ and bind it. The DLQ is bound to the DLX with a routing key of the original
+ * queue name because we use default exchange routing by queue name for the original message.
+ * @param queueName The base name for the queue (including the binder prefix, if any).
+ * @param routingKey The routing key for the queue.
+ * @param autoBindDlq true if the DLQ should be bound.
+ */
+ private void autoBindDLQ(final String queueName, String routingKey, String prefix, boolean autoBindDlq) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("autoBindDLQ=" + autoBindDlq
+ + " for: " + queueName);
+ }
+ if (autoBindDlq) {
+ String dlqName = constructDLQName(queueName);
+ Queue dlq = new Queue(dlqName);
+ declareQueue(dlqName, dlq);
+ final String dlxName = deadLetterExchangeName(prefix);
+ final DirectExchange dlx = new DirectExchange(dlxName);
+ declareExchange(dlxName, dlx);
+ declareBinding(dlqName, BindingBuilder.bind(dlq).to(dlx).with(routingKey));
+ }
+ }
+
+ public void declareQueue(String beanName, Queue queue) {
+ try {
+ this.rabbitAdmin.declareQueue(queue);
+ }
+ catch (AmqpConnectException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Declaration of queue: " + queue.getName() + " deferred - connection not available");
+ }
+ }
+ addToAutoDeclareContext(beanName, queue);
+ }
+
+ public void declareExchange(final String rootName, final Exchange exchange) {
+ try {
+ this.rabbitAdmin.declareExchange(exchange);
+ }
+ catch (AmqpConnectException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Declaration of exchange: " + exchange.getName() + " deferred - connection not available");
+ }
+ }
+ addToAutoDeclareContext(rootName + ".exchange", exchange);
+ }
+
+ public void declareBinding(String rootName, org.springframework.amqp.core.Binding binding) {
+ try {
+ this.rabbitAdmin.declareBinding(binding);
+ }
+ catch (AmqpConnectException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Declaration of binding: " + rootName + ".binding deferred - connection not available");
+ }
+ }
+ addToAutoDeclareContext(rootName + ".binding", binding);
+ }
+
+ private String deadLetterExchangeName(String prefix) {
+ return prefix + DEAD_LETTER_EXCHANGE;
+ }
+
+ private void addToAutoDeclareContext(String name, Object bean) {
+ synchronized (this.autoDeclareContext) {
+ if (!this.autoDeclareContext.containsBean(name)) {
+ this.autoDeclareContext.getBeanFactory().registerSingleton(name, bean);
+ }
+ }
+ }
+
+ public void cleanAutoDeclareContext(String prefix, String name) {
+ synchronized (this.autoDeclareContext) {
+ removeSingleton(applyPrefix(prefix, name) + ".binding");
+ removeSingleton(applyPrefix(prefix, name));
+ String dlq = applyPrefix(prefix, name) + ".dlq";
+ removeSingleton(dlq + ".binding");
+ removeSingleton(dlq);
+ }
+ }
+
+ private void removeSingleton(String name) {
+ if (this.autoDeclareContext.containsBean(name)) {
+ ConfigurableListableBeanFactory beanFactory = this.autoDeclareContext.getBeanFactory();
+ if (beanFactory instanceof DefaultListableBeanFactory) {
+ ((DefaultListableBeanFactory) beanFactory).destroySingleton(name);
+ }
+ }
+ }
+
+ @Override
+ public void doManualAck(LinkedList messageHeadersList) {
+ Iterator iterator = messageHeadersList.iterator();
+ Map channelsToAck = new HashMap<>();
+ while (iterator.hasNext()) {
+ MessageHeaders messageHeaders = iterator.next();
+ if (messageHeaders.containsKey(AmqpHeaders.CHANNEL)) {
+ Channel channel = (com.rabbitmq.client.Channel) messageHeaders.get(AmqpHeaders.CHANNEL);
+ Long deliveryTag = (Long) messageHeaders.get(AmqpHeaders.DELIVERY_TAG);
+ channelsToAck.put(channel, deliveryTag);
+ }
+ }
+ for (Map.Entry entry : channelsToAck.entrySet()) {
+ try {
+ ((Channel) entry.getKey()).basicAck(entry.getValue(), true);
+ }
+ catch (IOException e) {
+ this.logger.error("Exception while manually acknowledging " + e);
+ }
+ }
+ }
+
+ private final class SendingHandler extends AbstractMessageHandler implements Lifecycle {
+
+ private final MessageHandler delegate;
+
+ private final String replyTo;
+
+ private final ExtendedProducerProperties producerProperties;
+
+ private final PartitionHandler partitionHandler;
+
+ private SendingHandler(MessageHandler delegate, String replyTo,
+ ExtendedProducerProperties properties) {
+ this.delegate = delegate;
+ this.replyTo = replyTo;
+ producerProperties = properties;
+ ConfigurableListableBeanFactory beanFactory = RabbitMessageChannelBinder.this.getBeanFactory();
+ this.setBeanFactory(beanFactory);
+ this.partitionHandler = new PartitionHandler(beanFactory, evaluationContext, partitionSelector,
+ properties);
+ }
+
+ @Override
+ protected void handleMessageInternal(Message> message) throws Exception {
+ MessageValues messageToSend = serializePayloadIfNecessary(message);
+
+ if (this.replyTo != null) {
+ messageToSend.put(AmqpHeaders.REPLY_TO, this.replyTo);
+ }
+ if (producerProperties.isPartitioned()) {
+ messageToSend.put(PARTITION_HEADER,
+ this.partitionHandler.determinePartition(message));
+ }
+
+ this.delegate.handleMessage(messageToSend.toMessage(getMessageBuilderFactory()));
+ }
+
+ @Override
+ public void start() {
+ if (this.delegate instanceof Lifecycle) {
+ ((Lifecycle) this.delegate).start();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (this.delegate instanceof Lifecycle) {
+ ((Lifecycle) this.delegate).stop();
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ if (this.delegate instanceof Lifecycle) {
+ return ((Lifecycle) this.delegate).isRunning();
+ }
+ else {
+ return true;
+ }
+ }
+
+ }
+
+ private final class ReceivingHandler extends AbstractReplyProducingMessageHandler {
+
+ private ReceivingHandler() {
+ super();
+ this.setBeanFactory(RabbitMessageChannelBinder.this.getBeanFactory());
+ }
+
+ @Override
+ protected Object handleRequestMessage(Message> requestMessage) {
+ return deserializePayloadIfNecessary(requestMessage).toMessage(getMessageBuilderFactory());
+ }
+
+ @Override
+ protected boolean shouldCopyRequestHeaders() {
+ /*
+ * we've already copied the headers so no need for the ARPMH to do it, and we don't want the content-type
+ * restored if absent.
+ */
+ return false;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java
new file mode 100644
index 000000000..940ff586b
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitProducerProperties.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import javax.validation.constraints.Min;
+
+import org.springframework.amqp.core.MessageDeliveryMode;
+
+/**
+ * @author Marius Bogoevici
+ * @author Gary Russell
+ */
+public class RabbitProducerProperties {
+
+ private String prefix = "";
+
+ private String[] requestHeaderPatterns = new String[] {"STANDARD_REQUEST_HEADERS", "*"};
+
+ private boolean autoBindDlq;
+
+ private boolean compress;
+
+ private boolean batchingEnabled;
+
+ private int batchSize = 100;
+
+ private int batchBufferLimit = 10000;
+
+ private int batchTimeout = 5000;
+
+ private boolean transacted;
+
+ private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT;
+
+ private String[] replyHeaderPatterns = new String[] {"STANDARD_REPLY_HEADERS", "*"};
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
+ this.requestHeaderPatterns = requestHeaderPatterns;
+ }
+
+ public String[] getRequestHeaderPatterns() {
+ return requestHeaderPatterns;
+ }
+
+ public void setAutoBindDlq(boolean autoBindDlq) {
+ this.autoBindDlq = autoBindDlq;
+ }
+
+ public boolean isAutoBindDlq() {
+ return autoBindDlq;
+ }
+
+ public void setCompress(boolean compress) {
+ this.compress = compress;
+ }
+
+ public boolean isCompress() {
+ return compress;
+ }
+
+ public void setDeliveryMode(MessageDeliveryMode deliveryMode) {
+ this.deliveryMode = deliveryMode;
+ }
+
+ public MessageDeliveryMode getDeliveryMode() {
+ return deliveryMode;
+ }
+
+ public String[] getReplyHeaderPatterns() {
+ return replyHeaderPatterns;
+ }
+
+ public void setReplyHeaderPatterns(String[] replyHeaderPatterns) {
+ this.replyHeaderPatterns = replyHeaderPatterns;
+ }
+
+ public boolean isBatchingEnabled() {
+ return batchingEnabled;
+ }
+
+ public void setBatchingEnabled(boolean batchingEnabled) {
+ this.batchingEnabled = batchingEnabled;
+ }
+
+ @Min(value = 1, message = "Batch Size should be greater than zero.")
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+ public void setBatchSize(int batchSize) {
+ this.batchSize = batchSize;
+ }
+
+ @Min(value = 1, message = "Batch Buffer Limit should be greater than zero.")
+ public int getBatchBufferLimit() {
+ return batchBufferLimit;
+ }
+
+ public void setBatchBufferLimit(int batchBufferLimit) {
+ this.batchBufferLimit = batchBufferLimit;
+ }
+
+ @Min(value = 1, message = "Batch Timeout should be greater than zero.")
+ public int getBatchTimeout() {
+ return batchTimeout;
+ }
+
+ public void setBatchTimeout(int batchTimeout) {
+ this.batchTimeout = batchTimeout;
+ }
+
+ public boolean isTransacted() {
+ return this.transacted;
+ }
+
+ public void setTransacted(boolean transacted) {
+ this.transacted = transacted;
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java
new file mode 100644
index 000000000..093bb4b35
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitBinderConfigurationProperties.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @author David Turanski
+ */
+@ConfigurationProperties(prefix = "spring.cloud.stream.rabbit.binder")
+class RabbitBinderConfigurationProperties {
+
+ private String[] adminAdresses = new String[0];
+
+ private String[] nodes = new String[0];
+
+ private int compressionLevel;
+
+ public String[] getAdminAdresses() {
+ return adminAdresses;
+ }
+
+ public void setAdminAdresses(String[] adminAdresses) {
+ this.adminAdresses = adminAdresses;
+ }
+
+ public String[] getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(String[] nodes) {
+ this.nodes = nodes;
+ }
+
+ public int getCompressionLevel() {
+ return compressionLevel;
+ }
+
+ public void setCompressionLevel(int compressionLevel) {
+ this.compressionLevel = compressionLevel;
+ }
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java
new file mode 100644
index 000000000..5cfd50f98
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitMessageChannelBinderConfiguration.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit.config;
+
+import org.springframework.amqp.core.MessagePostProcessor;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
+import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
+import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.stream.binder.rabbit.RabbitExtendedBindingProperties;
+import org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder;
+import org.springframework.cloud.stream.config.codec.kryo.KryoCodecAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.integration.codec.Codec;
+
+
+/**
+ * Configuration class for RabbitMQ message channel binder.
+ *
+ * @author David Turanski
+ */
+
+@Configuration
+@Import({PropertyPlaceholderAutoConfiguration.class, KryoCodecAutoConfiguration.class})
+@EnableConfigurationProperties({RabbitBinderConfigurationProperties.class, RabbitExtendedBindingProperties.class})
+public class RabbitMessageChannelBinderConfiguration {
+
+ @Autowired
+ private Codec codec;
+
+ @Autowired
+ private ConnectionFactory rabbitConnectionFactory;
+
+ @Autowired
+ private RabbitProperties rabbitProperties;
+
+ @Autowired
+ private RabbitBinderConfigurationProperties rabbitBinderConfigurationProperties;
+
+ @Autowired
+ private RabbitExtendedBindingProperties rabbitExtendedBindingProperties;
+
+ @Bean
+ RabbitMessageChannelBinder rabbitMessageChannelBinder() {
+ RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(rabbitConnectionFactory, rabbitProperties);
+ binder.setCodec(codec);
+ binder.setAdminAddresses(rabbitBinderConfigurationProperties.getAdminAdresses());
+ binder.setCompressingPostProcessor(gZipPostProcessor());
+ binder.setDecompressingPostProcessor(deCompressingPostProcessor());
+ binder.setNodes(rabbitBinderConfigurationProperties.getNodes());
+ binder.setExtendedBindingProperties(rabbitExtendedBindingProperties);
+ return binder;
+ }
+
+ @Bean
+ MessagePostProcessor deCompressingPostProcessor() {
+ return new DelegatingDecompressingPostProcessor();
+ }
+
+ @Bean
+ MessagePostProcessor gZipPostProcessor() {
+ GZipPostProcessor gZipPostProcessor = new GZipPostProcessor();
+ gZipPostProcessor.setLevel(rabbitBinderConfigurationProperties.getCompressionLevel());
+ return gZipPostProcessor;
+ }
+}
+
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java
new file mode 100644
index 000000000..c69798c57
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/config/RabbitServiceAutoConfiguration.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit.config;
+
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.RabbitHealthIndicator;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
+import org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.cloud.Cloud;
+import org.springframework.cloud.CloudFactory;
+import org.springframework.cloud.stream.binder.Binder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Profile;
+
+/**
+ * Bind to services, either locally or in a cloud environment.
+ *
+ * @author Mark Fisher
+ * @author Dave Syer
+ * @author Glenn Renfro
+ * @author David Turanski
+ * @author Eric Bottard
+ */
+@Configuration
+@ConditionalOnMissingBean(Binder.class)
+@Import(RabbitMessageChannelBinderConfiguration.class)
+@AutoConfigureBefore({CloudAutoConfiguration.class, RabbitAutoConfiguration.class})
+public class RabbitServiceAutoConfiguration {
+
+ @Bean
+ public HealthIndicator binderHealthIndicator(RabbitTemplate rabbitTemplate) {
+ return new RabbitHealthIndicator(rabbitTemplate);
+ }
+
+ @Configuration
+ @Profile("cloud")
+ @ConditionalOnClass(Cloud.class)
+ protected static class CloudConfig {
+
+ @Bean
+ public Cloud cloud() {
+ return new CloudFactory().getCloud();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(ConnectionFactory.class)
+ ConnectionFactory rabbitConnectionFactory(Cloud cloud) {
+ return cloud.getSingletonServiceConnector(ConnectionFactory.class, null);
+ }
+ }
+
+ @Configuration
+ @Profile("!cloud")
+ @Import(RabbitAutoConfiguration.class)
+ protected static class NoCloudConfig {
+ }
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/package-info.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/package-info.java
new file mode 100644
index 000000000..6e65a701f
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.
+ */
+
+/**
+ * This package contains an implementation of the {@link org.springframework.cloud.stream.binder.Binder} for RabbitMQ.
+ */
+
+package org.springframework.cloud.stream.binder.rabbit;
diff --git a/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring.binders b/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring.binders
new file mode 100644
index 000000000..7a65c4682
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/main/resources/META-INF/spring.binders
@@ -0,0 +1,2 @@
+rabbit:\
+org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
diff --git a/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/LocalizedQueueConnectionFactoryIntegrationTests.java b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/LocalizedQueueConnectionFactoryIntegrationTests.java
new file mode 100644
index 000000000..3d8ddc06d
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/LocalizedQueueConnectionFactoryIntegrationTests.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.connection.LocalizedQueueConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+
+/**
+ *
+ * @author Gary Russell
+ */
+public class LocalizedQueueConnectionFactoryIntegrationTests {
+
+ @ClassRule
+ public static RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport(true);
+
+ private LocalizedQueueConnectionFactory lqcf;
+
+ @Before
+ public void setup() {
+ ConnectionFactory defaultConnectionFactory = rabbitAvailableRule.getResource();
+ String[] addresses = new String[] { "localhost:9999", "localhost:5672" };
+ String[] adminAddresses = new String[] { "http://localhost:15672", "http://localhost:15672" };
+ String[] nodes = new String[] { "foo@bar", "rabbit@localhost" };
+ String vhost = "/";
+ String username = "guest";
+ String password = "guest";
+ this.lqcf = new LocalizedQueueConnectionFactory(defaultConnectionFactory, addresses,
+ adminAddresses, nodes, vhost, username, password, false, null, null, null, null);
+ }
+
+ @Test
+ public void testConnect() {
+ RabbitAdmin admin = new RabbitAdmin(this.lqcf);
+ Queue queue = new Queue(UUID.randomUUID().toString(), false, false, true);
+ admin.declareQueue(queue);
+ ConnectionFactory targetConnectionFactory = this.lqcf.getTargetConnectionFactory("[" + queue.getName() + "]");
+ RabbitTemplate template = new RabbitTemplate(targetConnectionFactory);
+ template.convertAndSend("", queue.getName(), "foo");
+ assertThat(template.receiveAndConvert(queue.getName())).isEqualTo("foo");
+ ((CachingConnectionFactory) targetConnectionFactory).destroy();
+ admin.deleteQueue(queue.getName());
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderCleanerTests.java b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderCleanerTests.java
new file mode 100644
index 000000000..715ff7fe1
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderCleanerTests.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.rabbitmq.client.Channel;
+import com.rabbitmq.client.DefaultConsumer;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.core.TopicExchange;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.core.ChannelCallback;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.cloud.stream.binder.AbstractBinder;
+import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Gary Russell
+ * @since 1.2
+ */
+public class RabbitBinderCleanerTests {
+
+ private static final String BINDER_PREFIX = "binder.";
+
+ @Rule
+ public RabbitTestSupport rabbitWithMgmtEnabled = new RabbitTestSupport(true);
+
+ @Test
+ public void testCleanStream() {
+ final RabbitBindingCleaner cleaner = new RabbitBindingCleaner();
+ final RestTemplate template = RabbitManagementUtils.buildRestTemplate("http://localhost:15672", "guest",
+ "guest");
+ final String stream1 = UUID.randomUUID().toString();
+ String stream2 = stream1 + "-1";
+ String firstQueue = null;
+ CachingConnectionFactory connectionFactory = rabbitWithMgmtEnabled.getResource();
+ RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
+ for (int i = 0; i < 5; i++) {
+ String queue1Name = AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".default." + i);
+ String queue2Name = AbstractBinder.applyPrefix(BINDER_PREFIX, stream2 + ".default." + i);
+ if (firstQueue == null) {
+ firstQueue = queue1Name;
+ }
+ URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
+ .pathSegment("{vhost}", "{queue}")
+ .buildAndExpand("/", queue1Name)
+ .encode().toUri();
+ template.put(uri, new AmqpQueue(false, true));
+ uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
+ .pathSegment("{vhost}", "{queue}")
+ .buildAndExpand("/", queue2Name)
+ .encode().toUri();
+ template.put(uri, new AmqpQueue(false, true));
+ uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues")
+ .pathSegment("{vhost}", "{queue}")
+ .buildAndExpand("/", AbstractBinder.constructDLQName(queue1Name)).encode().toUri();
+ template.put(uri, new AmqpQueue(false, true));
+ TopicExchange exchange = new TopicExchange(queue1Name);
+ rabbitAdmin.declareExchange(exchange);
+ rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue1Name)).to(exchange).with(queue1Name));
+ exchange = new TopicExchange(queue2Name);
+ rabbitAdmin.declareExchange(exchange);
+ rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(queue2Name)).to(exchange).with(queue2Name));
+ }
+ final TopicExchange topic1 = new TopicExchange(
+ AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".foo.bar"));
+ rabbitAdmin.declareExchange(topic1);
+ rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic1).with("#"));
+ String foreignQueue = UUID.randomUUID().toString();
+ rabbitAdmin.declareQueue(new Queue(foreignQueue));
+ rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(foreignQueue)).to(topic1).with("#"));
+ final TopicExchange topic2 = new TopicExchange(
+ AbstractBinder.applyPrefix(BINDER_PREFIX, stream2 + ".foo.bar"));
+ rabbitAdmin.declareExchange(topic2);
+ rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue(firstQueue)).to(topic2).with("#"));
+ new RabbitTemplate(connectionFactory).execute(new ChannelCallback() {
+
+ @Override
+ public Void doInRabbit(Channel channel) throws Exception {
+ String queueName = AbstractBinder.applyPrefix(BINDER_PREFIX, stream1 + ".default." + 4);
+ String consumerTag = channel.basicConsume(queueName, new DefaultConsumer(channel));
+ try {
+ waitForConsumerStateNot(queueName, 0);
+ cleaner.clean(stream1, false);
+ fail("Expected exception");
+ }
+ catch (RabbitAdminException e) {
+ assertThat(e).hasMessageContaining("Queue " + queueName + " is in use");
+ }
+ channel.basicCancel(consumerTag);
+ waitForConsumerStateNot(queueName, 1);
+ try {
+ cleaner.clean(stream1, false);
+ fail("Expected exception");
+ }
+ catch (RabbitAdminException e) {
+ assertThat(e).hasMessageContaining("Cannot delete exchange ");
+ assertThat(e).hasMessageContaining("; it has bindings:");
+ }
+ return null;
+ }
+
+ private void waitForConsumerStateNot(String queueName, int state) throws InterruptedException {
+ int n = 0;
+ URI uri = UriComponentsBuilder.fromUriString("http://localhost:15672/api/queues").pathSegment(
+ "{vhost}", "{queue}")
+ .buildAndExpand("/", queueName).encode().toUri();
+ while (n++ < 100) {
+ @SuppressWarnings("unchecked")
+ Map queueInfo = template.getForObject(uri, Map.class);
+ if (!queueInfo.get("consumers").equals(Integer.valueOf(state))) {
+ break;
+ }
+ Thread.sleep(100);
+ }
+ assertThat(n < 100).withFailMessage("Consumer state remained at " + state + " after 10 seconds");
+ }
+
+ });
+ rabbitAdmin.deleteExchange(topic1.getName()); // easier than deleting the binding
+ rabbitAdmin.declareExchange(topic1);
+ rabbitAdmin.deleteQueue(foreignQueue);
+ connectionFactory.destroy();
+ Map> cleanedMap = cleaner.clean(stream1, false);
+ assertThat(cleanedMap).hasSize(2);
+ List cleanedQueues = cleanedMap.get("queues");
+ // should *not* clean stream2
+ assertThat(cleanedQueues).hasSize(10);
+ for (int i = 0; i < 5; i++) {
+ assertThat(cleanedQueues.get(i * 2)).isEqualTo(BINDER_PREFIX + stream1 + ".default." + i);
+ assertThat(cleanedQueues.get(i * 2 + 1)).isEqualTo(BINDER_PREFIX + stream1 + ".default." + i + ".dlq");
+ }
+ List cleanedExchanges = cleanedMap.get("exchanges");
+ assertThat(cleanedExchanges).hasSize(6);
+
+ // wild card *should* clean stream2
+ cleanedMap = cleaner.clean(stream1 + "*", false);
+ assertThat(cleanedMap).hasSize(2);
+ cleanedQueues = cleanedMap.get("queues");
+ assertThat(cleanedQueues).hasSize(5);
+ for (int i = 0; i < 5; i++) {
+ assertThat(cleanedQueues.get(i)).isEqualTo(BINDER_PREFIX + stream2 + ".default." + i);
+ }
+ cleanedExchanges = cleanedMap.get("exchanges");
+ assertThat(cleanedExchanges).hasSize(6);
+ }
+
+ public static class AmqpQueue {
+
+ private boolean autoDelete;
+
+ private boolean durable;
+
+ public AmqpQueue(boolean autoDelete, boolean durable) {
+ this.autoDelete = autoDelete;
+ this.durable = durable;
+ }
+
+
+ @JsonProperty("auto_delete")
+ protected boolean isAutoDelete() {
+ return autoDelete;
+ }
+
+
+ protected void setAutoDelete(boolean autoDelete) {
+ this.autoDelete = autoDelete;
+ }
+
+
+ protected boolean isDurable() {
+ return durable;
+ }
+
+
+ protected void setDurable(boolean durable) {
+ this.durable = durable;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java
new file mode 100644
index 000000000..0f828928d
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitBinderTests.java
@@ -0,0 +1,843 @@
+/*
+ * Copyright 2013-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
+
+import org.aopalliance.aop.Advice;
+import org.apache.commons.logging.Log;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import org.springframework.amqp.core.AcknowledgeMode;
+import org.springframework.amqp.core.MessageDeliveryMode;
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.core.RabbitTemplate;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.support.AmqpHeaders;
+import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
+import org.springframework.amqp.utils.test.TestUtils;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
+import org.springframework.cloud.stream.binder.Binding;
+import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
+import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
+import org.springframework.cloud.stream.binder.PartitionCapableBinderTests;
+import org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy;
+import org.springframework.cloud.stream.binder.PartitionSelectorStrategy;
+import org.springframework.cloud.stream.binder.PartitionTestSupport;
+import org.springframework.cloud.stream.binder.Spy;
+import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
+import org.springframework.context.ApplicationContext;
+import org.springframework.expression.spel.standard.SpelExpression;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.channel.QueueChannel;
+import org.springframework.integration.endpoint.AbstractEndpoint;
+import org.springframework.integration.support.MessageBuilder;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessageHeaders;
+import org.springframework.messaging.MessagingException;
+import org.springframework.messaging.support.GenericMessage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Mark Fisher
+ * @author Gary Russell
+ * @author David Turanski
+ */
+public class RabbitBinderTests extends
+ PartitionCapableBinderTests, ExtendedProducerProperties> {
+
+ private final String CLASS_UNDER_TEST_NAME = RabbitMessageChannelBinder.class.getSimpleName();
+
+ public static final String TEST_PREFIX = "bindertest.";
+
+ @Rule
+ public RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport();
+
+ @Override
+ protected RabbitTestBinder getBinder() {
+ if (testBinder == null) {
+ testBinder = new RabbitTestBinder(rabbitAvailableRule.getResource(), new RabbitProperties());
+ }
+ return testBinder;
+ }
+
+ @Override
+ protected ExtendedConsumerProperties createConsumerProperties() {
+ return new ExtendedConsumerProperties<>(new RabbitConsumerProperties());
+ }
+
+ @Override
+ protected ExtendedProducerProperties createProducerProperties() {
+ return new ExtendedProducerProperties<>(new RabbitProducerProperties());
+ }
+
+ @Override
+ protected boolean usesExplicitRouting() {
+ return true;
+ }
+
+ @Test
+ public void testSendAndReceiveBad() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ DirectChannel moduleOutputChannel = new DirectChannel();
+ DirectChannel moduleInputChannel = new DirectChannel();
+ Binding producerBinding = binder.bindProducer("bad.0", moduleOutputChannel,
+ createProducerProperties());
+ Binding consumerBinding = binder.bindConsumer("bad.0", "test", moduleInputChannel,
+ createConsumerProperties());
+ Message> message = MessageBuilder.withPayload("bad").setHeader(MessageHeaders.CONTENT_TYPE, "foo/bar")
+ .build();
+ final CountDownLatch latch = new CountDownLatch(3);
+ moduleInputChannel.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ latch.countDown();
+ throw new RuntimeException("bad");
+ }
+ });
+ moduleOutputChannel.send(message);
+ assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
+ producerBinding.unbind();
+ consumerBinding.unbind();
+ }
+
+ @Test
+ public void testConsumerProperties() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setTransacted(true);
+ Binding consumerBinding = binder.bindConsumer("props.0", null, new DirectChannel(), properties);
+ AbstractEndpoint endpoint = extractEndpoint(consumerBinding);
+ SimpleMessageListenerContainer container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
+ SimpleMessageListenerContainer.class);
+ assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.AUTO);
+ assertThat(container.getQueueNames()[0]).startsWith(properties.getExtension().getPrefix());
+ assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isTrue();
+ assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(1);
+ assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isNull();
+ assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isTrue();
+ assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(1);
+ assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(1);
+ Advice retry = TestUtils.getPropertyValue(container, "adviceChain", Advice[].class)[0];
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(3);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.initialInterval")).isEqualTo(1000L);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.maxInterval")).isEqualTo(10000L);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.multiplier")).isEqualTo(2.0);
+ consumerBinding.unbind();
+ assertThat(endpoint.isRunning()).isFalse();
+
+ properties = createConsumerProperties();
+ properties.getExtension().setAcknowledgeMode(AcknowledgeMode.NONE);
+ properties.setBackOffInitialInterval(2000);
+ properties.setBackOffMaxInterval(20000);
+ properties.setBackOffMultiplier(5.0);
+ properties.setConcurrency(2);
+ properties.setMaxAttempts(23);
+ properties.getExtension().setMaxConcurrency(3);
+ properties.getExtension().setPrefix("foo.");
+ properties.getExtension().setPrefetch(20);
+ properties.getExtension().setRequestHeaderPatterns(new String[] { "foo" });
+ properties.getExtension().setRequeueRejected(false);
+ properties.getExtension().setTxSize(10);
+ properties.setInstanceIndex(0);
+ consumerBinding = binder.bindConsumer("props.0", "test", new DirectChannel(), properties);
+
+ endpoint = extractEndpoint(consumerBinding);
+ container = verifyContainer(endpoint);
+
+ assertThat(container.getQueueNames()[0]).isEqualTo("foo.props.0.test");
+
+ consumerBinding.unbind();
+ assertThat(endpoint.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testProducerProperties() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ Binding producerBinding = binder.bindProducer("props.0", new DirectChannel(),
+ createProducerProperties());
+ @SuppressWarnings("unchecked")
+ AbstractEndpoint endpoint = extractEndpoint(producerBinding);
+ MessageDeliveryMode mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode",
+ MessageDeliveryMode.class);
+ assertThat(mode).isEqualTo(MessageDeliveryMode.PERSISTENT);
+ List> requestHeaders = TestUtils.getPropertyValue(endpoint,
+ "handler.delegate.headerMapper.requestHeaderMatcher.matchers", List.class);
+ assertThat(requestHeaders).hasSize(2);
+ producerBinding.unbind();
+ assertThat(endpoint.isRunning()).isFalse();
+ assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.amqpTemplate.transactional", Boolean.class))
+ .isFalse();
+
+ ExtendedProducerProperties properties = createProducerProperties();
+ properties.getExtension().setPrefix("foo.");
+ properties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
+ properties.getExtension().setRequestHeaderPatterns(new String[] { "foo" });
+ properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("'foo'"));
+ properties.setPartitionKeyExtractorClass(TestPartitionKeyExtractorClass.class);
+ properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("0"));
+ properties.setPartitionSelectorClass(TestPartitionSelectorClass.class);
+ properties.setPartitionCount(1);
+ properties.getExtension().setTransacted(true);
+
+ producerBinding = binder.bindProducer("props.0", new DirectChannel(), properties);
+ endpoint = extractEndpoint(producerBinding);
+ assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression", SpelExpression.class)
+ .getExpressionString()).isEqualTo("'props.0-' + headers['partition']");
+ mode = TestUtils.getPropertyValue(endpoint, "handler.delegate.defaultDeliveryMode", MessageDeliveryMode.class);
+ assertThat(mode).isEqualTo(MessageDeliveryMode.NON_PERSISTENT);
+ assertThat(TestUtils.getPropertyValue(endpoint, "handler.delegate.amqpTemplate.transactional", Boolean.class))
+ .isTrue();
+ verifyFooRequestProducer(endpoint);
+
+ producerBinding.unbind();
+ assertThat(endpoint.isRunning()).isFalse();
+ }
+
+ @Test
+ public void testDurablePubSubWithAutoBindDLQ() throws Exception {
+ RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
+
+ RabbitTestBinder binder = getBinder();
+
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setPrefix(TEST_PREFIX);
+ properties.getExtension().setAutoBindDlq(true);
+ properties.getExtension().setDurableSubscription(true);
+ properties.setMaxAttempts(1); // disable retry
+ properties.getExtension().setRequeueRejected(false);
+ DirectChannel moduleInputChannel = new DirectChannel();
+ moduleInputChannel.setBeanName("durableTest");
+ moduleInputChannel.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ throw new RuntimeException("foo");
+ }
+
+ });
+ Binding consumerBinding = binder.bindConsumer("durabletest.0", "tgroup", moduleInputChannel,
+ properties);
+
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.convertAndSend(TEST_PREFIX + "durabletest.0", "", "foo");
+
+ int n = 0;
+ while (n++ < 100) {
+ Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "durabletest.0.tgroup.dlq");
+ if (deadLetter != null) {
+ assertThat(deadLetter).isEqualTo("foo");
+ break;
+ }
+ Thread.sleep(100);
+ }
+ assertThat(n).isLessThan(100);
+
+ consumerBinding.unbind();
+ assertThat(admin.getQueueProperties(TEST_PREFIX + "durabletest.0.tgroup.dlq")).isNotNull();
+ }
+
+ @Test
+ public void testNonDurablePubSubWithAutoBindDLQ() throws Exception {
+ RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
+
+ RabbitTestBinder binder = getBinder();
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setPrefix(TEST_PREFIX);
+ properties.getExtension().setAutoBindDlq(true);
+ properties.getExtension().setDurableSubscription(false);
+ properties.setMaxAttempts(1); // disable retry
+ properties.getExtension().setRequeueRejected(false);
+ DirectChannel moduleInputChannel = new DirectChannel();
+ moduleInputChannel.setBeanName("nondurabletest");
+ moduleInputChannel.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ throw new RuntimeException("foo");
+ }
+
+ });
+ Binding consumerBinding = binder.bindConsumer("nondurabletest.0", "tgroup", moduleInputChannel,
+ properties);
+
+ consumerBinding.unbind();
+ assertThat(admin.getQueueProperties(TEST_PREFIX + "nondurabletest.0.dlq")).isNull();
+ }
+
+ @Test
+ public void testAutoBindDLQ() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setPrefix(TEST_PREFIX);
+ properties.getExtension().setAutoBindDlq(true);
+ properties.setMaxAttempts(1); // disable retry
+ properties.getExtension().setRequeueRejected(false);
+ properties.getExtension().setDurableSubscription(true);
+ DirectChannel moduleInputChannel = new DirectChannel();
+ moduleInputChannel.setBeanName("dlqTest");
+ moduleInputChannel.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ throw new RuntimeException("foo");
+ }
+
+ });
+ Binding consumerBinding = binder.bindConsumer("dlqtest", "default", moduleInputChannel,
+ properties);
+
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.convertAndSend("", TEST_PREFIX + "dlqtest.default", "foo");
+
+ int n = 0;
+ while (n++ < 100) {
+ Object deadLetter = template.receiveAndConvert(TEST_PREFIX + "dlqtest.default.dlq");
+ if (deadLetter != null) {
+ assertThat(deadLetter).isEqualTo("foo");
+ break;
+ }
+ Thread.sleep(100);
+ }
+ assertThat(n).isLessThan(100);
+
+ consumerBinding.unbind();
+
+ ApplicationContext context = TestUtils.getPropertyValue(binder, "binder.autoDeclareContext",
+ ApplicationContext.class);
+ assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.binding")).isFalse();
+ assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default")).isFalse();
+ assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq.binding")).isFalse();
+ assertThat(context.containsBean(TEST_PREFIX + "dlqtest.default.dlq")).isFalse();
+ }
+
+ @Test
+ public void testAutoBindDLQPartionedConsumerFirst() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setPrefix("bindertest.");
+ properties.getExtension().setAutoBindDlq(true);
+ properties.setMaxAttempts(1); // disable retry
+ properties.getExtension().setRequeueRejected(false);
+ properties.setPartitioned(true);
+ properties.setInstanceIndex(0);
+ DirectChannel input0 = new DirectChannel();
+ input0.setBeanName("test.input0DLQ");
+ Binding input0Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input0, properties);
+ Binding defaultConsumerBinding1 = binder.bindConsumer("partDLQ.0", "default",
+ new QueueChannel(), properties);
+ properties.setInstanceIndex(1);
+ DirectChannel input1 = new DirectChannel();
+ input1.setBeanName("test.input1DLQ");
+ Binding input1Binding = binder.bindConsumer("partDLQ.0", "dlqPartGrp", input1, properties);
+ Binding defaultConsumerBinding2 = binder.bindConsumer("partDLQ.0", "default",
+ new QueueChannel(), properties);
+
+ ExtendedProducerProperties producerProperties = createProducerProperties();
+ producerProperties.getExtension().setPrefix("bindertest.");
+ producerProperties.getExtension().setAutoBindDlq(true);
+ producerProperties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
+ producerProperties.setPartitionSelectorClass(PartitionTestSupport.class);
+ producerProperties.setPartitionCount(2);
+ DirectChannel output = new DirectChannel();
+ output.setBeanName("test.output");
+ Binding outputBinding = binder.bindProducer("partDLQ.0", output, producerProperties);
+
+ final CountDownLatch latch0 = new CountDownLatch(1);
+ input0.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ if (latch0.getCount() <= 0) {
+ throw new RuntimeException("dlq");
+ }
+ latch0.countDown();
+ }
+
+ });
+
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ input1.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ if (latch1.getCount() <= 0) {
+ throw new RuntimeException("dlq");
+ }
+ latch1.countDown();
+ }
+
+ });
+
+ output.send(new GenericMessage<>(1));
+ assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();
+
+ output.send(new GenericMessage<>(0));
+ assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();
+
+ output.send(new GenericMessage<>(1));
+
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.setReceiveTimeout(10000);
+
+ String streamDLQName = "bindertest.partDLQ.0.dlqPartGrp.dlq";
+
+ org.springframework.amqp.core.Message received = template.receive(streamDLQName);
+ assertThat(received).isNotNull();
+ assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 1);
+
+ output.send(new GenericMessage<>(0));
+ received = template.receive(streamDLQName);
+ assertThat(received).isNotNull();
+ assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 0);
+
+ input0Binding.unbind();
+ input1Binding.unbind();
+ defaultConsumerBinding1.unbind();
+ defaultConsumerBinding2.unbind();
+ outputBinding.unbind();
+ }
+
+ @Test
+ public void testAutoBindDLQPartitionedProducerFirst() throws Exception {
+ RabbitTestBinder binder = getBinder();
+ ExtendedProducerProperties properties = createProducerProperties();
+
+ properties.getExtension().setPrefix("bindertest.");
+ properties.getExtension().setAutoBindDlq(true);
+ properties.setRequiredGroups("dlqPartGrp");
+ properties.setPartitionKeyExtractorClass(PartitionTestSupport.class);
+ properties.setPartitionSelectorClass(PartitionTestSupport.class);
+ properties.setPartitionCount(2);
+ DirectChannel output = new DirectChannel();
+ output.setBeanName("test.output");
+ Binding outputBinding = binder.bindProducer("partDLQ.1", output, properties);
+
+ ExtendedConsumerProperties consumerProperties = createConsumerProperties();
+ consumerProperties.getExtension().setPrefix("bindertest.");
+ consumerProperties.getExtension().setAutoBindDlq(true);
+ consumerProperties.setMaxAttempts(1); // disable retry
+ consumerProperties.getExtension().setRequeueRejected(false);
+ consumerProperties.setPartitioned(true);
+ consumerProperties.setInstanceIndex(0);
+ DirectChannel input0 = new DirectChannel();
+ input0.setBeanName("test.input0DLQ");
+ Binding input0Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input0,
+ consumerProperties);
+ Binding defaultConsumerBinding1 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
+ new QueueChannel(), consumerProperties);
+ consumerProperties.setInstanceIndex(1);
+ DirectChannel input1 = new DirectChannel();
+ input1.setBeanName("test.input1DLQ");
+ Binding input1Binding = binder.bindConsumer("partDLQ.1", "dlqPartGrp", input1,
+ consumerProperties);
+ Binding defaultConsumerBinding2 = binder.bindConsumer("partDLQ.1", "defaultConsumer",
+ new QueueChannel(), consumerProperties);
+
+ final CountDownLatch latch0 = new CountDownLatch(1);
+ input0.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ if (latch0.getCount() <= 0) {
+ throw new RuntimeException("dlq");
+ }
+ latch0.countDown();
+ }
+
+ });
+
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ input1.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ if (latch1.getCount() <= 0) {
+ throw new RuntimeException("dlq");
+ }
+ latch1.countDown();
+ }
+
+ });
+
+ output.send(new GenericMessage(1));
+ assertThat(latch1.await(10, TimeUnit.SECONDS)).isTrue();
+
+ output.send(new GenericMessage(0));
+ assertThat(latch0.await(10, TimeUnit.SECONDS)).isTrue();
+
+ output.send(new GenericMessage(1));
+
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.setReceiveTimeout(10000);
+
+ String streamDLQName = "bindertest.partDLQ.1.dlqPartGrp.dlq";
+
+ org.springframework.amqp.core.Message received = template.receive(streamDLQName);
+ assertThat(received).isNotNull();
+ assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 1);
+
+ output.send(new GenericMessage(0));
+ received = template.receive(streamDLQName);
+ assertThat(received).isNotNull();
+ assertThat(received.getMessageProperties().getHeaders()).containsEntry("partition", 0);
+
+ input0Binding.unbind();
+ input1Binding.unbind();
+ defaultConsumerBinding1.unbind();
+ defaultConsumerBinding2.unbind();
+ outputBinding.unbind();
+ }
+
+ @Test
+ public void testAutoBindDLQwithRepublish() throws Exception {
+ // pre-declare the queue with dead-lettering, users can also use a policy
+ RabbitAdmin admin = new RabbitAdmin(this.rabbitAvailableRule.getResource());
+ Map args = new HashMap();
+ args.put("x-dead-letter-exchange", TEST_PREFIX + "DLX");
+ args.put("x-dead-letter-routing-key", TEST_PREFIX + "dlqpubtest.default");
+ Queue queue = new Queue(TEST_PREFIX + "dlqpubtest.default", true, false, false, args);
+ admin.declareQueue(queue);
+
+ RabbitTestBinder binder = getBinder();
+ ExtendedConsumerProperties properties = createConsumerProperties();
+ properties.getExtension().setPrefix(TEST_PREFIX);
+ properties.getExtension().setAutoBindDlq(true);
+ properties.getExtension().setRepublishToDlq(true);
+ properties.setMaxAttempts(1); // disable retry
+ properties.getExtension().setRequeueRejected(false);
+ properties.getExtension().setDurableSubscription(true);
+ DirectChannel moduleInputChannel = new DirectChannel();
+ moduleInputChannel.setBeanName("dlqPubTest");
+ moduleInputChannel.subscribe(new MessageHandler() {
+
+ @Override
+ public void handleMessage(Message> message) throws MessagingException {
+ throw new RuntimeException("foo");
+ }
+
+ });
+ Binding consumerBinding = binder.bindConsumer("dlqpubtest", "default", moduleInputChannel,
+ properties);
+
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.convertAndSend("", TEST_PREFIX + "dlqpubtest.default", "foo");
+
+ int n = 0;
+ while (n++ < 100) {
+ org.springframework.amqp.core.Message deadLetter = template.receive(TEST_PREFIX + "dlqpubtest.default.dlq");
+ if (deadLetter != null) {
+ assertThat(new String(deadLetter.getBody())).isEqualTo("foo");
+ assertThat(deadLetter.getMessageProperties().getHeaders()).containsKey(("x-exception-stacktrace"));
+ break;
+ }
+ Thread.sleep(100);
+ }
+ assertThat(n).isLessThan(100);
+
+ consumerBinding.unbind();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testBatchingAndCompression() throws Exception {
+ RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ RabbitTestBinder binder = getBinder();
+ ExtendedProducerProperties properties = createProducerProperties();
+ properties.getExtension().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
+ properties.getExtension().setBatchingEnabled(true);
+ properties.getExtension().setBatchSize(2);
+ properties.getExtension().setBatchBufferLimit(100000);
+ properties.getExtension().setBatchTimeout(30000);
+ properties.getExtension().setCompress(true);
+ properties.setRequiredGroups("default");
+
+ DirectChannel output = new DirectChannel();
+ output.setBeanName("batchingProducer");
+ Binding producerBinding = binder.bindProducer("batching.0", output, properties);
+
+ Log logger = spy(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.logger", Log.class));
+ new DirectFieldAccessor(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor"))
+ .setPropertyValue("logger", logger);
+ when(logger.isTraceEnabled()).thenReturn(true);
+
+ assertThat(TestUtils.getPropertyValue(binder, "binder.compressingPostProcessor.level"))
+ .isEqualTo(Deflater.BEST_SPEED);
+
+ output.send(new GenericMessage<>("foo".getBytes()));
+ output.send(new GenericMessage<>("bar".getBytes()));
+
+ Object out = spyOn("batching.0.default").receive(false);
+ assertThat(out).isInstanceOf(byte[].class);
+ assertThat(new String((byte[]) out)).isEqualTo("\u0000\u0000\u0000\u0003foo\u0000\u0000\u0000\u0003bar");
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Object.class);
+ verify(logger).trace(captor.capture());
+ assertThat(captor.getValue().toString()).contains(("Compressed 14 to "));
+
+ QueueChannel input = new QueueChannel();
+ input.setBeanName("batchingConsumer");
+ Binding consumerBinding = binder.bindConsumer("batching.0", "test", input,
+ createConsumerProperties());
+
+ output.send(new GenericMessage<>("foo".getBytes()));
+ output.send(new GenericMessage<>("bar".getBytes()));
+
+ Message in = (Message) input.receive(10000);
+ assertThat(in).isNotNull();
+ assertThat(new String(in.getPayload())).isEqualTo("foo");
+ in = (Message) input.receive(10000);
+ assertThat(in).isNotNull();
+ assertThat(new String(in.getPayload())).isEqualTo("bar");
+ assertThat(in.getHeaders().get(AmqpHeaders.DELIVERY_MODE)).isNull();
+
+ producerBinding.unbind();
+ consumerBinding.unbind();
+ }
+
+ /*
+ * Test late binding due to broker down; queues with and without DLQs, and partitioned
+ * queues.
+ */
+ @Test
+ public void testLateBinding() throws Exception {
+ RabbitTestSupport.RabbitProxy proxy = new RabbitTestSupport.RabbitProxy();
+ CachingConnectionFactory cf = new CachingConnectionFactory("localhost", proxy.getPort());
+ RabbitMessageChannelBinder rabbitBinder = new RabbitMessageChannelBinder(cf, new RabbitProperties());
+ RabbitTestBinder binder = new RabbitTestBinder(cf, rabbitBinder);
+
+ ExtendedProducerProperties properties = createProducerProperties();
+ properties.getExtension().setPrefix("latebinder.");
+ properties.getExtension().setAutoBindDlq(true);
+
+ MessageChannel moduleOutputChannel = new DirectChannel();
+ Binding late0ProducerBinding = binder.bindProducer("late.0", moduleOutputChannel, properties);
+
+ QueueChannel moduleInputChannel = new QueueChannel();
+ ExtendedConsumerProperties rabbitConsumerProperties = createConsumerProperties();
+ rabbitConsumerProperties.getExtension().setPrefix("latebinder.");
+ Binding late0ConsumerBinding = binder.bindConsumer("late.0", "test", moduleInputChannel,
+ rabbitConsumerProperties);
+
+ properties.setPartitionKeyExpression(spelExpressionParser.parseExpression("payload.equals('0') ? 0 : 1"));
+ properties.setPartitionSelectorExpression(spelExpressionParser.parseExpression("hashCode()"));
+ properties.setPartitionCount(2);
+
+ MessageChannel partOutputChannel = new DirectChannel();
+ Binding partlate0ProducerBinding = binder.bindProducer("partlate.0", partOutputChannel,
+ properties);
+
+ QueueChannel partInputChannel0 = new QueueChannel();
+ QueueChannel partInputChannel1 = new QueueChannel();
+
+ ExtendedConsumerProperties partLateConsumerProperties = createConsumerProperties();
+ partLateConsumerProperties.getExtension().setPrefix("latebinder.");
+ partLateConsumerProperties.setPartitioned(true);
+ partLateConsumerProperties.setInstanceIndex(0);
+ Binding partlate0Consumer0Binding = binder.bindConsumer("partlate.0", "test", partInputChannel0,
+ partLateConsumerProperties);
+ partLateConsumerProperties.setInstanceIndex(1);
+ Binding partlate0Consumer1Binding = binder.bindConsumer("partlate.0", "test", partInputChannel1,
+ partLateConsumerProperties);
+
+ ExtendedProducerProperties noDlqProducerProperties = createProducerProperties();
+ noDlqProducerProperties.getExtension().setPrefix("latebinder.");
+ MessageChannel noDLQOutputChannel = new DirectChannel();
+ Binding noDlqProducerBinding = binder.bindProducer("lateNoDLQ.0", noDLQOutputChannel,
+ noDlqProducerProperties);
+
+ QueueChannel noDLQInputChannel = new QueueChannel();
+ ExtendedConsumerProperties noDlqConsumerProperties = createConsumerProperties();
+ noDlqConsumerProperties.getExtension().setPrefix("latebinder.");
+ Binding noDlqConsumerBinding = binder.bindConsumer("lateNoDLQ.0", "test", noDLQInputChannel,
+ noDlqConsumerProperties);
+
+ MessageChannel outputChannel = new DirectChannel();
+ Binding pubSubProducerBinding = binder.bindProducer("latePubSub", outputChannel,
+ noDlqProducerProperties);
+ QueueChannel pubSubInputChannel = new QueueChannel();
+ noDlqConsumerProperties.getExtension().setDurableSubscription(false);
+ Binding nonDurableConsumerBinding = binder.bindConsumer("latePubSub", "lategroup",
+ pubSubInputChannel, noDlqConsumerProperties);
+ QueueChannel durablePubSubInputChannel = new QueueChannel();
+ noDlqConsumerProperties.getExtension().setDurableSubscription(true);
+ Binding durableConsumerBinding = binder.bindConsumer("latePubSub", "lateDurableGroup",
+ durablePubSubInputChannel, noDlqConsumerProperties);
+
+ proxy.start();
+
+ moduleOutputChannel.send(new GenericMessage<>("foo"));
+ Message> message = moduleInputChannel.receive(10000);
+ assertThat(message).isNotNull();
+ assertThat(message.getPayload()).isNotNull();
+
+ noDLQOutputChannel.send(new GenericMessage<>("bar"));
+ message = noDLQInputChannel.receive(10000);
+ assertThat(message);
+ assertThat(message.getPayload()).isEqualTo("bar");
+
+ outputChannel.send(new GenericMessage<>("baz"));
+ message = pubSubInputChannel.receive(10000);
+ assertThat(message);
+ assertThat(message.getPayload()).isEqualTo("baz");
+ message = durablePubSubInputChannel.receive(10000);
+ assertThat(message).isNotNull();
+ assertThat(message.getPayload()).isEqualTo("baz");
+
+ partOutputChannel.send(new GenericMessage<>("0"));
+ partOutputChannel.send(new GenericMessage<>("1"));
+ message = partInputChannel0.receive(10000);
+ assertThat(message).isNotNull();
+ assertThat(message.getPayload()).isEqualTo("0");
+ message = partInputChannel1.receive(10000);
+ assertThat(message).isNotNull();
+ assertThat(message.getPayload()).isEqualTo("1");
+
+ late0ProducerBinding.unbind();
+ late0ConsumerBinding.unbind();
+ partlate0ProducerBinding.unbind();
+ partlate0Consumer0Binding.unbind();
+ partlate0Consumer1Binding.unbind();
+ noDlqProducerBinding.unbind();
+ noDlqConsumerBinding.unbind();
+ pubSubProducerBinding.unbind();
+ nonDurableConsumerBinding.unbind();
+ durableConsumerBinding.unbind();
+
+ binder.cleanup();
+
+ proxy.stop();
+ cf.destroy();
+
+ this.rabbitAvailableRule.getResource().destroy();
+ }
+
+ private SimpleMessageListenerContainer verifyContainer(AbstractEndpoint endpoint) {
+ SimpleMessageListenerContainer container;
+ Advice retry;
+ container = TestUtils.getPropertyValue(endpoint, "messageListenerContainer",
+ SimpleMessageListenerContainer.class);
+ assertThat(container.getAcknowledgeMode()).isEqualTo(AcknowledgeMode.NONE);
+ assertThat(container.getQueueNames()[0]).startsWith("foo.props.0");
+ assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isFalse();
+ assertThat(TestUtils.getPropertyValue(container, "concurrentConsumers")).isEqualTo(2);
+ assertThat(TestUtils.getPropertyValue(container, "maxConcurrentConsumers")).isEqualTo(3);
+ assertThat(TestUtils.getPropertyValue(container, "defaultRequeueRejected", Boolean.class)).isFalse();
+ assertThat(TestUtils.getPropertyValue(container, "prefetchCount")).isEqualTo(20);
+ assertThat(TestUtils.getPropertyValue(container, "txSize")).isEqualTo(10);
+ retry = TestUtils.getPropertyValue(container, "adviceChain", Advice[].class)[0];
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.retryPolicy.maxAttempts")).isEqualTo(23);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.initialInterval")).isEqualTo(2000L);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.maxInterval")).isEqualTo(20000L);
+ assertThat(TestUtils.getPropertyValue(retry, "retryOperations.backOffPolicy.multiplier")).isEqualTo(5.0);
+
+ List> requestMatchers = TestUtils.getPropertyValue(endpoint, "headerMapper.requestHeaderMatcher.matchers",
+ List.class);
+ assertThat(requestMatchers).hasSize(1);
+ assertThat(TestUtils.getPropertyValue(requestMatchers.get(0), "pattern")).isEqualTo("foo");
+
+ return container;
+ }
+
+ private void verifyFooRequestProducer(AbstractEndpoint endpoint) {
+ List> requestMatchers = TestUtils.getPropertyValue(endpoint,
+ "handler.delegate.headerMapper.requestHeaderMatcher.matchers", List.class);
+ assertThat(requestMatchers).hasSize(1);
+ assertThat(TestUtils.getPropertyValue(requestMatchers.get(0), "pattern")).isEqualTo("foo");
+ }
+
+ @Override
+ protected String getEndpointRouting(AbstractEndpoint endpoint) {
+ return TestUtils.getPropertyValue(endpoint, "handler.delegate.routingKeyExpression", SpelExpression.class)
+ .getExpressionString();
+ }
+
+ @Override
+ protected String getExpectedRoutingBaseDestination(String name, String group) {
+ return name;
+ }
+
+ @Override
+ protected String getPubSubEndpointRouting(AbstractEndpoint endpoint) {
+ return TestUtils.getPropertyValue(endpoint, "handler.delegate.exchangeNameExpression", SpelExpression.class)
+ .getExpressionString();
+ }
+
+ @Override
+ protected String getClassUnderTestName() {
+ return CLASS_UNDER_TEST_NAME;
+ }
+
+ @Override
+ public Spy spyOn(final String queue) {
+ final RabbitTemplate template = new RabbitTemplate(this.rabbitAvailableRule.getResource());
+ template.setAfterReceivePostProcessors(new DelegatingDecompressingPostProcessor());
+ return new Spy() {
+
+ @Override
+ public Object receive(boolean expectNull) throws Exception {
+ if (expectNull) {
+ Thread.sleep(50);
+ return template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
+ }
+ Object bar = null;
+ int n = 0;
+ while (n++ < 100 && bar == null) {
+ bar = template.receiveAndConvert(new RabbitConsumerProperties().getPrefix() + queue);
+ Thread.sleep(100);
+ }
+ assertThat(n).isLessThan(100).withFailMessage("Message did not arrive in RabbitMQ");
+ return bar;
+ }
+
+ };
+ }
+
+ private static class TestPartitionKeyExtractorClass implements PartitionKeyExtractorStrategy {
+
+ @Override
+ public Object extractKey(Message> message) {
+ return null;
+ }
+ }
+
+ private static class TestPartitionSelectorClass implements PartitionSelectorStrategy {
+
+ @Override
+ public int selectPartition(Object key, int partitionCount) {
+ return 0;
+ }
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java
new file mode 100644
index 000000000..4b9e35b81
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/RabbitTestBinder.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
+import org.springframework.cloud.stream.binder.AbstractTestBinder;
+import org.springframework.cloud.stream.binder.Binding;
+import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
+import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.integration.codec.kryo.PojoCodec;
+import org.springframework.integration.context.IntegrationContextUtils;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+
+/**
+ * Test support class for {@link RabbitMessageChannelBinder}.
+ *
+ * @author Ilayaperumal Gopinathan
+ * @author Gary Russell
+ * @author David Turanski
+ * @author Mark Fisher
+ */
+public class RabbitTestBinder extends AbstractTestBinder, ExtendedProducerProperties> {
+
+ private final RabbitAdmin rabbitAdmin;
+
+ private final Set prefixes = new HashSet<>();
+
+ private final Set queues = new HashSet();
+
+ private final Set exchanges = new HashSet();
+
+ public RabbitTestBinder(ConnectionFactory connectionFactory, RabbitProperties rabbitProperties) {
+ this(connectionFactory, new RabbitMessageChannelBinder(connectionFactory, rabbitProperties));
+ }
+
+ public RabbitTestBinder(ConnectionFactory connectionFactory, RabbitMessageChannelBinder binder) {
+ GenericApplicationContext context = new GenericApplicationContext();
+ ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
+ scheduler.setPoolSize(1);
+ scheduler.afterPropertiesSet();
+ context.getBeanFactory().registerSingleton(IntegrationContextUtils.TASK_SCHEDULER_BEAN_NAME, scheduler);
+ context.refresh();
+ binder.setApplicationContext(context);
+ binder.setCodec(new PojoCodec());
+ this.setBinder(binder);
+ this.rabbitAdmin = new RabbitAdmin(connectionFactory);
+ }
+
+ @Override
+ public Binding bindConsumer(String name, String group, MessageChannel moduleInputChannel,
+ ExtendedConsumerProperties properties) {
+ if (group != null) {
+ this.queues.add(properties.getExtension().getPrefix() + name + ("." + group));
+ }
+ this.exchanges.add(properties.getExtension().getPrefix() + name);
+ return super.bindConsumer(name, group, moduleInputChannel, properties);
+ }
+
+ @Override
+ public Binding bindProducer(String name, MessageChannel moduleOutputChannel,
+ ExtendedProducerProperties properties) {
+ this.queues.add(properties.getExtension().getPrefix() + name + ".default");
+ this.exchanges.add(properties.getExtension().getPrefix() + name);
+ return super.bindProducer(name, moduleOutputChannel, properties);
+ }
+
+ @Override
+ public void cleanup() {
+ for (String queue : this.queues) {
+ this.rabbitAdmin.deleteQueue(queue);
+ this.rabbitAdmin.deleteQueue(queue + ".dlq");
+ // delete any partitioned queues
+ for (int i = 0; i < 10; i++) {
+ this.rabbitAdmin.deleteQueue(queue + "-" + i);
+ this.rabbitAdmin.deleteQueue(queue + "-" + i + ".dlq");
+ }
+ }
+ for (String exchange : this.exchanges) {
+ this.rabbitAdmin.deleteExchange(exchange);
+ }
+ for (String prefix : this.prefixes) {
+ this.rabbitAdmin.deleteExchange(prefix + "DLX");
+ }
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java
new file mode 100644
index 000000000..5ce7e402f
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/java/org/springframework/cloud/stream/binder/rabbit/integration/RabbitBinderModuleTests.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.cloud.stream.binder.rabbit.integration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.core.RabbitAdmin;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.utils.test.TestUtils;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.actuate.health.CompositeHealthIndicator;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.binder.Binder;
+import org.springframework.cloud.stream.binder.BinderFactory;
+import org.springframework.cloud.stream.binder.Binding;
+import org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder;
+import org.springframework.cloud.stream.binding.ChannelBindingService;
+import org.springframework.cloud.stream.messaging.Processor;
+import org.springframework.cloud.stream.test.junit.rabbit.RabbitTestSupport;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.messaging.MessageChannel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Marius Bogoevici
+ * @author Gary Russell
+ */
+public class RabbitBinderModuleTests {
+
+ @ClassRule
+ public static RabbitTestSupport rabbitTestSupport = new RabbitTestSupport();
+
+ private ConfigurableApplicationContext context;
+
+ public static final ConnectionFactory MOCK_CONNECTION_FACTORY = Mockito.mock(ConnectionFactory.class,
+ Mockito.RETURNS_MOCKS);
+
+ @After
+ public void tearDown() {
+ if (context != null) {
+ context.close();
+ context = null;
+ }
+ RabbitAdmin admin = new RabbitAdmin(rabbitTestSupport.getResource());
+ admin.deleteQueue("binder.input.default");
+ admin.deleteQueue("binder.output.default");
+ admin.deleteExchange("binder.input");
+ admin.deleteExchange("binder.output");
+ }
+
+ @Test
+ public void testParentConnectionFactoryInheritedByDefault() {
+ context = SpringApplication.run(SimpleProcessor.class, "--server.port=0");
+ BinderFactory> binderFactory = context.getBean(BinderFactory.class);
+ Binder binder = binderFactory.getBinder(null);
+ assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
+ DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
+ ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
+ .getPropertyValue("connectionFactory");
+ assertThat(binderConnectionFactory).isInstanceOf(CachingConnectionFactory.class);
+ ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
+ assertThat(binderConnectionFactory).isSameAs(connectionFactory);
+ CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
+ CompositeHealthIndicator.class);
+ DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
+ assertThat(bindersHealthIndicator).isNotNull();
+ @SuppressWarnings("unchecked")
+ Map healthIndicators = (Map) directFieldAccessor
+ .getPropertyValue("indicators");
+ assertThat(healthIndicators).containsKey(("rabbit"));
+ assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo((Status.UP));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testParentConnectionFactoryInheritedByDefaultAndRabbitSettingsPropagated() {
+ context = SpringApplication.run(SimpleProcessor.class, "--server.port=0",
+ "--spring.cloud.stream.rabbit.bindings.input.consumer.transacted=true",
+ "--spring.cloud.stream.rabbit.bindings.output.producer.transacted=true");
+ BinderFactory> binderFactory = context.getBean(BinderFactory.class);
+ Binder binder = binderFactory.getBinder(null);
+ assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
+ ChannelBindingService channelBindingService = context.getBean(ChannelBindingService.class);
+ DirectFieldAccessor channelBindingServiceAccessor = new DirectFieldAccessor(channelBindingService);
+ Map>> consumerBindings = (Map>>) channelBindingServiceAccessor
+ .getPropertyValue("consumerBindings");
+ Binding inputBinding = consumerBindings.get("input").get(0);
+ SimpleMessageListenerContainer container = TestUtils.getPropertyValue(inputBinding,
+ "endpoint.messageListenerContainer", SimpleMessageListenerContainer.class);
+ assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class)).isTrue();
+ Map> producerBindings = (Map>) TestUtils
+ .getPropertyValue(channelBindingService, "producerBindings");
+ Binding outputBinding = producerBindings.get("output");
+ assertThat(TestUtils.getPropertyValue(outputBinding, "endpoint.handler.delegate.amqpTemplate.transactional",
+ Boolean.class)).isTrue();
+ DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
+ ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
+ .getPropertyValue("connectionFactory");
+ assertThat(binderConnectionFactory).isInstanceOf(CachingConnectionFactory.class);
+ ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
+ assertThat(binderConnectionFactory).isSameAs(connectionFactory);
+ CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
+ CompositeHealthIndicator.class);
+ DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
+ assertThat(bindersHealthIndicator).isNotNull();
+ @SuppressWarnings("unchecked")
+ Map healthIndicators = (Map) directFieldAccessor
+ .getPropertyValue("indicators");
+ assertThat(healthIndicators).containsKey("rabbit");
+ assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo(Status.UP);
+ }
+
+ @Test
+ public void testParentConnectionFactoryInheritedIfOverridden() {
+ context = new SpringApplication(SimpleProcessor.class, ConnectionFactoryConfiguration.class)
+ .run("--server.port=0");
+ BinderFactory> binderFactory = context.getBean(BinderFactory.class);
+ Binder binder = binderFactory.getBinder(null);
+ assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
+ DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
+ ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
+ .getPropertyValue("connectionFactory");
+ assertThat(binderConnectionFactory).isSameAs(MOCK_CONNECTION_FACTORY);
+ ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
+ assertThat(binderConnectionFactory).isSameAs(connectionFactory);
+ CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
+ CompositeHealthIndicator.class);
+ assertThat(bindersHealthIndicator).isNotNull();
+ DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
+ @SuppressWarnings("unchecked")
+ Map healthIndicators = (Map) directFieldAccessor
+ .getPropertyValue("indicators");
+ assertThat(healthIndicators).containsKey("rabbit");
+ // mock connection factory behaves as if down
+ assertThat(healthIndicators.get("rabbit").health().getStatus()).isEqualTo(Status.DOWN);
+ }
+
+ @Test
+ public void testParentConnectionFactoryNotInheritedByCustomizedBinders() {
+ List params = new ArrayList<>();
+ params.add("--spring.cloud.stream.input.binder=custom");
+ params.add("--spring.cloud.stream.output.binder=custom");
+ params.add("--spring.cloud.stream.binders.custom.type=rabbit");
+ params.add("--spring.cloud.stream.binders.custom.environment.foo=bar");
+ params.add("--server.port=0");
+ context = SpringApplication.run(SimpleProcessor.class, params.toArray(new String[params.size()]));
+ BinderFactory> binderFactory = context.getBean(BinderFactory.class);
+ Binder binder = binderFactory.getBinder(null);
+ assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
+ DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
+ ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
+ .getPropertyValue("connectionFactory");
+ ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
+ assertThat(binderConnectionFactory).isNotSameAs(connectionFactory);
+ CompositeHealthIndicator bindersHealthIndicator = context.getBean("bindersHealthIndicator",
+ CompositeHealthIndicator.class);
+ assertThat(bindersHealthIndicator);
+ DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(bindersHealthIndicator);
+ @SuppressWarnings("unchecked")
+ Map healthIndicators = (Map) directFieldAccessor
+ .getPropertyValue("indicators");
+ assertThat(healthIndicators).containsKey("custom");
+ assertThat(healthIndicators.get("custom").health().getStatus()).isEqualTo(Status.UP);
+ }
+
+ @EnableBinding(Processor.class)
+ @SpringBootApplication
+ public static class SimpleProcessor {
+
+ }
+
+ public static class ConnectionFactoryConfiguration {
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ return MOCK_CONNECTION_FACTORY;
+ }
+
+ }
+
+}
diff --git a/spring-cloud-stream-binder-rabbit/src/test/resources/log4j.properties b/spring-cloud-stream-binder-rabbit/src/test/resources/log4j.properties
new file mode 100644
index 000000000..335956bc0
--- /dev/null
+++ b/spring-cloud-stream-binder-rabbit/src/test/resources/log4j.properties
@@ -0,0 +1,8 @@
+log4j.rootCategory=DEBUG, stdout
+
+# standard logging including calling site
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
+
+log4j.category.org.springframework.cloud.binder.rabbit=DEBUG