Merge remote-tracking branch 'r-binder/monorepo-3.2.x' into monorepo-3.2.x
This commit is contained in:
25
r-binder/.gitignore
vendored
Normal file
25
r-binder/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/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
|
||||
.sts4-cache
|
||||
1
r-binder/.mvn/jvm.config
Normal file
1
r-binder/.mvn/jvm.config
Normal file
@@ -0,0 +1 @@
|
||||
-Xmx1024m -XX:CICompilerCount=1 -XX:TieredStopAtLevel=1 -Djava.security.egd=file:/dev/./urandom
|
||||
1
r-binder/.mvn/maven.config
Normal file
1
r-binder/.mvn/maven.config
Normal file
@@ -0,0 +1 @@
|
||||
-DaltSnapshotDeploymentRepository=repo.spring.io::default::https://repo.spring.io/libs-snapshot-local -P spring
|
||||
117
r-binder/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
r-binder/.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2007-present 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.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
||||
BIN
r-binder/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
BIN
r-binder/.mvn/wrapper/maven-wrapper.jar
vendored
Normal file
Binary file not shown.
2
r-binder/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
2
r-binder/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
66
r-binder/.settings.xml
Normal file
66
r-binder/.settings.xml
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings>
|
||||
<servers>
|
||||
<server>
|
||||
<id>repo.spring.io</id>
|
||||
<username>${env.CI_DEPLOY_USERNAME}</username>
|
||||
<password>${env.CI_DEPLOY_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
<profiles>
|
||||
<profile>
|
||||
<!--
|
||||
N.B. this profile is only here to support users and IDEs that do not use Maven 3.3.
|
||||
It isn't needed on the command line if you use the wrapper script (mvnw) or if you use
|
||||
a native Maven with the right version. Eclipse users should points their Maven tooling to
|
||||
this settings file, or copy the profile into their ~/.m2/settings.xml.
|
||||
-->
|
||||
<id>spring</id>
|
||||
<activation><activeByDefault>true</activeByDefault></activation>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</settings>
|
||||
201
r-binder/LICENSE
Normal file
201
r-binder/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://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
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
1301
r-binder/README.adoc
Normal file
1301
r-binder/README.adoc
Normal file
File diff suppressed because it is too large
Load Diff
2
r-binder/README.md
Normal file
2
r-binder/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# spring-cloud-stream-binder-rabbit
|
||||
Spring Cloud Stream Binder implementation for Rabbit
|
||||
42
r-binder/ci-docker-compose/docker-compose-RABBITMQ-stop.sh
Executable file
42
r-binder/ci-docker-compose/docker-compose-RABBITMQ-stop.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
LOCAL_HOST="${LOCAL_HOST:-localhost}"
|
||||
RETRIES="${RETRIES:-70}"
|
||||
|
||||
PORT_TO_CHECK=5672
|
||||
|
||||
WAIT_TIME="${WAIT_TIME:-5}"
|
||||
|
||||
function netcat_port() {
|
||||
local PASSED_HOST="${2:-$LOCAL_HOST}"
|
||||
local RABBITMQ_STOPPED=1
|
||||
local counter=1
|
||||
for i in $( seq 1 "${RETRIES}" ); do
|
||||
((counter++))
|
||||
if [ "${counter}" -gt 2 ]
|
||||
then
|
||||
echo "Rabbitmq is still running. Will try to stop again in [${WAIT_TIME}] seconds"
|
||||
fi
|
||||
sleep "${WAIT_TIME}"
|
||||
nc -v -w 1 ${PASSED_HOST} $1 && continue
|
||||
echo "Rabbitmq stopped..."
|
||||
RABBITMQ_STOPPED=0
|
||||
break
|
||||
done
|
||||
return ${RABBITMQ_STOPPED}
|
||||
}
|
||||
|
||||
export -f netcat_port
|
||||
|
||||
dockerComposeFile="docker-compose-RABBITMQ.yml"
|
||||
docker-compose -f $dockerComposeFile kill
|
||||
|
||||
RABBITMQ_STOPPED="no"
|
||||
|
||||
echo "Waiting for RabbitMQ to stop for [$(( WAIT_TIME * RETRIES ))] seconds"
|
||||
netcat_port $PORT_TO_CHECK && RABBITMQ_STOPPED="yes"
|
||||
|
||||
if [[ "${RABBITMQ_STOPPED}" == "no" ]] ; then
|
||||
echo "RabbitMQ failed to stop..."
|
||||
exit 1
|
||||
fi
|
||||
41
r-binder/ci-docker-compose/docker-compose-RABBITMQ.sh
Executable file
41
r-binder/ci-docker-compose/docker-compose-RABBITMQ.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
|
||||
LOCAL_HOST="${LOCAL_HOST:-localhost}"
|
||||
SHOULD_START_RABBIT="${SHOULD_START_RABBIT:-yes}"
|
||||
PORT_TO_CHECK=5672
|
||||
|
||||
WAIT_TIME="${WAIT_TIME:-5}"
|
||||
RETRIES="${RETRIES:-70}"
|
||||
|
||||
function netcat_port() {
|
||||
local PASSED_HOST="${2:-$LOCAL_HOST}"
|
||||
local READY_FOR_TESTS=1
|
||||
for i in $( seq 1 "${RETRIES}" ); do
|
||||
sleep "${WAIT_TIME}"
|
||||
nc -v -w 1 ${PASSED_HOST} $1 && READY_FOR_TESTS=0 && break
|
||||
echo "Fail #$i/${RETRIES}... will try again in [${WAIT_TIME}] seconds"
|
||||
done
|
||||
return ${READY_FOR_TESTS}
|
||||
}
|
||||
|
||||
export -f netcat_port
|
||||
|
||||
dockerComposeFile="docker-compose-RABBITMQ.yml"
|
||||
docker-compose -f $dockerComposeFile kill
|
||||
docker-compose -f $dockerComposeFile build
|
||||
|
||||
if [[ "${SHOULD_START_RABBIT}" == "yes" ]] ; then
|
||||
echo -e "\n\nBooting up RabbitMQ"
|
||||
docker-compose -f $dockerComposeFile up -d rabbitmq
|
||||
fi
|
||||
|
||||
READY_FOR_TESTS="no"
|
||||
|
||||
echo "Waiting for RabbitMQ to boot for [$(( WAIT_TIME * RETRIES ))] seconds"
|
||||
netcat_port $PORT_TO_CHECK && READY_FOR_TESTS="yes"
|
||||
|
||||
if [[ "${READY_FOR_TESTS}" == "no" ]] ; then
|
||||
echo "RabbitMQ failed to start..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
5
r-binder/ci-docker-compose/docker-compose-RABBITMQ.yml
Normal file
5
r-binder/ci-docker-compose/docker-compose-RABBITMQ.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
rabbitmq:
|
||||
image: rabbitmq:management
|
||||
ports:
|
||||
- 5672:5672
|
||||
- 15672:15672
|
||||
65
r-binder/docs/pom.xml
Normal file
65
r-binder/docs/pom.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-docs</artifactId>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-binder-rabbit-docs</name>
|
||||
<description>Spring Cloud Stream Rabbit Binder Docs</description>
|
||||
<properties>
|
||||
<docs.main>spring-cloud-stream-binder-rabbit</docs.main>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<maven.plugin.plugin.version>3.4</maven.plugin.plugin.version>
|
||||
<configprops.inclusionPattern>.*stream.*</configprops.inclusionPattern>
|
||||
<upload-docs-zip.phase>deploy</upload-docs-zip.phase>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<sourceDirectory>src/main/asciidoc</sourceDirectory>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>docs</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
2
r-binder/docs/src/main/asciidoc/.gitignore
vendored
Normal file
2
r-binder/docs/src/main/asciidoc/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.html
|
||||
*.css
|
||||
20
r-binder/docs/src/main/asciidoc/Guardfile
Normal file
20
r-binder/docs/src/main/asciidoc/Guardfile
Normal file
@@ -0,0 +1,20 @@
|
||||
require 'asciidoctor'
|
||||
require 'erb'
|
||||
|
||||
guard 'shell' do
|
||||
watch(/.*\.adoc$/) {|m|
|
||||
Asciidoctor.render_file('index.adoc', \
|
||||
:in_place => true, \
|
||||
:safe => Asciidoctor::SafeMode::UNSAFE, \
|
||||
:attributes=> { \
|
||||
'source-highlighter' => 'prettify', \
|
||||
'icons' => 'font', \
|
||||
'linkcss'=> 'true', \
|
||||
'copycss' => 'true', \
|
||||
'doctype' => 'book'})
|
||||
}
|
||||
end
|
||||
|
||||
guard 'livereload' do
|
||||
watch(%r{^.+\.(css|js|html)$})
|
||||
end
|
||||
22
r-binder/docs/src/main/asciidoc/README.adoc
Normal file
22
r-binder/docs/src/main/asciidoc/README.adoc
Normal file
@@ -0,0 +1,22 @@
|
||||
:jdkversion: 1.8
|
||||
:github-tag: master
|
||||
:github-repo: spring-cloud/spring-cloud-stream-binder-rabbit
|
||||
|
||||
:github-raw: https://raw.githubusercontent.com/{github-repo}/{github-tag}
|
||||
:github-code: https://github.com/{github-repo}/tree/{github-tag}
|
||||
|
||||
image::https://circleci.com/gh/spring-cloud/spring-cloud-stream-binder-rabbit.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-stream-binder-rabbit"]
|
||||
image::https://codecov.io/gh/spring-cloud/spring-cloud-stream-binder-rabbit/branch/{github-tag}/graph/badge.svg["codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-stream-binder-rabbit"]
|
||||
image::https://badges.gitter.im/spring-cloud/spring-cloud-stream-binder-rabbit.svg[Gitter, link="https://gitter.im/spring-cloud/spring-cloud-stream-binder-rabbit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"]
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
//= Overview
|
||||
include::overview.adoc[]
|
||||
|
||||
= Appendices
|
||||
[appendix]
|
||||
include::building.adoc[]
|
||||
include::contributing.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
38
r-binder/docs/src/main/asciidoc/_configprops.adoc
Normal file
38
r-binder/docs/src/main/asciidoc/_configprops.adoc
Normal file
@@ -0,0 +1,38 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|spring.cloud.stream.binders | | Additional per-binder properties (see {@link BinderProperties}) if more then one binder of the same type is used (i.e., connect to multiple instances of RabbitMq). Here you can specify multiple binder configurations, each with different environment settings. For example; spring.cloud.stream.binders.rabbit1.environment. . . , spring.cloud.stream.binders.rabbit2.environment. . .
|
||||
|spring.cloud.stream.binding-retry-interval | `30` | Retry interval (in seconds) used to schedule binding attempts. Default: 30 sec.
|
||||
|spring.cloud.stream.bindings | | Additional binding properties (see {@link BinderProperties}) per binding name (e.g., 'input`). For example; This sets the content-type for the 'input' binding of a Sink application: 'spring.cloud.stream.bindings.input.contentType=text/plain'
|
||||
|spring.cloud.stream.default-binder | | The name of the binder to use by all bindings in the event multiple binders available (e.g., 'rabbit').
|
||||
|spring.cloud.stream.dynamic-destination-cache-size | `10` | The maximum size of Least Recently Used (LRU) cache of dynamic destinations. Once this size is reached, new destinations will trigger the removal of old destinations. Default: 10
|
||||
|spring.cloud.stream.dynamic-destinations | `[]` | A list of destinations that can be bound dynamically. If set, only listed destinations can be bound.
|
||||
|spring.cloud.stream.function.batch-mode | `false` |
|
||||
|spring.cloud.stream.function.bindings | |
|
||||
|spring.cloud.stream.function.definition | | Definition of functions to bind. If several functions need to be composed into one, use pipes (e.g., 'fooFunc\|barFunc')
|
||||
|spring.cloud.stream.instance-count | `1` | The number of deployed instances of an application. Default: 1. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-count" where 'foo' is the name of the binding.
|
||||
|spring.cloud.stream.instance-index | `0` | The instance id of the application: a number from 0 to instanceCount-1. Used for partitioning and with Kafka. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-index" where 'foo' is the name of the binding.
|
||||
|spring.cloud.stream.instance-index-list | | A list of instance id's from 0 to instanceCount-1. Used for partitioning and with Kafka. NOTE: Could also be managed per individual binding "spring.cloud.stream.bindings.foo.consumer.instance-index-list" where 'foo' is the name of the binding. This setting will override the one set in 'spring.cloud.stream.instance-index'
|
||||
|spring.cloud.stream.integration.message-handler-not-propagated-headers | | Message header names that will NOT be copied from the inbound message.
|
||||
|spring.cloud.stream.metrics.export-properties | | List of properties that are going to be appended to each message. This gets populate by onApplicationEvent, once the context refreshes to avoid overhead of doing per message basis.
|
||||
|spring.cloud.stream.metrics.key | | The name of the metric being emitted. Should be an unique value per application. Defaults to: ${spring.application.name:${vcap.application.name:${spring.config.name:application}}}.
|
||||
|spring.cloud.stream.metrics.meter-filter | | Pattern to control the 'meters' one wants to capture. By default all 'meters' will be captured. For example, 'spring.integration.*' will only capture metric information for meters whose name starts with 'spring.integration'.
|
||||
|spring.cloud.stream.metrics.properties | | Application properties that should be added to the metrics payload For example: `spring.application**`.
|
||||
|spring.cloud.stream.metrics.schedule-interval | `60s` | Interval expressed as Duration for scheduling metrics snapshots publishing. Defaults to 60 seconds
|
||||
|spring.cloud.stream.override-cloud-connectors | `false` | This property is only applicable when the cloud profile is active and Spring Cloud Connectors are provided with the application. If the property is false (the default), the binder detects a suitable bound service (for example, a RabbitMQ service bound in Cloud Foundry for the RabbitMQ binder) and uses it for creating connections (usually through Spring Cloud Connectors). When set to true, this property instructs binders to completely ignore the bound services and rely on Spring Boot properties (for example, relying on the spring.rabbitmq.* properties provided in the environment for the RabbitMQ binder). The typical usage of this property is to be nested in a customized environment when connecting to multiple systems.
|
||||
|spring.cloud.stream.pollable-source | `none` | A semi-colon delimited list of binding names of pollable sources. Binding names follow the same naming convention as functions. For example, name '...pollable-source=foobar' will be accessible as 'foobar-iin-0'' binding
|
||||
|spring.cloud.stream.poller.cron | | Cron expression value for the Cron Trigger.
|
||||
|spring.cloud.stream.poller.fixed-delay | `1000` | Fixed delay for default poller.
|
||||
|spring.cloud.stream.poller.initial-delay | `0` | Initial delay for periodic triggers.
|
||||
|spring.cloud.stream.poller.max-messages-per-poll | `1` | Maximum messages per poll for the default poller.
|
||||
|spring.cloud.stream.poller.time-unit | | The TimeUnit to apply to delay values.
|
||||
|spring.cloud.stream.rabbit.binder.admin-addresses | `[]` | Urls for management plugins; only needed for queue affinity.
|
||||
|spring.cloud.stream.rabbit.binder.admin-adresses | |
|
||||
|spring.cloud.stream.rabbit.binder.compression-level | `0` | Compression level for compressed bindings; see 'java.util.zip.Deflator'.
|
||||
|spring.cloud.stream.rabbit.binder.connection-name-prefix | | Prefix for connection names from this binder.
|
||||
|spring.cloud.stream.rabbit.binder.nodes | `[]` | Cluster member node names; only needed for queue affinity.
|
||||
|spring.cloud.stream.rabbit.bindings | |
|
||||
|spring.cloud.stream.sendto.destination | `none` | The name of the header used to determine the name of the output destination
|
||||
|spring.cloud.stream.source | | A colon delimited string representing the names of the sources based on which source bindings will be created. This is primarily to support cases where source binding may be required without providing a corresponding Supplier. (e.g., for cases where the actual source of data is outside of scope of spring-cloud-stream - HTTP -> Stream)
|
||||
|
||||
|===
|
||||
5
r-binder/docs/src/main/asciidoc/appendix.adoc
Normal file
5
r-binder/docs/src/main/asciidoc/appendix.adoc
Normal file
@@ -0,0 +1,5 @@
|
||||
[[appendix]]
|
||||
= Appendices
|
||||
|
||||
|
||||
|
||||
83
r-binder/docs/src/main/asciidoc/building.adoc
Normal file
83
r-binder/docs/src/main/asciidoc/building.adoc
Normal file
@@ -0,0 +1,83 @@
|
||||
[[building]]
|
||||
== Building
|
||||
|
||||
:jdkversion: 1.8
|
||||
|
||||
=== Basic Compile and Test
|
||||
|
||||
To build the source you will need to install JDK {jdkversion}.
|
||||
|
||||
The build uses the Maven wrapper so you don't have to install a specific
|
||||
version of Maven. To enable the tests, you should have RabbitMQ server running
|
||||
on localhost and the default port (5672)
|
||||
before building.
|
||||
|
||||
The main build command is
|
||||
|
||||
----
|
||||
$ ./mvnw clean install
|
||||
----
|
||||
|
||||
You can also add '-DskipTests' if you like, to avoid running the tests.
|
||||
|
||||
NOTE: You can also install Maven (>=3.3.3) yourself and run the `mvn` command
|
||||
in place of `./mvnw` in the examples below. If you do that you also
|
||||
might need to add `-P spring` if your local Maven settings do not
|
||||
contain repository declarations for spring pre-release artifacts.
|
||||
|
||||
NOTE: Be aware that you might need to increase the amount of memory
|
||||
available to Maven by setting a `MAVEN_OPTS` environment variable with
|
||||
a value like `-Xmx512m -XX:MaxPermSize=128m`. We try to cover this in
|
||||
the `.mvn` configuration, so if you find you have to do it to make a
|
||||
build succeed, please raise a ticket to get the settings added to
|
||||
source control.
|
||||
|
||||
|
||||
The projects that require middleware generally include a
|
||||
`docker-compose.yml`, so consider using
|
||||
https://compose.docker.io/[Docker Compose] to run the middeware servers
|
||||
in Docker containers.
|
||||
|
||||
=== Documentation
|
||||
|
||||
There is a "docs" profile that will generate documentation.
|
||||
|
||||
`./mvnw clean package -Pdocs -DskipTests`
|
||||
|
||||
The reference documentation can then be found in `docs/target/contents/reference`.
|
||||
|
||||
=== Working with the code
|
||||
If you don't have an IDE preference we would recommend that you use
|
||||
https://www.springsource.com/developer/sts[Spring Tools Suite] or
|
||||
https://eclipse.org[Eclipse] when working with the code. We use the
|
||||
https://eclipse.org/m2e/[m2eclipe] eclipse plugin for maven support. Other IDEs and tools
|
||||
should also work without issue.
|
||||
|
||||
==== Importing into eclipse with m2eclipse
|
||||
We recommend the https://eclipse.org/m2e/[m2eclipe] eclipse plugin when working with
|
||||
eclipse. If you don't already have m2eclipse installed it is available from the "eclipse
|
||||
marketplace".
|
||||
|
||||
Unfortunately m2e does not yet support Maven 3.3, so once the projects
|
||||
are imported into Eclipse you will also need to tell m2eclipse to use
|
||||
the `.settings.xml` file for the projects. If you do not do this you
|
||||
may see many different errors related to the POMs in the
|
||||
projects. Open your Eclipse preferences, expand the Maven
|
||||
preferences, and select User Settings. In the User Settings field
|
||||
click Browse and navigate to the Spring Cloud project you imported
|
||||
selecting the `.settings.xml` file in that project. Click Apply and
|
||||
then OK to save the preference changes.
|
||||
|
||||
NOTE: Alternatively you can copy the repository settings from https://github.com/spring-cloud/spring-cloud-build/blob/master/.settings.xml[`.settings.xml`] into your own `~/.m2/settings.xml`.
|
||||
|
||||
==== Importing into eclipse without m2eclipse
|
||||
If you prefer not to use m2eclipse you can generate eclipse project metadata using the
|
||||
following command:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ ./mvnw eclipse:eclipse
|
||||
----
|
||||
|
||||
The generated eclipse projects can be imported by selecting `import existing projects`
|
||||
from the `file` menu.
|
||||
42
r-binder/docs/src/main/asciidoc/contributing.adoc
Normal file
42
r-binder/docs/src/main/asciidoc/contributing.adoc
Normal file
@@ -0,0 +1,42 @@
|
||||
[[contributing]]
|
||||
== Contributing
|
||||
|
||||
Spring Cloud is released under the non-restrictive Apache 2.0 license,
|
||||
and follows a very standard Github development process, using Github
|
||||
tracker for issues and merging pull requests into master. If you want
|
||||
to contribute even something trivial please do not hesitate, but
|
||||
follow the guidelines below.
|
||||
|
||||
=== Sign the Contributor License Agreement
|
||||
Before we accept a non-trivial patch or pull request we will need you to sign the
|
||||
https://support.springsource.com/spring_committer_signup[contributor's agreement].
|
||||
Signing the contributor's agreement does not grant anyone commit rights to the main
|
||||
repository, but it does mean that we can accept your contributions, and you will get an
|
||||
author credit if we do. Active contributors might be asked to join the core team, and
|
||||
given the ability to merge pull requests.
|
||||
|
||||
=== Code Conventions and Housekeeping
|
||||
None of these is essential for a pull request, but they will all help. They can also be
|
||||
added after the original pull request but before a merge.
|
||||
|
||||
* Use the Spring Framework code format conventions. If you use Eclipse
|
||||
you can import formatter settings using the
|
||||
`eclipse-code-formatter.xml` file from the
|
||||
https://github.com/spring-cloud/build/tree/master/eclipse-coding-conventions.xml[Spring
|
||||
Cloud Build] project. If using IntelliJ, you can use the
|
||||
https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter
|
||||
Plugin] to import the same file.
|
||||
* Make sure all new `.java` files to have a simple Javadoc class comment with at least an
|
||||
`@author` tag identifying you, and preferably at least a paragraph on what the class is
|
||||
for.
|
||||
* Add the ASF license header comment to all new `.java` files (copy from existing files
|
||||
in the project)
|
||||
* Add yourself as an `@author` to the .java files that you modify substantially (more
|
||||
than cosmetic changes).
|
||||
* Add some Javadocs and, if you change the namespace, some XSD doc elements.
|
||||
* A few unit tests would help a lot as well -- someone has to do it.
|
||||
* If no-one else is using your branch, please rebase it against the current master (or
|
||||
other target branch in the main project).
|
||||
* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions],
|
||||
if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
|
||||
message (where XXXX is the issue number).
|
||||
243
r-binder/docs/src/main/asciidoc/dlq.adoc
Normal file
243
r-binder/docs/src/main/asciidoc/dlq.adoc
Normal file
@@ -0,0 +1,243 @@
|
||||
[[rabbit-dlq-processing]]
|
||||
== Dead-Letter Queue Processing
|
||||
|
||||
Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them.
|
||||
If the reason for the dead-lettering is transient, you may wish to route the messages back to the original queue.
|
||||
However, if the problem is a permanent issue, that could cause an infinite loop.
|
||||
The following Spring Boot application shows an example of how to route those messages back to the original queue but moves them to a third "`parking lot`" queue after three attempts.
|
||||
The second example uses the https://www.rabbitmq.com/blog/2015/04/16/scheduling-messages-with-rabbitmq/[RabbitMQ Delayed Message Exchange] to introduce a delay to the re-queued message.
|
||||
In this example, the delay increases for each attempt.
|
||||
These examples use a `@RabbitListener` to receive messages from the DLQ.
|
||||
You could also use `RabbitTemplate.receive()` in a batch process.
|
||||
|
||||
The examples assume the original destination is `so8400in` and the consumer group is `so8400`.
|
||||
|
||||
=== Non-Partitioned Destinations
|
||||
|
||||
The first two examples are for when the destination is *not* partitioned:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class ReRouteDlqApplication {
|
||||
|
||||
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
|
||||
|
||||
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
|
||||
|
||||
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
|
||||
|
||||
private static final String X_RETRIES_HEADER = "x-retries";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
|
||||
System.out.println("Press enter to exit");
|
||||
System.in.read();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@RabbitListener(queues = DLQ)
|
||||
public void rePublish(Message failedMessage) {
|
||||
Integer retriesHeader = (Integer) failedMessage.getMessageProperties().getHeaders().get(X_RETRIES_HEADER);
|
||||
if (retriesHeader == null) {
|
||||
retriesHeader = Integer.valueOf(0);
|
||||
}
|
||||
if (retriesHeader < 3) {
|
||||
failedMessage.getMessageProperties().getHeaders().put(X_RETRIES_HEADER, retriesHeader + 1);
|
||||
this.rabbitTemplate.send(ORIGINAL_QUEUE, failedMessage);
|
||||
}
|
||||
else {
|
||||
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue parkingLot() {
|
||||
return new Queue(PARKING_LOT);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class ReRouteDlqApplication {
|
||||
|
||||
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
|
||||
|
||||
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
|
||||
|
||||
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
|
||||
|
||||
private static final String X_RETRIES_HEADER = "x-retries";
|
||||
|
||||
private static final String DELAY_EXCHANGE = "dlqReRouter";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
|
||||
System.out.println("Press enter to exit");
|
||||
System.in.read();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@RabbitListener(queues = DLQ)
|
||||
public void rePublish(Message failedMessage) {
|
||||
Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
|
||||
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
|
||||
if (retriesHeader == null) {
|
||||
retriesHeader = Integer.valueOf(0);
|
||||
}
|
||||
if (retriesHeader < 3) {
|
||||
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
|
||||
headers.put("x-delay", 5000 * retriesHeader);
|
||||
this.rabbitTemplate.send(DELAY_EXCHANGE, ORIGINAL_QUEUE, failedMessage);
|
||||
}
|
||||
else {
|
||||
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DirectExchange delayExchange() {
|
||||
DirectExchange exchange = new DirectExchange(DELAY_EXCHANGE);
|
||||
exchange.setDelayed(true);
|
||||
return exchange;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding bindOriginalToDelay() {
|
||||
return BindingBuilder.bind(new Queue(ORIGINAL_QUEUE)).to(delayExchange()).with(ORIGINAL_QUEUE);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue parkingLot() {
|
||||
return new Queue(PARKING_LOT);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
=== Partitioned Destinations
|
||||
|
||||
With partitioned destinations, there is one DLQ for all partitions. We determine the original queue from the headers.
|
||||
|
||||
==== `republishToDlq=false`
|
||||
|
||||
When `republishToDlq` is `false`, RabbitMQ publishes the message to the DLX/DLQ with an `x-death` header containing information about the original destination, as shown in the following example:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class ReRouteDlqApplication {
|
||||
|
||||
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
|
||||
|
||||
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
|
||||
|
||||
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
|
||||
|
||||
private static final String X_DEATH_HEADER = "x-death";
|
||||
|
||||
private static final String X_RETRIES_HEADER = "x-retries";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
|
||||
System.out.println("Press enter to exit");
|
||||
System.in.read();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RabbitListener(queues = DLQ)
|
||||
public void rePublish(Message failedMessage) {
|
||||
Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
|
||||
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
|
||||
if (retriesHeader == null) {
|
||||
retriesHeader = Integer.valueOf(0);
|
||||
}
|
||||
if (retriesHeader < 3) {
|
||||
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
|
||||
List<Map<String, ?>> xDeath = (List<Map<String, ?>>) headers.get(X_DEATH_HEADER);
|
||||
String exchange = (String) xDeath.get(0).get("exchange");
|
||||
List<String> routingKeys = (List<String>) xDeath.get(0).get("routing-keys");
|
||||
this.rabbitTemplate.send(exchange, routingKeys.get(0), failedMessage);
|
||||
}
|
||||
else {
|
||||
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue parkingLot() {
|
||||
return new Queue(PARKING_LOT);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
==== `republishToDlq=true`
|
||||
|
||||
When `republishToDlq` is `true`, the republishing recoverer adds the original exchange and routing key to headers, as shown in the following example:
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
public class ReRouteDlqApplication {
|
||||
|
||||
private static final String ORIGINAL_QUEUE = "so8400in.so8400";
|
||||
|
||||
private static final String DLQ = ORIGINAL_QUEUE + ".dlq";
|
||||
|
||||
private static final String PARKING_LOT = ORIGINAL_QUEUE + ".parkingLot";
|
||||
|
||||
private static final String X_RETRIES_HEADER = "x-retries";
|
||||
|
||||
private static final String X_ORIGINAL_EXCHANGE_HEADER = RepublishMessageRecoverer.X_ORIGINAL_EXCHANGE;
|
||||
|
||||
private static final String X_ORIGINAL_ROUTING_KEY_HEADER = RepublishMessageRecoverer.X_ORIGINAL_ROUTING_KEY;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(ReRouteDlqApplication.class, args);
|
||||
System.out.println("Press enter to exit");
|
||||
System.in.read();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@RabbitListener(queues = DLQ)
|
||||
public void rePublish(Message failedMessage) {
|
||||
Map<String, Object> headers = failedMessage.getMessageProperties().getHeaders();
|
||||
Integer retriesHeader = (Integer) headers.get(X_RETRIES_HEADER);
|
||||
if (retriesHeader == null) {
|
||||
retriesHeader = Integer.valueOf(0);
|
||||
}
|
||||
if (retriesHeader < 3) {
|
||||
headers.put(X_RETRIES_HEADER, retriesHeader + 1);
|
||||
String exchange = (String) headers.get(X_ORIGINAL_EXCHANGE_HEADER);
|
||||
String originalRoutingKey = (String) headers.get(X_ORIGINAL_ROUTING_KEY_HEADER);
|
||||
this.rabbitTemplate.send(exchange, originalRoutingKey, failedMessage);
|
||||
}
|
||||
else {
|
||||
this.rabbitTemplate.send(PARKING_LOT, failedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue parkingLot() {
|
||||
return new Queue(PARKING_LOT);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
330
r-binder/docs/src/main/asciidoc/ghpages.sh
Executable file
330
r-binder/docs/src/main/asciidoc/ghpages.sh
Executable file
@@ -0,0 +1,330 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
set -e
|
||||
|
||||
# Set default props like MAVEN_PATH, ROOT_FOLDER etc.
|
||||
function set_default_props() {
|
||||
# The script should be run from the root folder
|
||||
ROOT_FOLDER=`pwd`
|
||||
echo "Current folder is ${ROOT_FOLDER}"
|
||||
|
||||
if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then
|
||||
echo "You're not in the root folder of the project!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prop that will let commit the changes
|
||||
COMMIT_CHANGES="no"
|
||||
MAVEN_PATH=${MAVEN_PATH:-}
|
||||
echo "Path to Maven is [${MAVEN_PATH}]"
|
||||
REPO_NAME=${PWD##*/}
|
||||
echo "Repo name is [${REPO_NAME}]"
|
||||
SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git}
|
||||
echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}"
|
||||
}
|
||||
|
||||
# Check if gh-pages exists and docs have been built
|
||||
function check_if_anything_to_sync() {
|
||||
git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'`
|
||||
|
||||
if ! (git remote set-branches --add origin gh-pages && git fetch -q); then
|
||||
echo "No gh-pages, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then
|
||||
echo "No gh-pages sources in docs/target/generated-docs, so not syncing"
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
function retrieve_current_branch() {
|
||||
# Code getting the name of the current branch. For master we want to publish as we did until now
|
||||
# https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
|
||||
# If there is a branch already passed will reuse it - otherwise will try to find it
|
||||
CURRENT_BRANCH=${BRANCH}
|
||||
if [[ -z "${CURRENT_BRANCH}" ]] ; then
|
||||
CURRENT_BRANCH=$(git symbolic-ref -q HEAD)
|
||||
CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/}
|
||||
CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD}
|
||||
fi
|
||||
echo "Current branch is [${CURRENT_BRANCH}]"
|
||||
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
|
||||
}
|
||||
|
||||
# Switches to the provided value of the release version. We always prefix it with `v`
|
||||
function switch_to_tag() {
|
||||
git checkout v${VERSION}
|
||||
}
|
||||
|
||||
# Build the docs if switch is on
|
||||
function build_docs_if_applicable() {
|
||||
if [[ "${BUILD}" == "yes" ]] ; then
|
||||
./mvnw clean install -P docs -pl docs -DskipTests
|
||||
fi
|
||||
}
|
||||
|
||||
# Get the name of the `docs.main` property
|
||||
# Get allowed branches - assumes that a `docs` module is available under `docs` profile
|
||||
function retrieve_doc_properties() {
|
||||
MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \
|
||||
-Dexec.executable="echo" \
|
||||
-Dexec.args='${docs.main}' \
|
||||
--non-recursive \
|
||||
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec)
|
||||
echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]"
|
||||
|
||||
|
||||
ALLOW_PROPERTY=${ALLOW_PROPERTY:-"docs.allowed.branches"}
|
||||
ALLOWED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \
|
||||
-Dexec.executable="echo" \
|
||||
-Dexec.args="\${${ALLOW_PROPERTY}}" \
|
||||
org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \
|
||||
-P docs \
|
||||
-pl docs)
|
||||
echo "Extracted '${ALLOW_PROPERTY}' from Maven build [${ALLOWED_BRANCHES_VALUE}]"
|
||||
}
|
||||
|
||||
# Stash any outstanding changes
|
||||
function stash_changes() {
|
||||
git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1")
|
||||
if [ "$dirty" != "0" ]; then git stash; fi
|
||||
}
|
||||
|
||||
# Switch to gh-pages branch to sync it with current branch
|
||||
function add_docs_from_target() {
|
||||
local DESTINATION_REPO_FOLDER
|
||||
if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then
|
||||
DESTINATION_REPO_FOLDER=${ROOT_FOLDER}
|
||||
elif [[ "${CLONE}" == "yes" ]]; then
|
||||
mkdir -p ${ROOT_FOLDER}/target
|
||||
local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static
|
||||
if [[ ! -e "${clonedStatic}/.git" ]]; then
|
||||
echo "Cloning Spring Cloud Static to target"
|
||||
git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages
|
||||
else
|
||||
echo "Spring Cloud Static already cloned - will pull changes"
|
||||
cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages
|
||||
fi
|
||||
DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME}
|
||||
mkdir -p ${DESTINATION_REPO_FOLDER}
|
||||
else
|
||||
if [[ ! -e "${DESTINATION}/.git" ]]; then
|
||||
echo "[${DESTINATION}] is not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME}
|
||||
mkdir -p ${DESTINATION_REPO_FOLDER}
|
||||
echo "Destination was provided [${DESTINATION}]"
|
||||
fi
|
||||
cd ${DESTINATION_REPO_FOLDER}
|
||||
git checkout gh-pages
|
||||
git pull origin gh-pages
|
||||
|
||||
# Add git branches
|
||||
###################################################################
|
||||
if [[ -z "${VERSION}" ]] ; then
|
||||
copy_docs_for_current_version
|
||||
else
|
||||
copy_docs_for_provided_version
|
||||
fi
|
||||
commit_changes_if_applicable
|
||||
}
|
||||
|
||||
|
||||
# Copies the docs by using the retrieved properties from Maven build
|
||||
function copy_docs_for_current_version() {
|
||||
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then
|
||||
echo -e "Current branch is master - will copy the current docs only to the root folder"
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
# Not ignored...
|
||||
cp -rf $f ${ROOT_FOLDER}/
|
||||
git add -A ${ROOT_FOLDER}/$file
|
||||
fi
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
else
|
||||
echo -e "Current branch is [${CURRENT_BRANCH}]"
|
||||
# https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin
|
||||
if [[ ",${ALLOWED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then
|
||||
mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH}
|
||||
echo -e "Branch [${CURRENT_BRANCH}] is allowed! Will copy the current docs to the [${CURRENT_BRANCH}] folder"
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
# Not ignored...
|
||||
# We want users to access 2.0.0.BUILD-SNAPSHOT/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
|
||||
if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then
|
||||
# We don't want to copy the spring-cloud-sleuth.html
|
||||
# we want it to be converted to index.html
|
||||
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
|
||||
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html
|
||||
else
|
||||
cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}
|
||||
git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file
|
||||
fi
|
||||
fi
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
else
|
||||
echo -e "Branch [${CURRENT_BRANCH}] is not on the allow list! Check out the Maven [${ALLOW_PROPERTY}] property in
|
||||
[docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies the docs by using the explicitly provided version
|
||||
function copy_docs_for_provided_version() {
|
||||
local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION}
|
||||
mkdir -p ${FOLDER}
|
||||
echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder"
|
||||
for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do
|
||||
file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*}
|
||||
copy_docs_for_branch ${file} ${FOLDER}
|
||||
done
|
||||
COMMIT_CHANGES="yes"
|
||||
CURRENT_BRANCH="v${VERSION}"
|
||||
}
|
||||
|
||||
# Copies the docs from target to the provided destination
|
||||
# Params:
|
||||
# $1 - file from target
|
||||
# $2 - destination to which copy the files
|
||||
function copy_docs_for_branch() {
|
||||
local file=$1
|
||||
local destination=$2
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then
|
||||
# Not ignored...
|
||||
# We want users to access 2.0.0.BUILD-SNAPSHOT/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html
|
||||
if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then
|
||||
# We don't want to copy the spring-cloud-sleuth.html
|
||||
# we want it to be converted to index.html
|
||||
cp -rf $f ${destination}/index.html
|
||||
git add -A ${destination}/index.html
|
||||
else
|
||||
cp -rf $f ${destination}
|
||||
git add -A ${destination}/$file
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function commit_changes_if_applicable() {
|
||||
if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then
|
||||
COMMIT_SUCCESSFUL="no"
|
||||
git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes"
|
||||
|
||||
# Uncomment the following push if you want to auto push to
|
||||
# the gh-pages branch whenever you commit to master locally.
|
||||
# This is a little extreme. Use with care!
|
||||
###################################################################
|
||||
if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then
|
||||
git push origin gh-pages
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Switch back to the previous branch and exit block
|
||||
function checkout_previous_branch() {
|
||||
# If -version was provided we need to come back to root project
|
||||
cd ${ROOT_FOLDER}
|
||||
git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script"
|
||||
if [ "$dirty" != "0" ]; then git stash pop; fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Assert if properties have been properly passed
|
||||
function assert_properties() {
|
||||
echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]"
|
||||
if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi
|
||||
if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi
|
||||
if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi
|
||||
}
|
||||
|
||||
# Prints the usage
|
||||
function print_usage() {
|
||||
cat <<EOF
|
||||
The idea of this script is to update gh-pages branch with the generated docs. Without any options
|
||||
the script will work in the following manner:
|
||||
|
||||
- if there's no gh-pages / target for docs module then the script ends
|
||||
- for master branch the generated docs are copied to the root of gh-pages branch
|
||||
- for any other branch (if that branch is allowed) a subfolder with branch name is created
|
||||
and docs are copied there
|
||||
- if the version switch is passed (-v) then a tag with (v) prefix will be retrieved and a folder
|
||||
with that version number will be created in the gh-pages branch. WARNING! No allow verification will take place
|
||||
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
|
||||
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
|
||||
- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will
|
||||
switch to gh-pages of that repo and copy the generated docs to `docs/<project-name>/<version>`
|
||||
|
||||
USAGE:
|
||||
|
||||
You can use the following options:
|
||||
|
||||
-v|--version - the script will apply the whole procedure for a particular library version
|
||||
-d|--destination - the root of destination folder where the docs should be copied. You have to use the full path.
|
||||
E.g. point to spring-cloud-static folder. Can't be used with (-c)
|
||||
-b|--build - will run the standard build process after checking out the branch
|
||||
-c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination.
|
||||
Obviously can't be used with (-d)
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# ____ ____ _____ _____ _____ _______
|
||||
# / ____|/ ____| __ \|_ _| __ \__ __|
|
||||
# | (___ | | | |__) | | | | |__) | | |
|
||||
# \___ \| | | _ / | | | ___/ | |
|
||||
# ____) | |____| | \ \ _| |_| | | |
|
||||
# |_____/ \_____|_| \_\_____|_| |_|
|
||||
#
|
||||
# ==========================================
|
||||
|
||||
while [[ $# > 0 ]]
|
||||
do
|
||||
key="$1"
|
||||
case ${key} in
|
||||
-v|--version)
|
||||
VERSION="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
-d|--destination)
|
||||
DESTINATION="$2"
|
||||
shift # past argument
|
||||
;;
|
||||
-b|--build)
|
||||
BUILD="yes"
|
||||
;;
|
||||
-c|--clone)
|
||||
CLONE="yes"
|
||||
;;
|
||||
-h|--help)
|
||||
print_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option: [$1]"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift # past argument or value
|
||||
done
|
||||
|
||||
assert_properties
|
||||
set_default_props
|
||||
check_if_anything_to_sync
|
||||
if [[ -z "${VERSION}" ]] ; then
|
||||
retrieve_current_branch
|
||||
else
|
||||
switch_to_tag
|
||||
fi
|
||||
build_docs_if_applicable
|
||||
retrieve_doc_properties
|
||||
stash_changes
|
||||
add_docs_from_target
|
||||
checkout_previous_branch
|
||||
BIN
r-binder/docs/src/main/asciidoc/images/part-bindings.png
Normal file
BIN
r-binder/docs/src/main/asciidoc/images/part-bindings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
r-binder/docs/src/main/asciidoc/images/part-exchange.png
Normal file
BIN
r-binder/docs/src/main/asciidoc/images/part-exchange.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
r-binder/docs/src/main/asciidoc/images/part-queues.png
Normal file
BIN
r-binder/docs/src/main/asciidoc/images/part-queues.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
r-binder/docs/src/main/asciidoc/images/rabbit-binder.png
Executable file
BIN
r-binder/docs/src/main/asciidoc/images/rabbit-binder.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
14
r-binder/docs/src/main/asciidoc/index-docinfo.xml
Normal file
14
r-binder/docs/src/main/asciidoc/index-docinfo.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<productname>Spring Cloud Stream Rabbit Binder</productname>
|
||||
<releaseinfo>{spring-cloud-stream-binder-Rabbit-version}</releaseinfo>
|
||||
<copyright>
|
||||
<year>2013-2016</year>
|
||||
<holder>Pivotal Software, Inc.</holder>
|
||||
</copyright>
|
||||
<legalnotice>
|
||||
<para>
|
||||
Copies of this document may be made for your own use and for distribution to
|
||||
others, provided that you do not charge any fee for such copies and further
|
||||
provided that each copy contains this Copyright Notice, whether distributed in
|
||||
print or electronically.
|
||||
</para>
|
||||
</legalnotice>
|
||||
1266
r-binder/docs/src/main/asciidoc/overview.adoc
Normal file
1266
r-binder/docs/src/main/asciidoc/overview.adoc
Normal file
File diff suppressed because it is too large
Load Diff
125
r-binder/docs/src/main/asciidoc/partitions.adoc
Normal file
125
r-binder/docs/src/main/asciidoc/partitions.adoc
Normal file
@@ -0,0 +1,125 @@
|
||||
== Partitioning with the RabbitMQ Binder
|
||||
|
||||
RabbitMQ does not support partitioning natively.
|
||||
|
||||
Sometimes, it is advantageous to send data to specific partitions -- for example, when you want to strictly order message processing, all messages for a particular customer should go to the same partition.
|
||||
|
||||
The `RabbitMessageChannelBinder` provides partitioning by binding a queue for each partition to the destination exchange.
|
||||
|
||||
The following Java and YAML examples show how to configure the producer:
|
||||
|
||||
.Producer
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableBinding(Source.class)
|
||||
public class RabbitPartitionProducerApplication {
|
||||
|
||||
private static final Random RANDOM = new Random(System.currentTimeMillis());
|
||||
|
||||
private static final String[] data = new String[] {
|
||||
"abc1", "def1", "qux1",
|
||||
"abc2", "def2", "qux2",
|
||||
"abc3", "def3", "qux3",
|
||||
"abc4", "def4", "qux4",
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(RabbitPartitionProducerApplication.class)
|
||||
.web(false)
|
||||
.run(args);
|
||||
}
|
||||
|
||||
@InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000"))
|
||||
public Message<?> generate() {
|
||||
String value = data[RANDOM.nextInt(data.length)];
|
||||
System.out.println("Sending: " + value);
|
||||
return MessageBuilder.withPayload(value)
|
||||
.setHeader("partitionKey", value)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
.application.yml
|
||||
[source, yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
stream:
|
||||
bindings:
|
||||
output:
|
||||
destination: partitioned.destination
|
||||
producer:
|
||||
partitioned: true
|
||||
partition-key-expression: headers['partitionKey']
|
||||
partition-count: 2
|
||||
required-groups:
|
||||
- myGroup
|
||||
----
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The configuration in the prececing example uses the default partitioning (`key.hashCode() % partitionCount`).
|
||||
This may or may not provide a suitably balanced algorithm, depending on the key values.
|
||||
You can override this default by using the `partitionSelectorExpression` or `partitionSelectorClass` properties.
|
||||
|
||||
The `required-groups` property is required only if you need the consumer queues to be provisioned when the producer is deployed.
|
||||
Otherwise, any messages sent to a partition are lost until the corresponding consumer is deployed.
|
||||
====
|
||||
|
||||
The following configuration provisions a topic exchange:
|
||||
|
||||
image::part-exchange.png[scaledwidth="50%"]
|
||||
|
||||
The following queues are bound to that exchange:
|
||||
|
||||
image::part-queues.png[scaledwidth="50%"]
|
||||
|
||||
The following bindings associate the queues to the exchange:
|
||||
|
||||
image::part-bindings.png[scaledwidth="50%"]
|
||||
|
||||
The following Java and YAML examples continue the previous examples and show how to configure the consumer:
|
||||
|
||||
.Consumer
|
||||
[source, java]
|
||||
----
|
||||
@SpringBootApplication
|
||||
@EnableBinding(Sink.class)
|
||||
public class RabbitPartitionConsumerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(RabbitPartitionConsumerApplication.class)
|
||||
.web(false)
|
||||
.run(args);
|
||||
}
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void listen(@Payload String in, @Header(AmqpHeaders.CONSUMER_QUEUE) String queue) {
|
||||
System.out.println(in + " received from queue " + queue);
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
.application.yml
|
||||
[source, yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
stream:
|
||||
bindings:
|
||||
input:
|
||||
destination: partitioned.destination
|
||||
group: myGroup
|
||||
consumer:
|
||||
partitioned: true
|
||||
instance-index: 0
|
||||
----
|
||||
|
||||
IMPORTANT: The `RabbitMessageChannelBinder` does not support dynamic scaling.
|
||||
There must be at least one consumer per partition.
|
||||
The consumer's `instanceIndex` is used to indicate which partition is consumed.
|
||||
Platforms such as Cloud Foundry can have only one instance with an `instanceIndex`.
|
||||
@@ -0,0 +1,3 @@
|
||||
include::overview.adoc[leveloffset=+1]
|
||||
include::dlq.adoc[leveloffset=+1]
|
||||
include::partitions.adoc[leveloffset=+1]
|
||||
@@ -0,0 +1,53 @@
|
||||
:github-tag: master
|
||||
:github-repo: spring-cloud/spring-cloud-stream-binder-rabbit
|
||||
:github-raw: https://raw.githubusercontent.com/{github-repo}/{github-tag}
|
||||
:github-code: https://github.com/{github-repo}/tree/{github-tag}
|
||||
:toc: left
|
||||
:toclevels: 8
|
||||
:nofooter:
|
||||
:sectlinks: true
|
||||
|
||||
[[spring-cloud-stream-binder-rabbit-reference]]
|
||||
= Spring Cloud Stream RabbitMQ Binder Reference Guide
|
||||
Sabby Anandan, Marius Bogoevici, Eric Bottard, Mark Fisher, Ilayaperumal Gopinathan, Gunnar Hillert, Mark Pollack, Patrick Peralta, Glenn Renfro, Thomas Risberg, Dave Syer, David Turanski, Janne Valkealahti, Benjamin Klein, Gary Russell, Jay Bryant
|
||||
:doctype: book
|
||||
:toc:
|
||||
:toclevels: 4
|
||||
:source-highlighter: prettify
|
||||
:numbered:
|
||||
:icons: font
|
||||
:hide-uri-scheme:
|
||||
:spring-cloud-stream-binder-rabbit-repo: snapshot
|
||||
:github-tag: master
|
||||
:spring-cloud-stream-binder-rabbit-docs-version: current
|
||||
:spring-cloud-stream-binder-rabbit-docs: https://docs.spring.io/spring-cloud-stream-binder-rabbit/docs/{spring-cloud-stream-binder-rabbit-docs-version}/reference
|
||||
:spring-cloud-stream-binder-rabbit-docs-current: https://docs.spring.io/spring-cloud-stream-binder-rabbit/docs/current-SNAPSHOT/reference/html/
|
||||
:github-repo: spring-cloud/spring-cloud-stream-binder-rabbit
|
||||
:github-raw: https://raw.github.com/{github-repo}/{github-tag}
|
||||
:github-code: https://github.com/{github-repo}/tree/{github-tag}
|
||||
:github-wiki: https://github.com/{github-repo}/wiki
|
||||
:github-master-code: https://github.com/{github-repo}/tree/master
|
||||
:sc-ext: java
|
||||
|
||||
// ======================================================================================
|
||||
|
||||
*{project-version}*
|
||||
|
||||
|
||||
= Reference Guide
|
||||
|
||||
|
||||
include::overview.adoc[]
|
||||
|
||||
include::dlq.adoc[]
|
||||
|
||||
include::partitions.adoc[]
|
||||
|
||||
= Appendices
|
||||
[appendix]
|
||||
include::building.adoc[]
|
||||
|
||||
[appendix]
|
||||
include::contributing.adoc[]
|
||||
|
||||
// ======================================================================================
|
||||
37
r-binder/docs/src/main/ruby/generate_readme.sh
Executable file
37
r-binder/docs/src/main/ruby/generate_readme.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
base_dir = File.join(File.dirname(__FILE__),'../../..')
|
||||
src_dir = File.join(base_dir, "/src/main/asciidoc")
|
||||
require 'asciidoctor'
|
||||
require 'optparse'
|
||||
|
||||
options = {}
|
||||
file = "#{src_dir}/README.adoc"
|
||||
|
||||
OptionParser.new do |o|
|
||||
o.on('-o OUTPUT_FILE', 'Output file (default is stdout)') { |file| options[:to_file] = file unless file=='-' }
|
||||
o.on('-h', '--help') { puts o; exit }
|
||||
o.parse!
|
||||
end
|
||||
|
||||
file = ARGV[0] if ARGV.length>0
|
||||
|
||||
# Copied from https://github.com/asciidoctor/asciidoctor-extensions-lab/blob/master/scripts/asciidoc-coalescer.rb
|
||||
doc = Asciidoctor.load_file file, safe: :unsafe, header_only: true, attributes: options[:attributes]
|
||||
header_attr_names = (doc.instance_variable_get :@attributes_modified).to_a
|
||||
header_attr_names.each {|k| doc.attributes[%(#{k}!)] = '' unless doc.attr? k }
|
||||
attrs = doc.attributes
|
||||
attrs['allow-uri-read'] = true
|
||||
puts attrs
|
||||
|
||||
out = "// Do not edit this file (e.g. go instead to src/main/asciidoc)\n\n"
|
||||
doc = Asciidoctor.load_file file, safe: :unsafe, parse: false, attributes: attrs
|
||||
out << doc.reader.read
|
||||
|
||||
unless options[:to_file]
|
||||
puts out
|
||||
else
|
||||
File.open(options[:to_file],'w+') do |file|
|
||||
file.write(out)
|
||||
end
|
||||
end
|
||||
310
r-binder/mvnw
vendored
Executable file
310
r-binder/mvnw
vendored
Executable file
@@ -0,0 +1,310 @@
|
||||
#!/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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven 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
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
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 Mingw, 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)`"
|
||||
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
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# 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"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# 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} $MAVEN_CONFIG "$@"
|
||||
182
r-binder/mvnw.cmd
vendored
Normal file
182
r-binder/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
216
r-binder/pom.xml
Normal file
216
r-binder/pom.xml
Normal file
@@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<properties>
|
||||
<spring-cloud-stream.version>3.2.3-SNAPSHOT</spring-cloud-stream.version>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud-function.version>3.2.2</spring-cloud-function.version>
|
||||
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
|
||||
<maven-checkstyle-plugin.failsOnViolation>true
|
||||
</maven-checkstyle-plugin.failsOnViolation>
|
||||
<maven-checkstyle-plugin.includeTestSourceDirectory>true
|
||||
</maven-checkstyle-plugin.includeTestSourceDirectory>
|
||||
</properties>
|
||||
<modules>
|
||||
<module>spring-cloud-stream-binder-rabbit-core</module>
|
||||
<module>spring-cloud-stream-binder-rabbit</module>
|
||||
<module>spring-cloud-starter-stream-rabbit</module>
|
||||
<module>spring-cloud-stream-binder-rabbit-test-support</module>
|
||||
<module>docs</module>
|
||||
</modules>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-test</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<version>${spring-cloud-stream.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-test-support</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
<version>1.7</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<quiet>true</quiet>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<compilerArgument>-parameters</compilerArgument>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<reporting>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</reporting>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>rsocket-snapshots</id>
|
||||
<name>RSocket Snapshots</name>
|
||||
<url>https://oss.jfrog.org/oss-snapshot-local</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</project>
|
||||
25
r-binder/spring-cloud-starter-stream-rabbit/pom.xml
Normal file
25
r-binder/spring-cloud-starter-stream-rabbit/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<description>Spring Cloud Starter Stream Rabbit</description>
|
||||
<url>https://projects.spring.io/spring-cloud</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>https://www.spring.io</url>
|
||||
</organization>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/../..</main.basedir>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
53
r-binder/spring-cloud-stream-binder-rabbit-core/pom.xml
Normal file
53
r-binder/spring-cloud-stream-binder-rabbit-core/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-binder-rabbit-core</name>
|
||||
<description>RabbitMQ binder core</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.rabbitmq</groupId>
|
||||
<artifactId>http-client</artifactId>
|
||||
<version>2.1.0.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.admin;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.admin;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.rabbitmq.http.client.Client;
|
||||
import com.rabbitmq.http.client.domain.BindingInfo;
|
||||
import com.rabbitmq.http.client.domain.ExchangeInfo;
|
||||
import com.rabbitmq.http.client.domain.QueueInfo;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 = ".";
|
||||
|
||||
/**
|
||||
* Binder prefix.
|
||||
*/
|
||||
public static final String BINDER_PREFIX = "binder" + PREFIX_DELIMITER;
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> clean(String entity, boolean isJob) {
|
||||
return clean("http://localhost:15672/api", "guest", "guest", "/", BINDER_PREFIX,
|
||||
entity, isJob);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> clean(String adminUri, String user, String pw,
|
||||
String vhost, String binderPrefix, String entity, boolean isJob) {
|
||||
|
||||
try {
|
||||
Client client = new Client(adminUri, user, pw);
|
||||
return doClean(client,
|
||||
vhost == null ? "/" : vhost,
|
||||
binderPrefix == null ? BINDER_PREFIX : binderPrefix, entity, isJob);
|
||||
}
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new RabbitAdminException("Couldn't create a Client", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, List<String>> doClean(Client client,
|
||||
String vhost, String binderPrefix, String entity, boolean isJob) {
|
||||
|
||||
LinkedList<String> removedQueues = isJob ? null
|
||||
: findStreamQueues(client, vhost, binderPrefix, entity);
|
||||
List<String> removedExchanges = findExchanges(client, vhost, binderPrefix, entity);
|
||||
// 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.
|
||||
if (removedQueues != null) {
|
||||
removedQueues.descendingIterator().forEachRemaining(q -> {
|
||||
client.deleteQueue(vhost, q);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("deleted queue: " + q);
|
||||
}
|
||||
});
|
||||
}
|
||||
Map<String, List<String>> results = new HashMap<>();
|
||||
if (removedQueues.size() > 0) {
|
||||
results.put("queues", removedQueues);
|
||||
}
|
||||
// Fanout exchanges for taps
|
||||
removedExchanges.forEach(exchange -> {
|
||||
client.deleteExchange(vhost, exchange);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("deleted exchange: " + exchange);
|
||||
}
|
||||
});
|
||||
if (removedExchanges.size() > 0) {
|
||||
results.put("exchanges", removedExchanges);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private LinkedList<String> findStreamQueues(Client client, String vhost, String binderPrefix, String stream) {
|
||||
String queueNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, stream));
|
||||
List<QueueInfo> queues = client.getQueues(vhost);
|
||||
return queues.stream()
|
||||
.filter(q -> q.getName().startsWith(queueNamePrefix))
|
||||
.map(q -> checkNoConsumers(q))
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
|
||||
private String adjustPrefix(String prefix) {
|
||||
if (prefix.endsWith("*")) {
|
||||
return prefix.substring(0, prefix.length() - 1);
|
||||
}
|
||||
else {
|
||||
return prefix + PREFIX_DELIMITER;
|
||||
}
|
||||
}
|
||||
|
||||
private String checkNoConsumers(QueueInfo queue) {
|
||||
if (queue.getConsumerCount() != 0) {
|
||||
throw new RabbitAdminException("Queue " + queue.getName() + " is in use");
|
||||
}
|
||||
return queue.getName();
|
||||
}
|
||||
|
||||
private List<String> findExchanges(Client client, String vhost, String binderPrefix, String entity) {
|
||||
List<ExchangeInfo> exchanges = client.getExchanges(vhost);
|
||||
String exchangeNamePrefix = adjustPrefix(AbstractBinder.applyPrefix(binderPrefix, entity));
|
||||
List<String> exchangesToRemove = exchanges.stream()
|
||||
.filter(e -> e.getName().startsWith(exchangeNamePrefix))
|
||||
.map(e -> {
|
||||
System.out.println(e.getName());
|
||||
List<BindingInfo> bindingsBySource = client.getBindingsBySource(vhost, e.getName());
|
||||
return Collections.singletonMap(e.getName(), bindingsBySource);
|
||||
})
|
||||
.map(bindingsMap -> hasNoForeignBindings(bindingsMap, exchangeNamePrefix))
|
||||
.collect(Collectors.toList());
|
||||
exchangesToRemove.stream()
|
||||
.map(exchange -> client.getExchangeBindingsByDestination(vhost, exchange))
|
||||
.forEach(bindings -> {
|
||||
if (bindings.size() > 0) {
|
||||
throw new RabbitAdminException("Cannot delete exchange "
|
||||
+ bindings.get(0).getDestination() + "; it is a destination: " + bindings);
|
||||
}
|
||||
});
|
||||
return exchangesToRemove;
|
||||
}
|
||||
|
||||
private String hasNoForeignBindings(Map<String, List<BindingInfo>> bindings, String exchangeNamePrefix) {
|
||||
Entry<String, List<BindingInfo>> next = bindings.entrySet().iterator().next();
|
||||
for (BindingInfo binding : next.getValue()) {
|
||||
if (!"queue".equals(binding.getDestinationType())
|
||||
|| !binding.getDestination().startsWith(exchangeNamePrefix)) {
|
||||
throw new RabbitAdminException("Cannot delete exchange "
|
||||
+ next.getKey() + "; it has bindings: " + bindings);
|
||||
}
|
||||
}
|
||||
return next.getKey();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2015-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author David Turanski
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.stream.rabbit.binder")
|
||||
public class RabbitBinderConfigurationProperties {
|
||||
|
||||
/**
|
||||
* Urls for management plugins; only needed for queue affinity.
|
||||
*/
|
||||
private String[] adminAddresses = new String[0];
|
||||
|
||||
/**
|
||||
* Cluster member node names; only needed for queue affinity.
|
||||
*/
|
||||
private String[] nodes = new String[0];
|
||||
|
||||
/**
|
||||
* Compression level for compressed bindings; see 'java.util.zip.Deflator'.
|
||||
*/
|
||||
private int compressionLevel;
|
||||
|
||||
/**
|
||||
* Prefix for connection names from this binder.
|
||||
*/
|
||||
private String connectionNamePrefix;
|
||||
|
||||
public String[] getAdminAddresses() {
|
||||
return adminAddresses;
|
||||
}
|
||||
|
||||
public void setAdminAddresses(String[] adminAddresses) {
|
||||
this.adminAddresses = adminAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param adminAddresses A comma-separated list of RabbitMQ management plugin URLs.
|
||||
* @deprecated in favor of {@link #setAdminAddresses(String[])}. Will be removed in a
|
||||
* future release.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAdminAdresses(String[] adminAddresses) {
|
||||
setAdminAddresses(adminAddresses);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public String[] getAdminAdresses() {
|
||||
return this.adminAddresses;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getConnectionNamePrefix() {
|
||||
return this.connectionNamePrefix;
|
||||
}
|
||||
|
||||
public void setConnectionNamePrefix(String connectionNamePrefix) {
|
||||
this.connectionNamePrefix = connectionNamePrefix;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
public class RabbitBindingProperties implements BinderSpecificPropertiesProvider {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import org.springframework.amqp.core.ExchangeTypes;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Soby Chacko
|
||||
* @since 1.2
|
||||
*
|
||||
*/
|
||||
public abstract class RabbitCommonProperties {
|
||||
|
||||
/**
|
||||
* DLQ name prefix.
|
||||
*/
|
||||
public static final String DEAD_LETTER_EXCHANGE = "DLX";
|
||||
|
||||
/**
|
||||
* type of exchange to declare (if necessary, and declareExchange is true).
|
||||
*/
|
||||
private String exchangeType = ExchangeTypes.TOPIC;
|
||||
|
||||
/**
|
||||
* whether to declare the exchange.
|
||||
*/
|
||||
private boolean declareExchange = true;
|
||||
|
||||
/**
|
||||
* whether to declare the exchange as durable.
|
||||
*/
|
||||
private boolean exchangeDurable = true;
|
||||
|
||||
/**
|
||||
* whether to declare the exchange as auto-delete.
|
||||
*/
|
||||
private boolean exchangeAutoDelete = false;
|
||||
|
||||
/**
|
||||
* whether a delayed message exchange should be used.
|
||||
*/
|
||||
private boolean delayedExchange = false;
|
||||
|
||||
/**
|
||||
* set to true to name the queue with only the group; default is destination.group.
|
||||
*/
|
||||
private boolean queueNameGroupOnly = false;
|
||||
|
||||
/**
|
||||
* whether to bind a queue (or queues when partitioned) to the exchange.
|
||||
*/
|
||||
private boolean bindQueue = true;
|
||||
|
||||
/**
|
||||
* routing key to bind (default # for non-partitioned, destination-instanceIndex for
|
||||
* partitioned).
|
||||
*/
|
||||
private String bindingRoutingKey;
|
||||
|
||||
/**
|
||||
* when not null, treat 'bindingRoutingKey' as a delimited list of keys to bind.
|
||||
*/
|
||||
private String bindingRoutingKeyDelimiter;
|
||||
|
||||
/**
|
||||
* default time to live to apply to the queue when declared (ms).
|
||||
*/
|
||||
private Integer ttl;
|
||||
|
||||
/**
|
||||
* how long before an unused queue is deleted (ms).
|
||||
*/
|
||||
private Integer expires;
|
||||
|
||||
/**
|
||||
* maximum number of messages in the queue.
|
||||
*/
|
||||
private Integer maxLength;
|
||||
|
||||
/**
|
||||
* maximum number of total bytes in the queue from all messages.
|
||||
*/
|
||||
private Integer maxLengthBytes;
|
||||
|
||||
/**
|
||||
* maximum priority of messages in the queue (0-255).
|
||||
*/
|
||||
private Integer maxPriority;
|
||||
|
||||
/**
|
||||
* name of the DLQ - default is prefix+destination.dlq.
|
||||
*/
|
||||
private String deadLetterQueueName;
|
||||
|
||||
/**
|
||||
* a DLX to assign to the queue; if autoBindDlq is true, defaults to 'prefix+DLX'.
|
||||
*/
|
||||
private String deadLetterExchange;
|
||||
|
||||
/**
|
||||
* the type of the DLX, if autoBindDlq is true.
|
||||
*/
|
||||
private String deadLetterExchangeType = ExchangeTypes.DIRECT;
|
||||
|
||||
/**
|
||||
* whether to declare the dead-letter exchange when autoBindDlq is true.
|
||||
*/
|
||||
private boolean declareDlx = true;
|
||||
|
||||
/**
|
||||
* a dead letter routing key to assign to that queue; if autoBindDlq is true, defaults
|
||||
* to destination.
|
||||
*/
|
||||
private String deadLetterRoutingKey;
|
||||
|
||||
/**
|
||||
* default time to live to apply to the dead letter queue when declared (ms).
|
||||
*/
|
||||
private Integer dlqTtl;
|
||||
|
||||
/**
|
||||
* how long before an unused dead letter queue is deleted (ms).
|
||||
*/
|
||||
private Integer dlqExpires;
|
||||
|
||||
/**
|
||||
* maximum number of messages in the dead letter queue.
|
||||
*/
|
||||
private Integer dlqMaxLength;
|
||||
|
||||
/**
|
||||
* maximum number of total bytes in the dead letter queue from all messages.
|
||||
*/
|
||||
private Integer dlqMaxLengthBytes;
|
||||
|
||||
/**
|
||||
* maximum priority of messages in the dead letter queue (0-255).
|
||||
*/
|
||||
private Integer dlqMaxPriority;
|
||||
|
||||
/**
|
||||
* if a DLQ is declared, a DLX to assign to that queue; default none.
|
||||
*/
|
||||
private String dlqDeadLetterExchange;
|
||||
|
||||
/**
|
||||
* if a DLQ is declared, a dead letter routing key to assign to that queue; default
|
||||
* none.
|
||||
*/
|
||||
private String dlqDeadLetterRoutingKey;
|
||||
|
||||
/**
|
||||
* true to automatically bind a dead letter queue to a DLX.
|
||||
*/
|
||||
private boolean autoBindDlq;
|
||||
|
||||
/**
|
||||
* prefix for elements declared in RabbitMQ (exchanges, queues).
|
||||
*/
|
||||
private String prefix = "";
|
||||
|
||||
/**
|
||||
* true if the queue is provisioned as a lazy queue.
|
||||
*/
|
||||
private boolean lazy;
|
||||
|
||||
/**
|
||||
* true if the DLQ is provisioned as a lazy queue.
|
||||
*/
|
||||
private boolean dlqLazy;
|
||||
|
||||
/**
|
||||
* action when maxLength or maxLengthBytes is exceeded.
|
||||
*/
|
||||
private String overflowBehavior;
|
||||
|
||||
/**
|
||||
* action when maxLength or maxLengthBytes is exceeded.
|
||||
*/
|
||||
private String dlqOverflowBehavior;
|
||||
|
||||
/**
|
||||
* A map of binding arguments to apply when binding the queue to the exchange.
|
||||
* Useful for a headers exchange, for example.
|
||||
*/
|
||||
private Map<String, String> queueBindingArguments = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A map of binding arguments to apply when binding the dlq to the exchange.
|
||||
* Useful for a headers exchange, for example.
|
||||
*/
|
||||
private Map<String, String> dlqBindingArguments = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Configure the queue to be type quorum instead of classic.
|
||||
*/
|
||||
private QuorumConfig quorum = new QuorumConfig();
|
||||
|
||||
/**
|
||||
* Configure the DLQ to be type quorum instead of classic.
|
||||
*/
|
||||
private QuorumConfig dlqQuorum = new QuorumConfig();
|
||||
|
||||
/**
|
||||
* When true, set the 'x-single-active-consumer' queue argument to true.
|
||||
*/
|
||||
private boolean singleActiveConsumer;
|
||||
|
||||
/**
|
||||
* When true, set the 'x-single-active-consumer' queue argument to true.
|
||||
*/
|
||||
private boolean dlqSingleActiveConsumer;
|
||||
|
||||
/**
|
||||
* The bean name of a stream message converter to convert from a Spring AMQP Message
|
||||
* to a Stream Message.
|
||||
* @since 3.2
|
||||
*/
|
||||
private String streamStreamMessageConverterBeanName;
|
||||
|
||||
public String getExchangeType() {
|
||||
return this.exchangeType;
|
||||
}
|
||||
|
||||
public void setExchangeType(String exchangeType) {
|
||||
this.exchangeType = exchangeType;
|
||||
}
|
||||
|
||||
public boolean isDeclareExchange() {
|
||||
return this.declareExchange;
|
||||
}
|
||||
|
||||
public void setDeclareExchange(boolean declareExchange) {
|
||||
this.declareExchange = declareExchange;
|
||||
}
|
||||
|
||||
public boolean isExchangeDurable() {
|
||||
return this.exchangeDurable;
|
||||
}
|
||||
|
||||
public void setExchangeDurable(boolean exchangeDurable) {
|
||||
this.exchangeDurable = exchangeDurable;
|
||||
}
|
||||
|
||||
public boolean isExchangeAutoDelete() {
|
||||
return this.exchangeAutoDelete;
|
||||
}
|
||||
|
||||
public void setExchangeAutoDelete(boolean exchangeAutoDelete) {
|
||||
this.exchangeAutoDelete = exchangeAutoDelete;
|
||||
}
|
||||
|
||||
public boolean isDelayedExchange() {
|
||||
return this.delayedExchange;
|
||||
}
|
||||
|
||||
public void setDelayedExchange(boolean delayedExchange) {
|
||||
this.delayedExchange = delayedExchange;
|
||||
}
|
||||
|
||||
public boolean isQueueNameGroupOnly() {
|
||||
return this.queueNameGroupOnly;
|
||||
}
|
||||
|
||||
public void setQueueNameGroupOnly(boolean queueNameGroupOnly) {
|
||||
this.queueNameGroupOnly = queueNameGroupOnly;
|
||||
}
|
||||
|
||||
public boolean isBindQueue() {
|
||||
return this.bindQueue;
|
||||
}
|
||||
|
||||
public void setBindQueue(boolean bindQueue) {
|
||||
this.bindQueue = bindQueue;
|
||||
}
|
||||
|
||||
public String getBindingRoutingKey() {
|
||||
return this.bindingRoutingKey;
|
||||
}
|
||||
|
||||
public void setBindingRoutingKey(String routingKey) {
|
||||
this.bindingRoutingKey = routingKey;
|
||||
}
|
||||
|
||||
public String getBindingRoutingKeyDelimiter() {
|
||||
return this.bindingRoutingKeyDelimiter;
|
||||
}
|
||||
|
||||
public void setBindingRoutingKeyDelimiter(String bindingRoutingKeyDelimiter) {
|
||||
this.bindingRoutingKeyDelimiter = bindingRoutingKeyDelimiter;
|
||||
}
|
||||
|
||||
public Integer getTtl() {
|
||||
return this.ttl;
|
||||
}
|
||||
|
||||
public void setTtl(Integer ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public Integer getExpires() {
|
||||
return this.expires;
|
||||
}
|
||||
|
||||
public void setExpires(Integer expires) {
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
public Integer getMaxLength() {
|
||||
return this.maxLength;
|
||||
}
|
||||
|
||||
public void setMaxLength(Integer maxLength) {
|
||||
this.maxLength = maxLength;
|
||||
}
|
||||
|
||||
public Integer getMaxLengthBytes() {
|
||||
return this.maxLengthBytes;
|
||||
}
|
||||
|
||||
public void setMaxLengthBytes(Integer maxLengthBytes) {
|
||||
this.maxLengthBytes = maxLengthBytes;
|
||||
}
|
||||
|
||||
@Range(min = 0, max = 255)
|
||||
public Integer getMaxPriority() {
|
||||
return this.maxPriority;
|
||||
}
|
||||
|
||||
public void setMaxPriority(Integer maxPriority) {
|
||||
this.maxPriority = maxPriority;
|
||||
}
|
||||
|
||||
public String getDeadLetterQueueName() {
|
||||
return this.deadLetterQueueName;
|
||||
}
|
||||
|
||||
public void setDeadLetterQueueName(String deadLetterQueueName) {
|
||||
this.deadLetterQueueName = deadLetterQueueName;
|
||||
}
|
||||
|
||||
public String getDeadLetterExchange() {
|
||||
return this.deadLetterExchange;
|
||||
}
|
||||
|
||||
public void setDeadLetterExchange(String deadLetterExchange) {
|
||||
this.deadLetterExchange = deadLetterExchange;
|
||||
}
|
||||
|
||||
public String getDeadLetterExchangeType() {
|
||||
return this.deadLetterExchangeType;
|
||||
}
|
||||
|
||||
public void setDeadLetterExchangeType(String deadLetterExchangeType) {
|
||||
this.deadLetterExchangeType = deadLetterExchangeType;
|
||||
}
|
||||
|
||||
public boolean isDeclareDlx() {
|
||||
return this.declareDlx;
|
||||
}
|
||||
|
||||
public void setDeclareDlx(boolean declareDlx) {
|
||||
this.declareDlx = declareDlx;
|
||||
}
|
||||
|
||||
public String getDeadLetterRoutingKey() {
|
||||
return this.deadLetterRoutingKey;
|
||||
}
|
||||
|
||||
public void setDeadLetterRoutingKey(String deadLetterRoutingKey) {
|
||||
this.deadLetterRoutingKey = deadLetterRoutingKey;
|
||||
}
|
||||
|
||||
public Integer getDlqTtl() {
|
||||
return this.dlqTtl;
|
||||
}
|
||||
|
||||
public void setDlqTtl(Integer dlqTtl) {
|
||||
this.dlqTtl = dlqTtl;
|
||||
}
|
||||
|
||||
public Integer getDlqExpires() {
|
||||
return this.dlqExpires;
|
||||
}
|
||||
|
||||
public void setDlqExpires(Integer dlqExpires) {
|
||||
this.dlqExpires = dlqExpires;
|
||||
}
|
||||
|
||||
public Integer getDlqMaxLength() {
|
||||
return this.dlqMaxLength;
|
||||
}
|
||||
|
||||
public void setDlqMaxLength(Integer dlqMaxLength) {
|
||||
this.dlqMaxLength = dlqMaxLength;
|
||||
}
|
||||
|
||||
public Integer getDlqMaxLengthBytes() {
|
||||
return this.dlqMaxLengthBytes;
|
||||
}
|
||||
|
||||
public void setDlqMaxLengthBytes(Integer dlqMaxLengthBytes) {
|
||||
this.dlqMaxLengthBytes = dlqMaxLengthBytes;
|
||||
}
|
||||
|
||||
public Integer getDlqMaxPriority() {
|
||||
return this.dlqMaxPriority;
|
||||
}
|
||||
|
||||
public void setDlqMaxPriority(Integer dlqMaxPriority) {
|
||||
this.dlqMaxPriority = dlqMaxPriority;
|
||||
}
|
||||
|
||||
public String getDlqDeadLetterExchange() {
|
||||
return this.dlqDeadLetterExchange;
|
||||
}
|
||||
|
||||
public void setDlqDeadLetterExchange(String dlqDeadLetterExchange) {
|
||||
this.dlqDeadLetterExchange = dlqDeadLetterExchange;
|
||||
}
|
||||
|
||||
public String getDlqDeadLetterRoutingKey() {
|
||||
return this.dlqDeadLetterRoutingKey;
|
||||
}
|
||||
|
||||
public void setDlqDeadLetterRoutingKey(String dlqDeadLetterRoutingKey) {
|
||||
this.dlqDeadLetterRoutingKey = dlqDeadLetterRoutingKey;
|
||||
}
|
||||
|
||||
public boolean isAutoBindDlq() {
|
||||
return autoBindDlq;
|
||||
}
|
||||
|
||||
public void setAutoBindDlq(boolean autoBindDlq) {
|
||||
this.autoBindDlq = autoBindDlq;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public boolean isLazy() {
|
||||
return this.lazy;
|
||||
}
|
||||
|
||||
public void setLazy(boolean lazy) {
|
||||
this.lazy = lazy;
|
||||
}
|
||||
|
||||
public boolean isDlqLazy() {
|
||||
return this.dlqLazy;
|
||||
}
|
||||
|
||||
public void setDlqLazy(boolean dlqLazy) {
|
||||
this.dlqLazy = dlqLazy;
|
||||
}
|
||||
|
||||
public String getOverflowBehavior() {
|
||||
return this.overflowBehavior;
|
||||
}
|
||||
|
||||
public void setOverflowBehavior(String overflowBehavior) {
|
||||
this.overflowBehavior = overflowBehavior;
|
||||
}
|
||||
|
||||
public String getDlqOverflowBehavior() {
|
||||
return this.dlqOverflowBehavior;
|
||||
}
|
||||
|
||||
public void setDlqOverflowBehavior(String dlqOverflowBehavior) {
|
||||
this.dlqOverflowBehavior = dlqOverflowBehavior;
|
||||
}
|
||||
|
||||
public Map<String, String> getQueueBindingArguments() {
|
||||
return this.queueBindingArguments;
|
||||
}
|
||||
|
||||
public void setQueueBindingArguments(Map<String, String> queueBindingArguments) {
|
||||
this.queueBindingArguments = queueBindingArguments;
|
||||
}
|
||||
|
||||
public Map<String, String> getDlqBindingArguments() {
|
||||
return this.dlqBindingArguments;
|
||||
}
|
||||
|
||||
public void setDlqBindingArguments(Map<String, String> dlqBindingArguments) {
|
||||
this.dlqBindingArguments = dlqBindingArguments;
|
||||
}
|
||||
|
||||
public QuorumConfig getQuorum() {
|
||||
return this.quorum;
|
||||
}
|
||||
|
||||
public void setQuorum(QuorumConfig quorum) {
|
||||
this.quorum = quorum;
|
||||
}
|
||||
|
||||
public QuorumConfig getDlqQuorum() {
|
||||
return this.dlqQuorum;
|
||||
}
|
||||
|
||||
public void setDlqQuorum(QuorumConfig dlqQuorum) {
|
||||
this.dlqQuorum = dlqQuorum;
|
||||
}
|
||||
|
||||
public boolean isSingleActiveConsumer() {
|
||||
return this.singleActiveConsumer;
|
||||
}
|
||||
|
||||
public void setSingleActiveConsumer(boolean singleActiveConsumer) {
|
||||
this.singleActiveConsumer = singleActiveConsumer;
|
||||
}
|
||||
|
||||
public boolean isDlqSingleActiveConsumer() {
|
||||
return this.dlqSingleActiveConsumer;
|
||||
}
|
||||
|
||||
public void setDlqSingleActiveConsumer(boolean dlqSingleActiveConsumer) {
|
||||
this.dlqSingleActiveConsumer = dlqSingleActiveConsumer;
|
||||
}
|
||||
|
||||
public String getStreamStreamMessageConverterBeanName() {
|
||||
return this.streamStreamMessageConverterBeanName;
|
||||
}
|
||||
|
||||
public void setStreamStreamMessageConverterBeanName(String streamStreamMessageConverterBeanName) {
|
||||
this.streamStreamMessageConverterBeanName = streamStreamMessageConverterBeanName;
|
||||
}
|
||||
|
||||
public static class QuorumConfig {
|
||||
|
||||
private boolean enabled;
|
||||
|
||||
private Integer initialGroupSize;
|
||||
|
||||
private Integer deliveryLimit;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Integer getInitialGroupSize() {
|
||||
return this.initialGroupSize;
|
||||
}
|
||||
|
||||
public void setInitialGroupSize(Integer initialGroupSize) {
|
||||
this.initialGroupSize = initialGroupSize;
|
||||
}
|
||||
|
||||
public Integer getDeliveryLimit() {
|
||||
return this.deliveryLimit;
|
||||
}
|
||||
|
||||
public void setDeliveryLimit(Integer deliveryLimit) {
|
||||
this.deliveryLimit = deliveryLimit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright 2016-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import org.springframework.amqp.core.AcknowledgeMode;
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RabbitConsumerProperties extends RabbitCommonProperties {
|
||||
|
||||
/**
|
||||
* true to use transacted channels.
|
||||
*/
|
||||
private boolean transacted;
|
||||
|
||||
/**
|
||||
* container acknowledge mode.
|
||||
*/
|
||||
private AcknowledgeMode acknowledgeMode = AcknowledgeMode.AUTO;
|
||||
|
||||
/**
|
||||
* maxumum concurrency of this consumer (threads).
|
||||
*/
|
||||
private int maxConcurrency = 1;
|
||||
|
||||
/**
|
||||
* number of prefetched messages pre consumer thread.
|
||||
*/
|
||||
private int prefetch = 1;
|
||||
|
||||
/**
|
||||
* messages per acknowledgment (and commit when transacted).
|
||||
*/
|
||||
private int batchSize = 1;
|
||||
|
||||
/**
|
||||
* true for a durable subscription.
|
||||
*/
|
||||
private boolean durableSubscription = true;
|
||||
|
||||
/**
|
||||
* republish failures to the DLQ with diagnostic headers.
|
||||
*/
|
||||
private boolean republishToDlq = true;
|
||||
|
||||
/**
|
||||
* when republishing to the DLQ, the delivery mode to use.
|
||||
*/
|
||||
private MessageDeliveryMode republishDeliveyMode = MessageDeliveryMode.PERSISTENT;
|
||||
|
||||
/**
|
||||
* true to requeue rejected messages, false to discard (or route to DLQ).
|
||||
*/
|
||||
private boolean requeueRejected = false;
|
||||
|
||||
/**
|
||||
* patterns to match which headers are mapped (inbound).
|
||||
*/
|
||||
private String[] headerPatterns = new String[] { "*" };
|
||||
|
||||
/**
|
||||
* interval between reconnection attempts.
|
||||
*/
|
||||
private long recoveryInterval = 5000;
|
||||
|
||||
/**
|
||||
* true if the consumer is exclusive.
|
||||
*/
|
||||
private boolean exclusive;
|
||||
|
||||
/**
|
||||
* when true, stop the container instead of retrying queue declarations.
|
||||
*/
|
||||
private boolean missingQueuesFatal = false;
|
||||
|
||||
/**
|
||||
* how many times to attempt passive queue declaration.
|
||||
*/
|
||||
private Integer queueDeclarationRetries;
|
||||
|
||||
/**
|
||||
* interval between attempts to passively declare missing queues.
|
||||
*/
|
||||
private Long failedDeclarationRetryInterval;
|
||||
|
||||
/**
|
||||
* Used to create the consumer tags; will be appended by '#n' where 'n' increments for
|
||||
* each consumer created.
|
||||
*/
|
||||
private String consumerTagPrefix;
|
||||
|
||||
/**
|
||||
* Room to leave for other headers after adding the stack trace to a DLQ message.
|
||||
*/
|
||||
private int frameMaxHeadroom = 20_000;
|
||||
|
||||
/**
|
||||
* The container type, SIMPLE or DIRECT.
|
||||
*/
|
||||
private ContainerType containerType = ContainerType.SIMPLE;
|
||||
|
||||
/**
|
||||
* Prefix for anonymous queue names (when no group is provided).
|
||||
*/
|
||||
private String anonymousGroupPrefix = "anonymous.";
|
||||
|
||||
/**
|
||||
* When true, the listener container will assemble a list from multiple messages,
|
||||
* according to the batchSize and receiveTimeout properties. Only applies with
|
||||
* {@link ContainerType#SIMPLE}.
|
||||
*/
|
||||
private boolean enableBatching;
|
||||
|
||||
/**
|
||||
* How long to block waiting to receive messages; increasing from the default 1 second
|
||||
* will make the binding less responsive to stop requests. When enableConsumerBatching
|
||||
* is true, a short batch may be emitted if this time elapses before the batchSize is
|
||||
* satisfied. Only applies with {@link ContainerType#SIMPLE}.
|
||||
*/
|
||||
private Long receiveTimeout;
|
||||
|
||||
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(acknowledgeMode, "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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated - use {@link #getHeaderPatterns()}.
|
||||
* @return the header patterns.
|
||||
*/
|
||||
@Deprecated
|
||||
public String[] getRequestHeaderPatterns() {
|
||||
return this.headerPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated - use {@link #setHeaderPatterns(String[])}.
|
||||
* @param requestHeaderPatterns request header patterns
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
|
||||
this.headerPatterns = requestHeaderPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated in favor of {@link #getBatchSize()}
|
||||
* @return the tx size.
|
||||
*/
|
||||
@Deprecated
|
||||
@Min(value = 1, message = "Tx Size should be greater than zero.")
|
||||
public int getTxSize() {
|
||||
return getBatchSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* deprecated in favor of {@link #setBatchSize(int)}.
|
||||
* @param txSize the tx size
|
||||
*/
|
||||
public void setTxSize(int txSize) {
|
||||
setBatchSize(txSize);
|
||||
}
|
||||
|
||||
@Min(value = 1, message = "Batch Size should be greater than zero.")
|
||||
public int getBatchSize() {
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize) {
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
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 MessageDeliveryMode getRepublishDeliveyMode() {
|
||||
return this.republishDeliveyMode;
|
||||
}
|
||||
|
||||
public void setRepublishDeliveyMode(MessageDeliveryMode republishDeliveyMode) {
|
||||
this.republishDeliveyMode = republishDeliveyMode;
|
||||
}
|
||||
|
||||
public void setRequeueRejected(boolean requeueRejected) {
|
||||
this.requeueRejected = requeueRejected;
|
||||
}
|
||||
|
||||
public String[] getHeaderPatterns() {
|
||||
return headerPatterns;
|
||||
}
|
||||
|
||||
public void setHeaderPatterns(String[] replyHeaderPatterns) {
|
||||
this.headerPatterns = replyHeaderPatterns;
|
||||
}
|
||||
|
||||
public long getRecoveryInterval() {
|
||||
return recoveryInterval;
|
||||
}
|
||||
|
||||
public void setRecoveryInterval(long recoveryInterval) {
|
||||
this.recoveryInterval = recoveryInterval;
|
||||
}
|
||||
|
||||
public boolean isExclusive() {
|
||||
return this.exclusive;
|
||||
}
|
||||
|
||||
public void setExclusive(boolean exclusive) {
|
||||
this.exclusive = exclusive;
|
||||
}
|
||||
|
||||
public boolean getMissingQueuesFatal() {
|
||||
return this.missingQueuesFatal;
|
||||
}
|
||||
|
||||
public void setMissingQueuesFatal(boolean missingQueuesFatal) {
|
||||
this.missingQueuesFatal = missingQueuesFatal;
|
||||
}
|
||||
|
||||
public Integer getQueueDeclarationRetries() {
|
||||
return this.queueDeclarationRetries;
|
||||
}
|
||||
|
||||
public void setQueueDeclarationRetries(Integer queueDeclarationRetries) {
|
||||
this.queueDeclarationRetries = queueDeclarationRetries;
|
||||
}
|
||||
|
||||
public Long getFailedDeclarationRetryInterval() {
|
||||
return this.failedDeclarationRetryInterval;
|
||||
}
|
||||
|
||||
public void setFailedDeclarationRetryInterval(Long failedDeclarationRetryInterval) {
|
||||
this.failedDeclarationRetryInterval = failedDeclarationRetryInterval;
|
||||
}
|
||||
|
||||
public String getConsumerTagPrefix() {
|
||||
return this.consumerTagPrefix;
|
||||
}
|
||||
|
||||
public void setConsumerTagPrefix(String consumerTagPrefix) {
|
||||
this.consumerTagPrefix = consumerTagPrefix;
|
||||
}
|
||||
|
||||
public int getFrameMaxHeadroom() {
|
||||
return this.frameMaxHeadroom;
|
||||
}
|
||||
|
||||
public void setFrameMaxHeadroom(int frameMaxHeadroom) {
|
||||
this.frameMaxHeadroom = frameMaxHeadroom;
|
||||
}
|
||||
|
||||
public ContainerType getContainerType() {
|
||||
return this.containerType;
|
||||
}
|
||||
|
||||
public void setContainerType(ContainerType containerType) {
|
||||
this.containerType = containerType;
|
||||
}
|
||||
|
||||
public String getAnonymousGroupPrefix() {
|
||||
return this.anonymousGroupPrefix;
|
||||
}
|
||||
|
||||
public void setAnonymousGroupPrefix(String anonymousGroupPrefix) {
|
||||
this.anonymousGroupPrefix = anonymousGroupPrefix;
|
||||
}
|
||||
|
||||
public boolean isEnableBatching() {
|
||||
return this.enableBatching;
|
||||
}
|
||||
|
||||
public void setEnableBatching(boolean enableBatching) {
|
||||
this.enableBatching = enableBatching;
|
||||
}
|
||||
|
||||
public Long getReceiveTimeout() {
|
||||
return this.receiveTimeout;
|
||||
}
|
||||
|
||||
public void setReceiveTimeout(Long receiveTimeout) {
|
||||
this.receiveTimeout = receiveTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container type.
|
||||
* @author Gary Russell
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public enum ContainerType {
|
||||
|
||||
/**
|
||||
* Container where the RabbitMQ consumer dispatches messages to an invoker thread.
|
||||
*/
|
||||
SIMPLE,
|
||||
|
||||
/**
|
||||
* Container where the listener is invoked directly on the RabbitMQ consumer
|
||||
* thread.
|
||||
*/
|
||||
DIRECT,
|
||||
|
||||
/**
|
||||
* Container that uses the RabbitMQ Stream Client.
|
||||
*/
|
||||
STREAM
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2016-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.AbstractExtendedBindingProperties;
|
||||
import org.springframework.cloud.stream.binder.BinderSpecificPropertiesProvider;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.stream.rabbit")
|
||||
public class RabbitExtendedBindingProperties extends
|
||||
AbstractExtendedBindingProperties<RabbitConsumerProperties, RabbitProducerProperties, RabbitBindingProperties> {
|
||||
|
||||
private static final String DEFAULTS_PREFIX = "spring.cloud.stream.rabbit.default";
|
||||
|
||||
@Override
|
||||
public String getDefaultsPrefix() {
|
||||
return DEFAULTS_PREFIX;
|
||||
}
|
||||
|
||||
public Map<String, RabbitBindingProperties> getBindings() {
|
||||
return this.doGetBindings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BinderSpecificPropertiesProvider> getExtendedPropertiesEntryClass() {
|
||||
return RabbitBindingProperties.class;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright 2016-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.properties;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
|
||||
import org.springframework.amqp.core.MessageDeliveryMode;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RabbitProducerProperties extends RabbitCommonProperties {
|
||||
|
||||
/**
|
||||
* Determines the producer type.
|
||||
* @since 3.2
|
||||
*/
|
||||
public enum ProducerType {
|
||||
|
||||
/**
|
||||
* RabbitMQ Stream producer - blocks until confirm received.
|
||||
*/
|
||||
STREAM_SYNC,
|
||||
|
||||
/**
|
||||
* RabbitMQ Stream producer - does not block.
|
||||
*/
|
||||
STREAM_ASYNC,
|
||||
|
||||
/**
|
||||
* Classic AMQP producer.
|
||||
*/
|
||||
AMQP
|
||||
}
|
||||
|
||||
/**
|
||||
* true to compress messages.
|
||||
*/
|
||||
private boolean compress;
|
||||
|
||||
/**
|
||||
* true to batch multiple messages into one.
|
||||
*/
|
||||
private boolean batchingEnabled;
|
||||
|
||||
/**
|
||||
* the number of messages to batch, when enabled.
|
||||
*/
|
||||
private int batchSize = 100;
|
||||
|
||||
/**
|
||||
* the size limit for batched messages.
|
||||
*/
|
||||
private int batchBufferLimit = 10000;
|
||||
|
||||
/**
|
||||
* the time after which an incomplete batch will be sent.
|
||||
*/
|
||||
private int batchTimeout = 5000;
|
||||
|
||||
/**
|
||||
* the bean name of a custom batching strategy to use instead of the
|
||||
* {@link org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy}.
|
||||
*/
|
||||
private String batchingStrategyBeanName;
|
||||
|
||||
/**
|
||||
* true to use transacted channels.
|
||||
*/
|
||||
private boolean transacted;
|
||||
|
||||
/**
|
||||
* the delivery mode for published messages.
|
||||
*/
|
||||
private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT;
|
||||
|
||||
/**
|
||||
* patterns to match which headers are mapped (inbound).
|
||||
*/
|
||||
private String[] headerPatterns = new String[] { "*" };
|
||||
|
||||
/**
|
||||
* when using a delayed message exchange, a SpEL expression to determine the delay to
|
||||
* apply to messages.
|
||||
*/
|
||||
private Expression delayExpression;
|
||||
|
||||
/**
|
||||
* a static routing key when publishing messages; default is the destination name;
|
||||
* suffixed by "-partition" when partitioned. This is only used if `routingKeyExpression` is null
|
||||
*/
|
||||
private String routingKey;
|
||||
|
||||
/**
|
||||
* a custom routing key when publishing messages; default is the destination name;
|
||||
* suffixed by "-partition" when partitioned.
|
||||
*/
|
||||
private Expression routingKeyExpression;
|
||||
|
||||
/**
|
||||
* the channel name to which to send publisher confirms (acks) if the connection
|
||||
* factory is so configured; default 'nullChannel'; requires
|
||||
* 'errorChannelEnabled=true'.
|
||||
*/
|
||||
private String confirmAckChannel;
|
||||
|
||||
/**
|
||||
* When true, the binding will complete the {@link java.util.concurrent.Future} field
|
||||
* in a {@link org.springframework.amqp.rabbit.connection.CorrelationData} contained
|
||||
* in the
|
||||
* {@link org.springframework.amqp.support.AmqpHeaders#PUBLISH_CONFIRM_CORRELATION}
|
||||
* header when the confirmation is received.
|
||||
*/
|
||||
private boolean useConfirmHeader;
|
||||
|
||||
/**
|
||||
* When STREAM_SYNC or STREAM_ASYNC, create a RabbitMQ Stream producer instead of an
|
||||
* AMQP producer.
|
||||
* @since 3.2
|
||||
*/
|
||||
private ProducerType producerType = ProducerType.AMQP;
|
||||
|
||||
/**
|
||||
* The bean name of a message converter to convert from spring-messaging Message to
|
||||
* a Spring AMQP Message.
|
||||
* @since 3.2
|
||||
*/
|
||||
private String streamMessageConverterBeanName;
|
||||
|
||||
/**
|
||||
* @deprecated - use {@link #setHeaderPatterns(String[])}.
|
||||
* @param requestHeaderPatterns the patterns.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRequestHeaderPatterns(String[] requestHeaderPatterns) {
|
||||
this.headerPatterns = requestHeaderPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated - use {@link #getHeaderPatterns()}.
|
||||
* @return the header patterns.
|
||||
*/
|
||||
@Deprecated
|
||||
public String[] getRequestHeaderPatterns() {
|
||||
return this.headerPatterns;
|
||||
}
|
||||
|
||||
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[] getHeaderPatterns() {
|
||||
return headerPatterns;
|
||||
}
|
||||
|
||||
public void setHeaderPatterns(String[] replyHeaderPatterns) {
|
||||
this.headerPatterns = 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;
|
||||
}
|
||||
|
||||
public Expression getDelayExpression() {
|
||||
return this.delayExpression;
|
||||
}
|
||||
|
||||
public void setDelayExpression(Expression delayExpression) {
|
||||
this.delayExpression = delayExpression;
|
||||
}
|
||||
|
||||
public Expression getRoutingKeyExpression() {
|
||||
return this.routingKeyExpression;
|
||||
}
|
||||
|
||||
public void setRoutingKeyExpression(Expression routingKeyExpression) {
|
||||
this.routingKeyExpression = routingKeyExpression;
|
||||
}
|
||||
|
||||
public String getRoutingKey() {
|
||||
return this.routingKey;
|
||||
}
|
||||
|
||||
public void setRoutingKey(String routingKey) {
|
||||
this.routingKey = routingKey;
|
||||
}
|
||||
|
||||
public String getConfirmAckChannel() {
|
||||
return this.confirmAckChannel;
|
||||
}
|
||||
|
||||
public void setConfirmAckChannel(String confirmAckChannel) {
|
||||
this.confirmAckChannel = confirmAckChannel;
|
||||
}
|
||||
|
||||
public String getBatchingStrategyBeanName() {
|
||||
return batchingStrategyBeanName;
|
||||
}
|
||||
|
||||
public void setBatchingStrategyBeanName(String batchingStrategyBeanName) {
|
||||
this.batchingStrategyBeanName = batchingStrategyBeanName;
|
||||
}
|
||||
|
||||
public boolean isUseConfirmHeader() {
|
||||
return this.useConfirmHeader;
|
||||
}
|
||||
|
||||
public void setUseConfirmHeader(boolean useConfirmHeader) {
|
||||
this.useConfirmHeader = useConfirmHeader;
|
||||
}
|
||||
|
||||
public ProducerType getProducerType() {
|
||||
return this.producerType;
|
||||
}
|
||||
|
||||
public void setProducerType(ProducerType producerType) {
|
||||
Assert.notNull(producerType, "'producerType' cannot be null");
|
||||
this.producerType = producerType;
|
||||
}
|
||||
|
||||
public String getStreamMessageConverterBeanName() {
|
||||
return this.streamMessageConverterBeanName;
|
||||
}
|
||||
|
||||
public void setStreamMessageConverterBeanName(String streamMessageConverterBeanName) {
|
||||
this.streamMessageConverterBeanName = streamMessageConverterBeanName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
/*
|
||||
* Copyright 2016-2022 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.provisioning;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.amqp.AmqpConnectException;
|
||||
import org.springframework.amqp.core.AnonymousQueue;
|
||||
import org.springframework.amqp.core.Base64UrlNamingStrategy;
|
||||
import org.springframework.amqp.core.Binding;
|
||||
import org.springframework.amqp.core.Binding.DestinationType;
|
||||
import org.springframework.amqp.core.BindingBuilder;
|
||||
import org.springframework.amqp.core.DeclarableCustomizer;
|
||||
import org.springframework.amqp.core.DirectExchange;
|
||||
import org.springframework.amqp.core.Exchange;
|
||||
import org.springframework.amqp.core.ExchangeBuilder;
|
||||
import org.springframework.amqp.core.FanoutExchange;
|
||||
import org.springframework.amqp.core.HeadersExchange;
|
||||
import org.springframework.amqp.core.Queue;
|
||||
import org.springframework.amqp.core.TopicExchange;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.DeclarationExceptionEvent;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitCommonProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitCommonProperties.QuorumConfig;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties.ContainerType;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
|
||||
import org.springframework.cloud.stream.provisioning.ProducerDestination;
|
||||
import org.springframework.cloud.stream.provisioning.ProvisioningException;
|
||||
import org.springframework.cloud.stream.provisioning.ProvisioningProvider;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* AMQP implementation for {@link ProvisioningProvider}.
|
||||
*
|
||||
* @author Soby Chacko
|
||||
* @author Gary Russell
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Michael Michailidis
|
||||
*/
|
||||
// @checkstyle:off
|
||||
public class RabbitExchangeQueueProvisioner
|
||||
implements ApplicationListener<DeclarationExceptionEvent>,
|
||||
ProvisioningProvider<ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> {
|
||||
|
||||
// @checkstyle:on
|
||||
|
||||
/**
|
||||
* The delimiter between a group and index when constructing a binder
|
||||
* consumer/producer.
|
||||
*/
|
||||
private static final String GROUP_INDEX_DELIMITER = ".";
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private boolean notOurAdminException;
|
||||
|
||||
private final GenericApplicationContext autoDeclareContext = new GenericApplicationContext();
|
||||
|
||||
private final List<DeclarableCustomizer> customizers;
|
||||
|
||||
private final AtomicInteger producerExchangeBeanNameQualifier = new AtomicInteger();
|
||||
|
||||
public RabbitExchangeQueueProvisioner(ConnectionFactory connectionFactory) {
|
||||
this(connectionFactory, Collections.emptyList());
|
||||
}
|
||||
|
||||
public RabbitExchangeQueueProvisioner(ConnectionFactory connectionFactory,
|
||||
List<DeclarableCustomizer> customizers) {
|
||||
|
||||
this.rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
this.autoDeclareContext.refresh();
|
||||
this.rabbitAdmin.setApplicationContext(this.autoDeclareContext);
|
||||
this.rabbitAdmin.afterPropertiesSet();
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProducerDestination provisionProducerDestination(String name,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> producerProperties) {
|
||||
final String exchangeName = applyPrefix(
|
||||
producerProperties.getExtension().getPrefix(), name);
|
||||
Exchange exchange = buildExchange(producerProperties.getExtension(),
|
||||
exchangeName);
|
||||
String beanNameQualifier = "prod" + this.producerExchangeBeanNameQualifier.incrementAndGet();
|
||||
if (producerProperties.getExtension().isDeclareExchange()) {
|
||||
declareExchange(exchangeName, beanNameQualifier, exchange);
|
||||
}
|
||||
Binding binding = null;
|
||||
for (String requiredGroupName : producerProperties.getRequiredGroups()) {
|
||||
String baseQueueName = producerProperties.getExtension()
|
||||
.isQueueNameGroupOnly() ? requiredGroupName
|
||||
: (exchangeName + "." + requiredGroupName);
|
||||
if (!producerProperties.isPartitioned()) {
|
||||
autoBindDLQ(baseQueueName, baseQueueName, requiredGroupName,
|
||||
producerProperties.getExtension());
|
||||
if (producerProperties.getExtension().isBindQueue()) {
|
||||
Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(
|
||||
baseQueueName, producerProperties.getExtension(), false));
|
||||
declareQueue(baseQueueName, queue);
|
||||
String[] routingKeys = bindingRoutingKeys(producerProperties.getExtension());
|
||||
if (ObjectUtils.isEmpty(routingKeys)) {
|
||||
binding = notPartitionedBinding(exchange, queue, null, producerProperties.getExtension());
|
||||
}
|
||||
else {
|
||||
for (String routingKey : routingKeys) {
|
||||
binding = notPartitionedBinding(exchange, queue, routingKey,
|
||||
producerProperties.getExtension());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if the stream is partitioned, create one queue for each target
|
||||
// partition for the default group
|
||||
for (int i = 0; i < producerProperties.getPartitionCount(); i++) {
|
||||
String partitionSuffix = "-" + i;
|
||||
String partitionQueueName = baseQueueName + partitionSuffix;
|
||||
autoBindDLQ(baseQueueName, baseQueueName + partitionSuffix, requiredGroupName,
|
||||
producerProperties.getExtension());
|
||||
if (producerProperties.getExtension().isBindQueue()) {
|
||||
Queue queue = new Queue(partitionQueueName, true, false, false,
|
||||
queueArgs(partitionQueueName,
|
||||
producerProperties.getExtension(), false));
|
||||
declareQueue(queue.getName(), queue);
|
||||
String prefix = producerProperties.getExtension().getPrefix();
|
||||
String destination = StringUtils.isEmpty(prefix) ? exchangeName
|
||||
: exchangeName.substring(prefix.length());
|
||||
String[] routingKeys = bindingRoutingKeys(producerProperties.getExtension());
|
||||
if (ObjectUtils.isEmpty(routingKeys)) {
|
||||
binding = partitionedBinding(destination, exchange, queue, null,
|
||||
producerProperties.getExtension(), i);
|
||||
}
|
||||
else {
|
||||
for (String routingKey : routingKeys) {
|
||||
binding = partitionedBinding(destination, exchange, queue, routingKey,
|
||||
producerProperties.getExtension(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new RabbitProducerDestination(exchange, binding, beanNameQualifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConsumerDestination provisionConsumerDestination(String name, String group,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
ConsumerDestination consumerDestination;
|
||||
if (!properties.isMultiplex()) {
|
||||
consumerDestination = doProvisionConsumerDestination(name, group, properties);
|
||||
}
|
||||
else {
|
||||
String[] provisionedDestinations = Stream
|
||||
.of(StringUtils.tokenizeToStringArray(name, ",", true, true))
|
||||
.flatMap(destination -> {
|
||||
if (properties.isPartitioned() && !ObjectUtils.isEmpty(properties.getInstanceIndexList())) {
|
||||
List<String> consumerDestinationNames = new ArrayList<>();
|
||||
|
||||
for (Integer index : properties.getInstanceIndexList()) {
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> temporaryProperties =
|
||||
new ExtendedConsumerProperties<>(properties.getExtension());
|
||||
BeanUtils.copyProperties(properties, temporaryProperties);
|
||||
temporaryProperties.setInstanceIndex(index);
|
||||
consumerDestinationNames.add(doProvisionConsumerDestination(destination, group,
|
||||
temporaryProperties).getName());
|
||||
}
|
||||
|
||||
return consumerDestinationNames.stream();
|
||||
}
|
||||
else {
|
||||
return Stream.of(doProvisionConsumerDestination(destination, group,
|
||||
properties).getName());
|
||||
}
|
||||
})
|
||||
.toArray(String[]::new);
|
||||
consumerDestination = new RabbitConsumerDestination(
|
||||
StringUtils.arrayToCommaDelimitedString(provisionedDestinations),
|
||||
null, group, name);
|
||||
}
|
||||
return consumerDestination;
|
||||
}
|
||||
|
||||
private ConsumerDestination doProvisionConsumerDestination(String name, String group,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
|
||||
boolean anonymous = !StringUtils.hasText(group);
|
||||
String anonymousGroup = null;
|
||||
if (anonymous) {
|
||||
anonymousGroup = new Base64UrlNamingStrategy(
|
||||
properties.getExtension().getAnonymousGroupPrefix() == null
|
||||
? ""
|
||||
: properties.getExtension().getAnonymousGroupPrefix()).generateName();
|
||||
}
|
||||
String baseQueueName;
|
||||
if (properties.getExtension().isQueueNameGroupOnly()) {
|
||||
baseQueueName = anonymous ? anonymousGroup : group;
|
||||
}
|
||||
else {
|
||||
baseQueueName = groupedName(name, anonymous ? anonymousGroup : group);
|
||||
}
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
this.logger.info("declaring queue for inbound: " + baseQueueName
|
||||
+ ", bound to: " + name);
|
||||
}
|
||||
String prefix = properties.getExtension().getPrefix();
|
||||
final String exchangeName = applyPrefix(prefix, name);
|
||||
Exchange exchange = buildExchange(properties.getExtension(), exchangeName);
|
||||
if (properties.getExtension().isDeclareExchange()) {
|
||||
declareExchange(exchangeName, anonymous ? anonymousGroup : group, exchange);
|
||||
}
|
||||
String queueName = applyPrefix(prefix, baseQueueName);
|
||||
boolean partitioned = !anonymous && properties.isPartitioned();
|
||||
boolean durable = !anonymous && properties.getExtension().isDurableSubscription();
|
||||
Queue queue;
|
||||
if (anonymous) {
|
||||
String anonQueueName = queueName;
|
||||
queue = new AnonymousQueue((org.springframework.amqp.core.NamingStrategy) () -> anonQueueName,
|
||||
queueArgs(queueName, properties.getExtension(), false));
|
||||
}
|
||||
else {
|
||||
if (partitioned) {
|
||||
String partitionSuffix = "-" + properties.getInstanceIndex();
|
||||
queueName += partitionSuffix;
|
||||
}
|
||||
if (durable) {
|
||||
queue = new Queue(queueName, true, false, false,
|
||||
queueArgs(queueName, properties.getExtension(), false));
|
||||
}
|
||||
else {
|
||||
queue = new Queue(queueName, false, false, true,
|
||||
queueArgs(queueName, properties.getExtension(), false));
|
||||
}
|
||||
}
|
||||
Binding binding = null;
|
||||
if (properties.getExtension().isBindQueue()) {
|
||||
if (properties.getExtension().getContainerType().equals(ContainerType.STREAM)) {
|
||||
queue.getArguments().put("x-queue-type", "stream");
|
||||
}
|
||||
declareQueue(queueName, queue);
|
||||
String[] routingKeys = bindingRoutingKeys(properties.getExtension());
|
||||
if (ObjectUtils.isEmpty(routingKeys)) {
|
||||
binding = declareConsumerBindings(name, null, properties, exchange, partitioned, queue);
|
||||
}
|
||||
else {
|
||||
for (String routingKey : routingKeys) {
|
||||
binding = declareConsumerBindings(name, routingKey, properties, exchange, partitioned, queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (durable) {
|
||||
autoBindDLQ(applyPrefix(properties.getExtension().getPrefix(), baseQueueName),
|
||||
queueName, group, properties.getExtension());
|
||||
}
|
||||
return new RabbitConsumerDestination(queue.getName(), binding, anonymous ? baseQueueName : group, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a name comprised of the name and group.
|
||||
* @param name the name.
|
||||
* @param group the group.
|
||||
* @return the constructed name.
|
||||
*/
|
||||
protected final String groupedName(String name, String group) {
|
||||
return name + GROUP_INDEX_DELIMITER
|
||||
+ (StringUtils.hasText(group) ? group : "default");
|
||||
}
|
||||
|
||||
private Binding declareConsumerBindings(String name, String routingKey,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties,
|
||||
Exchange exchange, boolean partitioned, Queue queue) {
|
||||
|
||||
if (partitioned) {
|
||||
return partitionedBinding(name, exchange, queue, routingKey, properties.getExtension(),
|
||||
properties.getInstanceIndex());
|
||||
}
|
||||
else {
|
||||
return notPartitionedBinding(exchange, queue, routingKey, properties.getExtension());
|
||||
}
|
||||
}
|
||||
|
||||
private Binding partitionedBinding(String destination, Exchange exchange, Queue queue, String rk,
|
||||
RabbitCommonProperties extendedProperties, int index) {
|
||||
|
||||
String bindingKey = rk;
|
||||
if (bindingKey == null) {
|
||||
bindingKey = destination;
|
||||
}
|
||||
bindingKey += "-" + index;
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
arguments.putAll(extendedProperties.getQueueBindingArguments());
|
||||
if (exchange instanceof TopicExchange) {
|
||||
Binding binding = BindingBuilder.bind(queue).to((TopicExchange) exchange)
|
||||
.with(bindingKey);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else if (exchange instanceof DirectExchange) {
|
||||
Binding binding = BindingBuilder.bind(queue).to((DirectExchange) exchange)
|
||||
.with(bindingKey);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else if (exchange instanceof FanoutExchange) {
|
||||
throw new ProvisioningException(
|
||||
"A fanout exchange is not appropriate for partitioned apps");
|
||||
}
|
||||
else if (exchange instanceof HeadersExchange) {
|
||||
Binding binding = new Binding(queue.getName(), DestinationType.QUEUE, exchange.getName(), "", arguments);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else {
|
||||
throw new ProvisioningException(
|
||||
"Cannot bind to a " + exchange.getType() + " exchange");
|
||||
}
|
||||
}
|
||||
|
||||
private Binding notPartitionedBinding(Exchange exchange, Queue queue, String rk,
|
||||
RabbitCommonProperties extendedProperties) {
|
||||
|
||||
String routingKey = rk;
|
||||
if (routingKey == null) {
|
||||
routingKey = "#";
|
||||
}
|
||||
Map<String, Object> arguments = new HashMap<>(extendedProperties.getQueueBindingArguments());
|
||||
if (exchange instanceof TopicExchange) {
|
||||
Binding binding = BindingBuilder.bind(queue).to((TopicExchange) exchange)
|
||||
.with(routingKey);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else if (exchange instanceof DirectExchange) {
|
||||
Binding binding = BindingBuilder.bind(queue).to((DirectExchange) exchange)
|
||||
.with(routingKey);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else if (exchange instanceof FanoutExchange) {
|
||||
Binding binding = BindingBuilder.bind(queue).to((FanoutExchange) exchange);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else if (exchange instanceof HeadersExchange) {
|
||||
Binding binding = new Binding(queue.getName(), DestinationType.QUEUE, exchange.getName(), "", arguments);
|
||||
declareBinding(queue.getName(), binding);
|
||||
return binding;
|
||||
}
|
||||
else {
|
||||
throw new ProvisioningException(
|
||||
"Cannot bind to a " + exchange.getType() + " exchange");
|
||||
}
|
||||
}
|
||||
|
||||
private String[] bindingRoutingKeys(RabbitCommonProperties extendedProperties) {
|
||||
/*
|
||||
* When the delimiter is null, we get a String[1] containing the original.
|
||||
*/
|
||||
return StringUtils.delimitedListToStringArray(extendedProperties.getBindingRoutingKey(),
|
||||
extendedProperties.getBindingRoutingKeyDelimiter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 baseQueueName The base name for the queue (including the binder prefix, if
|
||||
* any).
|
||||
* @param routingKey The routing key for the queue.
|
||||
* @param group The consumer group.
|
||||
* @param properties the properties.
|
||||
*/
|
||||
private void autoBindDLQ(final String baseQueueName, String routingKey, String group,
|
||||
RabbitCommonProperties properties) {
|
||||
boolean autoBindDlq = properties.isAutoBindDlq();
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("autoBindDLQ=" + autoBindDlq + " for: " + baseQueueName);
|
||||
}
|
||||
if (autoBindDlq) {
|
||||
String dlqName;
|
||||
if (properties.getDeadLetterQueueName() == null) {
|
||||
dlqName = constructDLQName(baseQueueName);
|
||||
}
|
||||
else {
|
||||
dlqName = properties.getDeadLetterQueueName();
|
||||
}
|
||||
Queue dlq = new Queue(dlqName, true, false, false,
|
||||
queueArgs(dlqName, properties, true));
|
||||
declareQueue(dlqName, dlq);
|
||||
String dlxName = deadLetterExchangeName(properties);
|
||||
if (properties.isDeclareDlx()) {
|
||||
declareExchange(dlxName, group,
|
||||
new ExchangeBuilder(dlxName,
|
||||
properties.getDeadLetterExchangeType()).durable(true)
|
||||
.build());
|
||||
}
|
||||
Map<String, Object> arguments = new HashMap<>(properties.getDlqBindingArguments());
|
||||
Binding dlqBinding = new Binding(dlq.getName(), DestinationType.QUEUE,
|
||||
dlxName, properties.getDeadLetterRoutingKey() == null ? routingKey
|
||||
: properties.getDeadLetterRoutingKey(),
|
||||
arguments);
|
||||
declareBinding(dlqName, dlqBinding);
|
||||
if (properties instanceof RabbitConsumerProperties
|
||||
&& ((RabbitConsumerProperties) properties).isRepublishToDlq()) {
|
||||
/*
|
||||
* Also bind with the base queue name when republishToDlq is used, which
|
||||
* does not know about partitioning
|
||||
*/
|
||||
declareBinding(dlqName + ".2", new Binding(dlq.getName(), DestinationType.QUEUE,
|
||||
dlxName, baseQueueName, arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For binder implementations that support dead lettering, construct the name of the
|
||||
* dead letter entity for the underlying pipe name.
|
||||
* @param name the name.
|
||||
* @return constructDLQName
|
||||
*/
|
||||
public static String constructDLQName(String name) {
|
||||
return name + ".dlq";
|
||||
}
|
||||
|
||||
private String deadLetterExchangeName(RabbitCommonProperties properties) {
|
||||
if (properties.getDeadLetterExchange() == null) {
|
||||
return properties.getPrefix() + RabbitCommonProperties.DEAD_LETTER_EXCHANGE;
|
||||
}
|
||||
else {
|
||||
return properties.getDeadLetterExchange();
|
||||
}
|
||||
}
|
||||
|
||||
private void declareQueue(String beanName, Queue queueArg) {
|
||||
Queue queue = queueArg;
|
||||
for (DeclarableCustomizer customizer : this.customizers) {
|
||||
queue = (Queue) customizer.apply(queue);
|
||||
}
|
||||
try {
|
||||
this.rabbitAdmin.declareQueue(queue);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Declaration of queue: " + queue.getName()
|
||||
+ " deferred - connection not available");
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (this.notOurAdminException) {
|
||||
this.notOurAdminException = false;
|
||||
throw e;
|
||||
}
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(
|
||||
"Declaration of queue: " + queue.getName() + " deferred", e);
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(beanName, queue);
|
||||
}
|
||||
|
||||
private Map<String, Object> queueArgs(String queueName,
|
||||
RabbitCommonProperties properties, boolean isDlq) {
|
||||
Map<String, Object> args = new HashMap<>();
|
||||
if (!isDlq) {
|
||||
if (properties.isAutoBindDlq()) {
|
||||
String dlx;
|
||||
if (properties.getDeadLetterExchange() != null) {
|
||||
dlx = properties.getDeadLetterExchange();
|
||||
}
|
||||
else {
|
||||
dlx = applyPrefix(properties.getPrefix(), "DLX");
|
||||
}
|
||||
args.put("x-dead-letter-exchange", dlx);
|
||||
String dlRk;
|
||||
if (properties.getDeadLetterRoutingKey() != null) {
|
||||
dlRk = properties.getDeadLetterRoutingKey();
|
||||
}
|
||||
else {
|
||||
dlRk = queueName;
|
||||
}
|
||||
args.put("x-dead-letter-routing-key", dlRk);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (properties.getDlqDeadLetterExchange() != null) {
|
||||
args.put("x-dead-letter-exchange", properties.getDlqDeadLetterExchange());
|
||||
}
|
||||
if (properties.getDlqDeadLetterRoutingKey() != null) {
|
||||
args.put("x-dead-letter-routing-key",
|
||||
properties.getDlqDeadLetterRoutingKey());
|
||||
}
|
||||
}
|
||||
additionalArgs(args, properties, isDlq);
|
||||
return args;
|
||||
}
|
||||
|
||||
private void additionalArgs(Map<String, Object> args, RabbitCommonProperties properties, boolean isDlq) {
|
||||
Integer expires = isDlq ? properties.getDlqExpires() : properties.getExpires();
|
||||
Integer maxLength = isDlq ? properties.getDlqMaxLength()
|
||||
: properties.getMaxLength();
|
||||
Integer maxLengthBytes = isDlq ? properties.getDlqMaxLengthBytes()
|
||||
: properties.getMaxLengthBytes();
|
||||
Integer maxPriority = isDlq ? properties.getDlqMaxPriority()
|
||||
: properties.getMaxPriority();
|
||||
Integer ttl = isDlq ? properties.getDlqTtl() : properties.getTtl();
|
||||
boolean lazy = isDlq ? properties.isDlqLazy() : properties.isLazy();
|
||||
String overflow = isDlq ? properties.getDlqOverflowBehavior()
|
||||
: properties.getOverflowBehavior();
|
||||
QuorumConfig quorum = isDlq ? properties.getDlqQuorum() : properties.getQuorum();
|
||||
boolean singleActive = isDlq ? properties.isDlqSingleActiveConsumer() : properties.isSingleActiveConsumer();
|
||||
if (expires != null) {
|
||||
args.put("x-expires", expires);
|
||||
}
|
||||
if (maxLength != null) {
|
||||
args.put("x-max-length", maxLength);
|
||||
}
|
||||
if (maxLengthBytes != null) {
|
||||
args.put("x-max-length-bytes", maxLengthBytes);
|
||||
}
|
||||
if (maxPriority != null) {
|
||||
args.put("x-max-priority", maxPriority);
|
||||
}
|
||||
if (ttl != null) {
|
||||
args.put("x-message-ttl", ttl);
|
||||
}
|
||||
if (lazy) {
|
||||
args.put("x-queue-mode", "lazy");
|
||||
}
|
||||
if (StringUtils.hasText(overflow)) {
|
||||
args.put("x-overflow", overflow);
|
||||
}
|
||||
if (quorum != null && quorum.isEnabled()) {
|
||||
args.put("x-queue-type", "quorum");
|
||||
if (quorum.getDeliveryLimit() != null) {
|
||||
args.put("x-delivery-limit", quorum.getDeliveryLimit());
|
||||
}
|
||||
if (quorum.getInitialGroupSize() != null) {
|
||||
args.put("x-quorum-initial-group-size", quorum.getInitialGroupSize());
|
||||
}
|
||||
}
|
||||
if (singleActive) {
|
||||
args.put("x-single-active-consumer", true);
|
||||
}
|
||||
}
|
||||
|
||||
public static String applyPrefix(String prefix, String name) {
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
private Exchange buildExchange(RabbitCommonProperties properties,
|
||||
String exchangeName) {
|
||||
try {
|
||||
ExchangeBuilder builder = new ExchangeBuilder(exchangeName,
|
||||
properties.getExchangeType());
|
||||
builder.durable(properties.isExchangeDurable());
|
||||
if (properties.isExchangeAutoDelete()) {
|
||||
builder.autoDelete();
|
||||
}
|
||||
if (properties.isDelayedExchange()) {
|
||||
builder.delayed();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new ProvisioningException("Failed to create exchange object", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void declareExchange(final String rootName, String group, final Exchange exchangeArg) {
|
||||
Exchange exchange = exchangeArg;
|
||||
for (DeclarableCustomizer customizer : this.customizers) {
|
||||
exchange = (Exchange) customizer.apply(exchange);
|
||||
}
|
||||
try {
|
||||
this.rabbitAdmin.declareExchange(exchange);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Declaration of exchange: " + exchange.getName()
|
||||
+ " deferred - connection not available");
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (this.notOurAdminException) {
|
||||
this.notOurAdminException = false;
|
||||
throw e;
|
||||
}
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(
|
||||
"Declaration of exchange: " + exchange.getName() + " deferred",
|
||||
e);
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(rootName + "." + group + ".exchange", exchange);
|
||||
}
|
||||
|
||||
private void addToAutoDeclareContext(String name, Object bean) {
|
||||
synchronized (this.autoDeclareContext) {
|
||||
if (!this.autoDeclareContext.containsBean(name)) {
|
||||
this.autoDeclareContext.getBeanFactory().registerSingleton(name, bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void declareBinding(String rootName, org.springframework.amqp.core.Binding bindingArg) {
|
||||
Binding binding = bindingArg;
|
||||
for (DeclarableCustomizer customizer : this.customizers) {
|
||||
binding = (Binding) customizer.apply(binding);
|
||||
}
|
||||
try {
|
||||
this.rabbitAdmin.declareBinding(binding);
|
||||
}
|
||||
catch (AmqpConnectException e) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug("Declaration of binding: " + rootName
|
||||
+ ".binding deferred - connection not available");
|
||||
}
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (this.notOurAdminException) {
|
||||
this.notOurAdminException = false;
|
||||
throw e;
|
||||
}
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(
|
||||
"Declaration of binding: " + rootName + ".binding deferred", e);
|
||||
}
|
||||
}
|
||||
addToAutoDeclareContext(rootName + ".binding", binding);
|
||||
}
|
||||
|
||||
public void cleanAutoDeclareContext(ConsumerDestination destination,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> consumerProperties) {
|
||||
|
||||
synchronized (this.autoDeclareContext) {
|
||||
Stream.of(StringUtils.tokenizeToStringArray(destination.getName(), ",", true,
|
||||
true)).forEach(name -> {
|
||||
String group = null;
|
||||
String bindingName = null;
|
||||
if (destination instanceof RabbitConsumerDestination) {
|
||||
group = ((RabbitConsumerDestination) destination).getGroup();
|
||||
bindingName = ((RabbitConsumerDestination) destination).getBindingName();
|
||||
}
|
||||
RabbitConsumerProperties properties = consumerProperties.getExtension();
|
||||
String toRemove = properties.isQueueNameGroupOnly() ? bindingName + "." + group : name.trim();
|
||||
boolean partitioned = consumerProperties.isPartitioned();
|
||||
if (partitioned) {
|
||||
toRemove = removePartitionPart(toRemove);
|
||||
}
|
||||
removeSingleton(toRemove + ".exchange");
|
||||
removeQueueAndBindingBeans(properties, name.trim(), "", group, partitioned);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanAutoDeclareContext(ProducerDestination dest,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties) {
|
||||
|
||||
synchronized (this.autoDeclareContext) {
|
||||
if (dest instanceof RabbitProducerDestination) {
|
||||
String qual = ((RabbitProducerDestination) dest).getBeanNameQualifier();
|
||||
removeSingleton(dest.getName() + "." + qual + ".exchange");
|
||||
String[] requiredGroups = properties.getRequiredGroups();
|
||||
if (!ObjectUtils.isEmpty(requiredGroups)) {
|
||||
for (String group : requiredGroups) {
|
||||
if (properties.isPartitioned()) {
|
||||
for (int i = 0; i < properties.getPartitionCount(); i++) {
|
||||
removeQueueAndBindingBeans(properties.getExtension(),
|
||||
properties.getExtension().isQueueNameGroupOnly() ? "" : dest.getName(),
|
||||
group + "-" + i, group, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
removeQueueAndBindingBeans(properties.getExtension(), dest.getName() + "." + group, "",
|
||||
group, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeQueueAndBindingBeans(RabbitCommonProperties properties, String name, String suffix,
|
||||
String group, boolean partitioned) {
|
||||
|
||||
boolean suffixPresent = StringUtils.hasText(suffix);
|
||||
String withSuffix = name + (suffixPresent ? ("." + suffix) : "");
|
||||
String nameDotOptional = name;
|
||||
if (!StringUtils.hasText(name)) {
|
||||
withSuffix = suffix;
|
||||
}
|
||||
else {
|
||||
nameDotOptional = name + ".";
|
||||
}
|
||||
removeSingleton(withSuffix + ".binding");
|
||||
removeSingleton(withSuffix);
|
||||
String dlq = (suffixPresent ? nameDotOptional + group : withSuffix) + ".dlq"; // only one DLQ when partitioned
|
||||
if (StringUtils.hasText(properties.getDeadLetterQueueName())) {
|
||||
dlq = properties.getDeadLetterQueueName();
|
||||
}
|
||||
else if (partitioned) {
|
||||
String removedPart = removePartitionPart(dlq);
|
||||
if (!removedPart.endsWith(".dlq")) {
|
||||
dlq = removedPart + ".dlq";
|
||||
}
|
||||
}
|
||||
removeSingleton(dlq + ".binding");
|
||||
removeSingleton(dlq + ".2.binding");
|
||||
removeSingleton(dlq);
|
||||
removeSingleton(deadLetterExchangeName(properties) + "." + group + ".exchange");
|
||||
}
|
||||
|
||||
private String removePartitionPart(String toRemove) {
|
||||
int finalHyphen = toRemove.lastIndexOf("-");
|
||||
if (finalHyphen > 0) {
|
||||
return toRemove.substring(0, finalHyphen);
|
||||
}
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
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 onApplicationEvent(DeclarationExceptionEvent event) {
|
||||
this.notOurAdminException = true; // our admin doesn't have an event publisher
|
||||
}
|
||||
|
||||
private static final class RabbitProducerDestination implements ProducerDestination {
|
||||
|
||||
private final Exchange exchange;
|
||||
|
||||
private final Binding binding;
|
||||
|
||||
private final String beanNameQualifier;
|
||||
|
||||
RabbitProducerDestination(Exchange exchange, Binding binding, String beanNameQualifier) {
|
||||
Assert.notNull(exchange, "exchange must not be null");
|
||||
this.exchange = exchange;
|
||||
this.binding = binding;
|
||||
this.beanNameQualifier = beanNameQualifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.exchange.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameForPartition(int partition) {
|
||||
return this.exchange.getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getBeanNameQualifier() {
|
||||
return this.beanNameQualifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RabbitProducerDestination{" + "exchange=" + this.exchange + ", binding="
|
||||
+ this.binding + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class RabbitConsumerDestination implements ConsumerDestination {
|
||||
|
||||
private final String queue;
|
||||
|
||||
private final Binding binding;
|
||||
|
||||
private final String group;
|
||||
|
||||
private final String bindingName;
|
||||
|
||||
RabbitConsumerDestination(String queue, Binding binding, String group, String bindingName) {
|
||||
Assert.notNull(queue, "queue must not be null");
|
||||
this.queue = queue;
|
||||
this.binding = binding;
|
||||
this.group = group;
|
||||
this.bindingName = bindingName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.queue;
|
||||
}
|
||||
|
||||
String getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
String getBindingName() {
|
||||
return this.bindingName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RabbitConsumerDestination{" + "queue=" + this.queue + ", binding="
|
||||
+ this.binding + ", group=" + this.group + ", bindingName=" + this.bindingName + '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2022-2022 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.provisioning;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.amqp.core.Declarable;
|
||||
import org.springframework.amqp.rabbit.connection.Connection;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.utils.test.TestUtils;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
|
||||
import org.springframework.cloud.stream.provisioning.ProducerDestination;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.2.3
|
||||
*
|
||||
*/
|
||||
public class RabbitExchangeQueueProvisionerTests {
|
||||
|
||||
@Test
|
||||
void consumerDeclarationsWithDlq() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitConsumerProperties props = new RabbitConsumerProperties();
|
||||
props.setAutoBindDlq(true);
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties =
|
||||
new ExtendedConsumerProperties<RabbitConsumerProperties>(props);
|
||||
ConsumerDestination dest = provisioner.provisionConsumerDestination("foo", "group", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
assertThat(declarables).contains("foo.group.exchange", "foo.group", "foo.group.binding", "foo.group.dlq",
|
||||
"DLX.group.exchange", "foo.group.dlq.binding", "foo.group.dlq.2.binding");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void consumerDeclarationsWithDlqQueueNameIsGroup() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitConsumerProperties props = new RabbitConsumerProperties();
|
||||
props.setAutoBindDlq(true);
|
||||
props.setQueueNameGroupOnly(true);
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties =
|
||||
new ExtendedConsumerProperties<RabbitConsumerProperties>(props);
|
||||
ConsumerDestination dest = provisioner.provisionConsumerDestination("fiz", "group", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
assertThat(declarables).contains("fiz.group.exchange", "group", "group.binding", "group.dlq",
|
||||
"DLX.group.exchange", "group.dlq.binding", "group.dlq.2.binding");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void producerDeclarationsNoGroups() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitProducerProperties props = new RabbitProducerProperties();
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties =
|
||||
new ExtendedProducerProperties<RabbitProducerProperties>(props);
|
||||
ProducerDestination dest = provisioner.provisionProducerDestination("bar", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
String qual = TestUtils.getPropertyValue(dest, "beanNameQualifier", String.class);
|
||||
assertThat(declarables).contains("bar." + qual + ".exchange");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void producerDeclarationsWithGroupsAndDlq() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitProducerProperties props = new RabbitProducerProperties();
|
||||
props.setAutoBindDlq(true);
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties =
|
||||
new ExtendedProducerProperties<RabbitProducerProperties>(props);
|
||||
properties.setRequiredGroups("group1", "group2");
|
||||
ProducerDestination dest = provisioner.provisionProducerDestination("baz", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
String qual = TestUtils.getPropertyValue(dest, "beanNameQualifier", String.class);
|
||||
assertThat(declarables).contains("baz." + qual + ".exchange", "baz.group1", "baz.group1.binding",
|
||||
"baz.group1.dlq", "DLX.group1.exchange", "baz.group1.dlq.binding", "baz.group2", "baz.group2.binding",
|
||||
"baz.group2.dlq", "DLX.group2.exchange", "baz.group2.dlq.binding");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void producerDeclarationsWithGroupsAndDlqAndPartitions() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitProducerProperties props = new RabbitProducerProperties();
|
||||
props.setAutoBindDlq(true);
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties =
|
||||
new ExtendedProducerProperties<RabbitProducerProperties>(props);
|
||||
properties.setRequiredGroups("group1", "group2");
|
||||
properties.setPartitionKeyExpression(new LiteralExpression("foo"));
|
||||
properties.setPartitionCount(2);
|
||||
ProducerDestination dest = provisioner.provisionProducerDestination("qux", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
String qual = TestUtils.getPropertyValue(dest, "beanNameQualifier", String.class);
|
||||
assertThat(declarables).contains("qux." + qual + ".exchange", "qux.group1-0", "qux.group1-0.binding",
|
||||
"qux.group1-1", "qux.group1-1.binding", "qux.group1.dlq", "DLX.group1.exchange",
|
||||
"qux.group1.dlq.binding", "qux.group2-0",
|
||||
"qux.group2-0.binding", "qux.group2-1", "qux.group2-1.binding", "qux.group2.dlq", "DLX.group2.exchange",
|
||||
"qux.group2.dlq.binding");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void producerDeclarationsWithGroupsAndDlqAndPartitionsQueueNameIsGroup() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
willReturn(new DeclareOk("x", 0, 0))
|
||||
.given(channel).queueDeclare(any(), eq(Boolean.TRUE), eq(Boolean.FALSE), eq(Boolean.FALSE), any());
|
||||
given(conn.createChannel(anyBoolean())).willReturn(channel);
|
||||
RabbitExchangeQueueProvisioner provisioner = new RabbitExchangeQueueProvisioner(cf);
|
||||
|
||||
RabbitProducerProperties props = new RabbitProducerProperties();
|
||||
props.setAutoBindDlq(true);
|
||||
props.setQueueNameGroupOnly(true);
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties =
|
||||
new ExtendedProducerProperties<RabbitProducerProperties>(props);
|
||||
properties.setRequiredGroups("group1", "group2");
|
||||
properties.setPartitionKeyExpression(new LiteralExpression("foo"));
|
||||
properties.setPartitionCount(2);
|
||||
ProducerDestination dest = provisioner.provisionProducerDestination("qux", properties);
|
||||
ApplicationContext ctx =
|
||||
TestUtils.getPropertyValue(provisioner, "autoDeclareContext", ApplicationContext.class);
|
||||
Set<String> declarables = ctx.getBeansOfType(Declarable.class).keySet();
|
||||
String qual = TestUtils.getPropertyValue(dest, "beanNameQualifier", String.class);
|
||||
assertThat(declarables).contains("qux." + qual + ".exchange", "group1-0", "group1-0.binding", "group1-1",
|
||||
"group1-1.binding", "group1.dlq", "DLX.group1.exchange", "group1.dlq.binding", "group2-0",
|
||||
"group2-0.binding", "group2-1", "group2-1.binding", "group2.dlq", "DLX.group2.exchange",
|
||||
"group2.dlq.binding");
|
||||
provisioner.cleanAutoDeclareContext(dest, properties);
|
||||
assertThat(ctx.getBeansOfType(Declarable.class)).isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-test-support</artifactId>
|
||||
<description>Rabbit related test classes</description>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.test.junit.rabbit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
import javax.net.SocketFactory;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.cloud.stream.test.junit.AbstractExternalResourceTestSupport;
|
||||
|
||||
/**
|
||||
* JUnit {@link org.junit.Rule} that detects the fact that RabbitMQ is available on
|
||||
* localhost.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class RabbitTestSupport
|
||||
extends AbstractExternalResourceTestSupport<CachingConnectionFactory> {
|
||||
|
||||
private final boolean management;
|
||||
|
||||
public RabbitTestSupport() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public RabbitTestSupport(boolean management) {
|
||||
super("RABBIT");
|
||||
this.management = management;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void obtainResource() throws Exception {
|
||||
resource = new CachingConnectionFactory("localhost");
|
||||
resource.createConnection().close();
|
||||
if (management) {
|
||||
Socket socket = SocketFactory.getDefault().createSocket("localhost", 15672);
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanupResource() throws Exception {
|
||||
resource.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class to allow testing deferred entity declarations when RabbitMQ is down.
|
||||
*/
|
||||
public static class RabbitProxy {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(RabbitProxy.class);
|
||||
|
||||
private final int port;
|
||||
|
||||
private final ExecutorService serverExec = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final ExecutorService socketExec = Executors.newCachedThreadPool();
|
||||
|
||||
private volatile ServerSocket serverSocket;
|
||||
|
||||
public RabbitProxy() throws IOException {
|
||||
ServerSocket serverSocket = ServerSocketFactory.getDefault()
|
||||
.createServerSocket(0);
|
||||
this.port = serverSocket.getLocalPort();
|
||||
serverSocket.close();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
this.serverSocket = ServerSocketFactory.getDefault()
|
||||
.createServerSocket(this.port, 10);
|
||||
LOGGER.info("Proxy started");
|
||||
this.serverExec.execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
final Socket socket = serverSocket.accept();
|
||||
LOGGER.info("Accepted Connection");
|
||||
socketExec.execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final Socket rabbitSocket = SocketFactory
|
||||
.getDefault()
|
||||
.createSocket("localhost", 5672);
|
||||
socketExec.execute(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOGGER.info("Running: " + rabbitSocket.getLocalPort());
|
||||
try {
|
||||
InputStream is = rabbitSocket
|
||||
.getInputStream();
|
||||
OutputStream os = socket
|
||||
.getOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) >= 0) {
|
||||
os.write(c);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
socket.close();
|
||||
rabbitSocket.close();
|
||||
}
|
||||
catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
InputStream is = socket.getInputStream();
|
||||
OutputStream os = rabbitSocket.getOutputStream();
|
||||
int c;
|
||||
while ((c = is.read()) >= 0) {
|
||||
os.write(c);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
socket.close();
|
||||
}
|
||||
catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
}
|
||||
catch (IOException e1) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() throws IOException {
|
||||
this.serverSocket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
0
r-binder/spring-cloud-stream-binder-rabbit/.jdk8
Normal file
0
r-binder/spring-cloud-stream-binder-rabbit/.jdk8
Normal file
116
r-binder/spring-cloud-stream-binder-rabbit/pom.xml
Normal file
116
r-binder/spring-cloud-stream-binder-rabbit/pom.xml
Normal file
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>spring-cloud-stream-binder-rabbit</name>
|
||||
<description>RabbitMQ binder implementation</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-parent</artifactId>
|
||||
<version>3.2.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-deployer</artifactId>
|
||||
<version>${spring-cloud-function.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-connectors-core</artifactId>
|
||||
<optional>true</optional>
|
||||
<version>2.0.7.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-spring-service-connector</artifactId>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
<version>2.0.7.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit-stream</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-jmx</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.integration</groupId>
|
||||
<artifactId>spring-integration-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-test-support-internal</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-rabbit-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>rabbitmq</artifactId>
|
||||
<version>1.15.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- Temporary override - see https://github.com/spring-projects/spring-boot/issues/16043 -->
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<classifier>exec</classifier>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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 org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interceptor to evaluate expressions for outbound messages before serialization.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 2.0
|
||||
*
|
||||
*/
|
||||
public class RabbitExpressionEvaluatingInterceptor implements ChannelInterceptor {
|
||||
|
||||
/**
|
||||
* Instance of ExpressionParser.
|
||||
*/
|
||||
public static final ExpressionParser PARSER = new SpelExpressionParser();
|
||||
|
||||
/**
|
||||
* Default name for routing key header.
|
||||
*/
|
||||
public static final String ROUTING_KEY_HEADER = "scst_routingKey";
|
||||
|
||||
/**
|
||||
* Default name for delay header.
|
||||
*/
|
||||
public static final String DELAY_HEADER = "scst_delay";
|
||||
|
||||
private final Expression routingKeyExpression;
|
||||
|
||||
private final Expression delayExpression;
|
||||
|
||||
private final EvaluationContext evaluationContext;
|
||||
|
||||
/**
|
||||
* Construct an instance with the provided expressions and evaluation context. At
|
||||
* least one expression muse be non-null.
|
||||
* @param routingKeyExpression the routing key expresssion.
|
||||
* @param delayExpression the delay expression.
|
||||
* @param evaluationContext the evaluation context.
|
||||
*/
|
||||
public RabbitExpressionEvaluatingInterceptor(Expression routingKeyExpression,
|
||||
Expression delayExpression, EvaluationContext evaluationContext) {
|
||||
Assert.isTrue(routingKeyExpression != null || delayExpression != null,
|
||||
"At least one expression is required");
|
||||
Assert.notNull(evaluationContext, "the 'evaluationContext' cannot be null");
|
||||
if (routingKeyExpression != null) {
|
||||
this.routingKeyExpression = routingKeyExpression;
|
||||
}
|
||||
else {
|
||||
this.routingKeyExpression = null;
|
||||
}
|
||||
if (delayExpression != null) {
|
||||
this.delayExpression = delayExpression;
|
||||
}
|
||||
else {
|
||||
this.delayExpression = null;
|
||||
}
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
||||
MessageBuilder<?> builder = MessageBuilder.fromMessage(message);
|
||||
if (this.routingKeyExpression != null) {
|
||||
builder.setHeader(ROUTING_KEY_HEADER,
|
||||
this.routingKeyExpression.getValue(this.evaluationContext, message));
|
||||
}
|
||||
if (this.delayExpression != null) {
|
||||
builder.setHeader(DELAY_HEADER,
|
||||
this.delayExpression.getValue(this.evaluationContext, message));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
|
||||
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
|
||||
import org.springframework.integration.handler.AbstractMessageHandler;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.rabbit.stream.producer.RabbitStreamOperations;
|
||||
import org.springframework.rabbit.stream.support.StreamMessageProperties;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.SuccessCallback;
|
||||
|
||||
/**
|
||||
* {@link MessageHandler} based on {@link RabbitStreamOperations}.
|
||||
*
|
||||
* TODO: This class will move to Spring Integration in 6.0.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public class RabbitStreamMessageHandler extends AbstractMessageHandler implements Lifecycle {
|
||||
|
||||
private static final int DEFAULT_CONFIRM_TIMEOUT = 10_000;
|
||||
|
||||
private final RabbitStreamOperations streamOperations;
|
||||
|
||||
private boolean sync;
|
||||
|
||||
private long confirmTimeout = DEFAULT_CONFIRM_TIMEOUT;
|
||||
|
||||
private SuccessCallback<Message<?>> successCallback = msg -> { };
|
||||
|
||||
private FailureCallback failureCallback = (msg, ex) -> { };
|
||||
|
||||
private AmqpHeaderMapper headerMapper = DefaultAmqpHeaderMapper.outboundMapper();
|
||||
|
||||
private boolean headersMappedLast;
|
||||
|
||||
/**
|
||||
* Create an instance with the provided {@link RabbitStreamOperations}.
|
||||
* @param streamOperations the operations.
|
||||
*/
|
||||
public RabbitStreamMessageHandler(RabbitStreamOperations streamOperations) {
|
||||
Assert.notNull(streamOperations, "'streamOperations' cannot be null");
|
||||
this.streamOperations = streamOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback to be invoked when a send is successful.
|
||||
* @param successCallback the callback.
|
||||
*/
|
||||
public void setSuccessCallback(SuccessCallback<Message<?>> successCallback) {
|
||||
Assert.notNull(successCallback, "'successCallback' cannot be null");
|
||||
this.successCallback = successCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback to be invoked when a send fails.
|
||||
* @param failureCallback the callback.
|
||||
*/
|
||||
public void setFailureCallback(FailureCallback failureCallback) {
|
||||
Assert.notNull(failureCallback, "'failureCallback' cannot be null");
|
||||
this.failureCallback = failureCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to wait for a confirmation.
|
||||
* @param sync true to wait.
|
||||
* @see #setConfirmTimeout(long)
|
||||
*/
|
||||
public void setSync(boolean sync) {
|
||||
this.sync = sync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the confirm timeout.
|
||||
* @param confirmTimeout the timeout.
|
||||
* @see #setSync(boolean)
|
||||
*/
|
||||
public void setConfirmTimeout(long confirmTimeout) {
|
||||
this.confirmTimeout = confirmTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom {@link AmqpHeaderMapper} for mapping request and reply headers.
|
||||
* Defaults to {@link DefaultAmqpHeaderMapper#outboundMapper()}.
|
||||
* @param headerMapper the {@link AmqpHeaderMapper} to use.
|
||||
*/
|
||||
public void setHeaderMapper(AmqpHeaderMapper headerMapper) {
|
||||
Assert.notNull(headerMapper, "headerMapper must not be null");
|
||||
this.headerMapper = headerMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* When mapping headers for the outbound message, determine whether the headers are
|
||||
* mapped before the message is converted, or afterwards. This only affects headers
|
||||
* that might be added by the message converter. When false, the converter's headers
|
||||
* win; when true, any headers added by the converter will be overridden (if the
|
||||
* source message has a header that maps to those headers). You might wish to set this
|
||||
* to true, for example, when using a
|
||||
* {@link org.springframework.amqp.support.converter.SimpleMessageConverter} with a
|
||||
* String payload that contains json; the converter will set the content type to
|
||||
* {@code text/plain} which can be overridden to {@code application/json} by setting
|
||||
* the {@link AmqpHeaders#CONTENT_TYPE} message header. Default: false.
|
||||
* @param headersMappedLast true if headers are mapped after conversion.
|
||||
*/
|
||||
public void setHeadersMappedLast(boolean headersMappedLast) {
|
||||
this.headersMappedLast = headersMappedLast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link RabbitStreamOperations}.
|
||||
* @return the operations.
|
||||
*/
|
||||
public RabbitStreamOperations getStreamOperations() {
|
||||
return this.streamOperations;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(Message<?> requestMessage) {
|
||||
ListenableFuture<Boolean> future;
|
||||
com.rabbitmq.stream.Message streamMessage;
|
||||
if (requestMessage.getPayload() instanceof com.rabbitmq.stream.Message) {
|
||||
streamMessage = (com.rabbitmq.stream.Message) requestMessage.getPayload();
|
||||
}
|
||||
else {
|
||||
MessageConverter converter = streamOperations.messageConverter();
|
||||
org.springframework.amqp.core.Message amqpMessage = mapMessage(requestMessage, converter,
|
||||
this.headerMapper, this.headersMappedLast);
|
||||
streamMessage = this.streamOperations.streamMessageConverter().fromMessage(amqpMessage);
|
||||
}
|
||||
future = this.streamOperations.send(streamMessage);
|
||||
handleConfirms(requestMessage, future);
|
||||
}
|
||||
|
||||
private void handleConfirms(Message<?> message, ListenableFuture<Boolean> future) {
|
||||
future.addCallback(bool -> this.successCallback.onSuccess(message),
|
||||
ex -> this.failureCallback.failure(message, ex));
|
||||
if (this.sync) {
|
||||
try {
|
||||
future.get(this.confirmTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new MessageHandlingException(message, ex);
|
||||
}
|
||||
catch (ExecutionException | TimeoutException ex) {
|
||||
throw new MessageHandlingException(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO Copied/modified from MapppingUtils until SI 6.0
|
||||
*/
|
||||
private static org.springframework.amqp.core.Message mapMessage(Message<?> message,
|
||||
MessageConverter converter, AmqpHeaderMapper headerMapper, boolean headersMappedLast) {
|
||||
|
||||
MessageProperties amqpMessageProperties = new StreamMessageProperties();
|
||||
org.springframework.amqp.core.Message amqpMessage;
|
||||
if (!headersMappedLast) {
|
||||
mapHeaders(message.getHeaders(), amqpMessageProperties, headerMapper);
|
||||
}
|
||||
if (converter instanceof ContentTypeDelegatingMessageConverter && headersMappedLast) {
|
||||
String contentType = contentTypeAsString(message.getHeaders());
|
||||
if (contentType != null) {
|
||||
amqpMessageProperties.setContentType(contentType);
|
||||
}
|
||||
}
|
||||
amqpMessage = converter.toMessage(message.getPayload(), amqpMessageProperties);
|
||||
if (headersMappedLast) {
|
||||
mapHeaders(message.getHeaders(), amqpMessageProperties, headerMapper);
|
||||
}
|
||||
return amqpMessage;
|
||||
}
|
||||
|
||||
private static void mapHeaders(MessageHeaders messageHeaders, MessageProperties amqpMessageProperties,
|
||||
AmqpHeaderMapper headerMapper) {
|
||||
|
||||
headerMapper.fromHeadersToRequest(messageHeaders, amqpMessageProperties);
|
||||
}
|
||||
|
||||
private static String contentTypeAsString(MessageHeaders headers) {
|
||||
Object contentType = headers.get(AmqpHeaders.CONTENT_TYPE);
|
||||
if (contentType instanceof MimeType) {
|
||||
contentType = contentType.toString();
|
||||
}
|
||||
if (contentType instanceof String) {
|
||||
return (String) contentType;
|
||||
}
|
||||
else if (contentType != null) {
|
||||
throw new IllegalArgumentException(AmqpHeaders.CONTENT_TYPE
|
||||
+ " header must be a MimeType or String, found: " + contentType.getClass().getName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/*
|
||||
* End copied/modified from MappingUtils
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.streamOperations.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when publishing fails.
|
||||
*/
|
||||
public interface FailureCallback {
|
||||
|
||||
/**
|
||||
* Message publish failure.
|
||||
* @param message the message.
|
||||
* @param throwable the throwable.
|
||||
*/
|
||||
void failure(Message<?> message, Throwable throwable);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.rabbitmq.stream.Environment;
|
||||
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties.ProducerType;
|
||||
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
|
||||
import org.springframework.cloud.stream.provisioning.ProducerDestination;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
|
||||
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
|
||||
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
import org.springframework.rabbit.stream.listener.ConsumerCustomizer;
|
||||
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
|
||||
import org.springframework.rabbit.stream.producer.RabbitStreamTemplate;
|
||||
import org.springframework.rabbit.stream.support.StreamMessageProperties;
|
||||
import org.springframework.rabbit.stream.support.converter.StreamMessageConverter;
|
||||
|
||||
/**
|
||||
* Utilities for stream components. Used to prevent a hard runtime dependency on
|
||||
* spring-rabbit-stream.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public final class StreamUtils {
|
||||
|
||||
private StreamUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link StreamListenerContainer}.
|
||||
*
|
||||
* @param consumerDestination the destination.
|
||||
* @param group the group.
|
||||
* @param properties the properties.
|
||||
* @param destination the destination.
|
||||
* @param extension the properties extension.
|
||||
* @param applicationContext the application context.
|
||||
* @return the container.
|
||||
*/
|
||||
public static MessageListenerContainer createContainer(ConsumerDestination consumerDestination, String group,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties, String destination,
|
||||
RabbitConsumerProperties extension, AbstractApplicationContext applicationContext) {
|
||||
|
||||
StreamListenerContainer container = new StreamListenerContainer(applicationContext.getBean(Environment.class)) {
|
||||
|
||||
@Override
|
||||
public synchronized void setConsumerCustomizer(ConsumerCustomizer consumerCustomizer) {
|
||||
super.setConsumerCustomizer((id, builder) -> {
|
||||
builder.name(consumerDestination.getName() + "." + group);
|
||||
consumerCustomizer.accept(id, builder);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
container.setBeanName(consumerDestination.getName() + "." + group + ".container");
|
||||
String beanName = extension.getStreamStreamMessageConverterBeanName();
|
||||
if (beanName != null) {
|
||||
container.setStreamConverter(applicationContext.getBean(beanName, StreamMessageConverter.class));
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the channel adapter for streams support.
|
||||
* @param adapter the adapter.
|
||||
*/
|
||||
public static void configureAdapter(AmqpInboundChannelAdapter adapter) {
|
||||
adapter.setHeaderMapper(new AmqpHeaderMapper() {
|
||||
|
||||
AmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper();
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toHeadersFromRequest(MessageProperties source) {
|
||||
Map<String, Object> headers = this.mapper.toHeadersFromRequest(source);
|
||||
headers.put("rabbitmq_streamContext", ((StreamMessageProperties) source).getContext());
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> toHeadersFromReply(MessageProperties source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromHeadersToRequest(MessageHeaders headers, MessageProperties target) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromHeadersToReply(MessageHeaders headers, MessageProperties target) {
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link RabbitStreamMessageHandler}.
|
||||
*
|
||||
* @param producerDestination the destination.
|
||||
* @param producerProperties the properties.
|
||||
* @param errorChannel the error channel
|
||||
* @param destination the destination.
|
||||
* @param extendedProperties the extended properties.
|
||||
* @param abstractApplicationContext the application context.
|
||||
* @param headerMapperFunction the header mapper function.
|
||||
* @return the handler.
|
||||
*/
|
||||
public static MessageHandler createStreamMessageHandler(ProducerDestination producerDestination,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> producerProperties, MessageChannel errorChannel,
|
||||
String destination, RabbitProducerProperties extendedProperties,
|
||||
AbstractApplicationContext applicationContext,
|
||||
Function<RabbitProducerProperties, AmqpHeaderMapper> headerMapperFunction) {
|
||||
|
||||
RabbitStreamTemplate template = new RabbitStreamTemplate(applicationContext.getBean(Environment.class),
|
||||
producerDestination.getName());
|
||||
String beanName = extendedProperties.getStreamMessageConverterBeanName();
|
||||
if (beanName != null) {
|
||||
template.setMessageConverter(applicationContext.getBean(beanName, MessageConverter.class));
|
||||
}
|
||||
beanName = extendedProperties.getStreamStreamMessageConverterBeanName();
|
||||
if (beanName != null) {
|
||||
template.setStreamConverter(applicationContext.getBean(beanName, StreamMessageConverter.class));
|
||||
}
|
||||
RabbitStreamMessageHandler handler = new RabbitStreamMessageHandler(template);
|
||||
if (errorChannel != null) {
|
||||
handler.setFailureCallback((msg, ex) -> {
|
||||
errorChannel.send(new ErrorMessage(new MessageHandlingException(msg, ex)));
|
||||
});
|
||||
}
|
||||
handler.setHeaderMapper(headerMapperFunction.apply(extendedProperties));
|
||||
handler.setSync(ProducerType.STREAM_SYNC.equals(producerProperties.getExtension().getProducerType()));
|
||||
return handler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.cloud.stream.config.BindingHandlerAdvise.MappingsProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration for extended binding metadata.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class ExtendedBindingHandlerMappingsProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
public MappingsProvider rabbitExtendedPropertiesDefaultMappingsProvider() {
|
||||
return () -> {
|
||||
Map<ConfigurationPropertyName, ConfigurationPropertyName> mappings = new HashMap<>();
|
||||
mappings.put(
|
||||
ConfigurationPropertyName.of("spring.cloud.stream.rabbit.bindings"),
|
||||
ConfigurationPropertyName.of("spring.cloud.stream.rabbit.default"));
|
||||
return mappings;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright 2015-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory.ConfirmType;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.Cloud;
|
||||
import org.springframework.cloud.CloudFactory;
|
||||
import org.springframework.cloud.service.messaging.RabbitConnectionFactoryConfig;
|
||||
import org.springframework.cloud.stream.binder.Binder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @author Marius Bogoevici
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Chris Bono
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(Binder.class)
|
||||
@Import({ RabbitMessageChannelBinderConfiguration.class,
|
||||
RabbitBinderConfiguration.RabbitHealthIndicatorConfiguration.class })
|
||||
public abstract class RabbitBinderConfiguration {
|
||||
|
||||
static void configureCachingConnectionFactory(
|
||||
CachingConnectionFactory connectionFactory,
|
||||
ConfigurableApplicationContext applicationContext,
|
||||
RabbitProperties rabbitProperties) throws Exception {
|
||||
|
||||
if (StringUtils.hasText(rabbitProperties.getAddresses())) {
|
||||
connectionFactory.setAddresses(rabbitProperties.determineAddresses());
|
||||
}
|
||||
|
||||
connectionFactory.setPublisherConfirmType(rabbitProperties.getPublisherConfirmType() == null ? ConfirmType.NONE : rabbitProperties.getPublisherConfirmType());
|
||||
connectionFactory.setPublisherReturns(rabbitProperties.isPublisherReturns());
|
||||
if (rabbitProperties.getCache().getChannel().getSize() != null) {
|
||||
connectionFactory.setChannelCacheSize(
|
||||
rabbitProperties.getCache().getChannel().getSize());
|
||||
}
|
||||
if (rabbitProperties.getCache().getConnection().getMode() != null) {
|
||||
connectionFactory
|
||||
.setCacheMode(rabbitProperties.getCache().getConnection().getMode());
|
||||
}
|
||||
if (rabbitProperties.getCache().getConnection().getSize() != null) {
|
||||
connectionFactory.setConnectionCacheSize(
|
||||
rabbitProperties.getCache().getConnection().getSize());
|
||||
}
|
||||
if (rabbitProperties.getCache().getChannel().getCheckoutTimeout() != null) {
|
||||
connectionFactory.setChannelCheckoutTimeout(rabbitProperties.getCache()
|
||||
.getChannel().getCheckoutTimeout().toMillis());
|
||||
}
|
||||
connectionFactory.setApplicationContext(applicationContext);
|
||||
applicationContext.addApplicationListener(connectionFactory);
|
||||
connectionFactory.afterPropertiesSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is set.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("cloud")
|
||||
protected static class CloudProfile {
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is set, and Cloud Connectors
|
||||
* are found on the classpath.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(Cloud.class)
|
||||
protected static class CloudConnectors {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Cloud cloud() {
|
||||
return new CloudFactory().getCloud();
|
||||
}
|
||||
|
||||
/**
|
||||
* Active only if {@code spring.cloud.stream.override-cloud-connectors} is not
|
||||
* set to {@code true}.
|
||||
*/
|
||||
// @checkstyle:off
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "spring.cloud.stream.override-cloud-connectors", havingValue = "false", matchIfMissing = true)
|
||||
// @checkstyle:on
|
||||
// Required to parse Rabbit properties which are passed to the binder for
|
||||
// clustering. We need to enable it here explicitly as the default Rabbit
|
||||
// configuration is not triggered.
|
||||
@EnableConfigurationProperties(RabbitProperties.class)
|
||||
protected static class UseCloudConnectors {
|
||||
|
||||
/**
|
||||
* Creates a {@link ConnectionFactory} using the singleton service
|
||||
* connector.
|
||||
* @param cloud {@link Cloud} instance to be used for accessing services.
|
||||
* @param connectorConfigObjectProvider the {@link ObjectProvider} for the
|
||||
* {@link RabbitConnectionFactoryConfig}.
|
||||
* @param applicationContext application context instance
|
||||
* @param rabbitProperties rabbit properties
|
||||
* @return the {@link ConnectionFactory} used by the binder.
|
||||
* @throws Exception if configuration of connection factory fails
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
ConnectionFactory rabbitConnectionFactory(Cloud cloud,
|
||||
ObjectProvider<RabbitConnectionFactoryConfig> connectorConfigObjectProvider,
|
||||
ConfigurableApplicationContext applicationContext,
|
||||
RabbitProperties rabbitProperties) throws Exception {
|
||||
|
||||
ConnectionFactory connectionFactory = cloud
|
||||
.getSingletonServiceConnector(ConnectionFactory.class,
|
||||
connectorConfigObjectProvider.getIfUnique());
|
||||
|
||||
configureCachingConnectionFactory(
|
||||
(CachingConnectionFactory) connectionFactory,
|
||||
applicationContext, rabbitProperties);
|
||||
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(RabbitTemplate.class)
|
||||
RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
return new RabbitTemplate(connectionFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used if
|
||||
* {@code spring.cloud.stream.override-cloud-connectors} is set to
|
||||
* {@code true}. Defers to Spring Boot auto-configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty("spring.cloud.stream.override-cloud-connectors")
|
||||
@Import(RabbitConfiguration.class)
|
||||
protected static class OverrideCloudConnectors {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingClass("org.springframework.cloud.Cloud")
|
||||
@Import(RabbitConfiguration.class)
|
||||
protected static class NoCloudConnectors {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration to be used when the cloud profile is not set. Defer to Spring Boot
|
||||
* auto-configuration.
|
||||
*/
|
||||
@Configuration
|
||||
@Profile("!cloud")
|
||||
@Import(RabbitConfiguration.class)
|
||||
protected static class NoCloudProfile {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for Rabbit health indicator.
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "org.springframework.boot.actuate.health.HealthIndicator")
|
||||
@ConditionalOnEnabledHealthIndicator("binders")
|
||||
public static class RabbitHealthIndicatorConfiguration {
|
||||
|
||||
@Bean
|
||||
public HealthIndicator binderHealthIndicator(RabbitTemplate rabbitTemplate) {
|
||||
return new RabbitHealthIndicator(rabbitTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2015-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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 java.util.stream.Collectors;
|
||||
|
||||
import com.rabbitmq.client.impl.CredentialsProvider;
|
||||
import com.rabbitmq.client.impl.CredentialsRefreshService;
|
||||
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
|
||||
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
|
||||
import org.springframework.amqp.rabbit.core.RabbitOperations;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.amqp.CachingConnectionFactoryConfigurer;
|
||||
import org.springframework.boot.autoconfigure.amqp.ConnectionFactoryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionFactoryBeanConfigurer;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitRetryTemplateCustomizer;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitTemplateConfigurer;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* Configuration for {@link RabbitTemplate} and {@link CachingConnectionFactory}.
|
||||
*
|
||||
* @author Chris Bono
|
||||
* @since 3.2
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(RabbitProperties.class)
|
||||
public class RabbitConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer(RabbitProperties properties,
|
||||
ResourceLoader resourceLoader, ObjectProvider<CredentialsProvider> credentialsProvider,
|
||||
ObjectProvider<CredentialsRefreshService> credentialsRefreshService) {
|
||||
RabbitConnectionFactoryBeanConfigurer configurer = new RabbitConnectionFactoryBeanConfigurer(resourceLoader, properties);
|
||||
configurer.setCredentialsProvider(credentialsProvider.getIfUnique());
|
||||
configurer.setCredentialsRefreshService(credentialsRefreshService.getIfUnique());
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
CachingConnectionFactoryConfigurer rabbitConnectionFactoryConfigurer(RabbitProperties rabbitProperties,
|
||||
ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
|
||||
CachingConnectionFactoryConfigurer configurer = new CachingConnectionFactoryConfigurer(rabbitProperties);
|
||||
configurer.setConnectionNameStrategy(connectionNameStrategy.getIfUnique());
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionFactory.class)
|
||||
CachingConnectionFactory rabbitConnectionFactory(
|
||||
RabbitConnectionFactoryBeanConfigurer rabbitConnectionFactoryBeanConfigurer,
|
||||
CachingConnectionFactoryConfigurer rabbitCachingConnectionFactoryConfigurer,
|
||||
ObjectProvider<ConnectionFactoryCustomizer> connectionFactoryCustomizers) throws Exception {
|
||||
|
||||
RabbitConnectionFactoryBean connectionFactoryBean = new RabbitConnectionFactoryBean();
|
||||
rabbitConnectionFactoryBeanConfigurer.configure(connectionFactoryBean);
|
||||
connectionFactoryBean.afterPropertiesSet();
|
||||
com.rabbitmq.client.ConnectionFactory connectionFactory = connectionFactoryBean.getObject();
|
||||
connectionFactoryCustomizers.orderedStream()
|
||||
.forEach((customizer) -> customizer.customize(connectionFactory));
|
||||
|
||||
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(connectionFactory);
|
||||
rabbitCachingConnectionFactoryConfigurer.configure(cachingConnectionFactory);
|
||||
|
||||
return cachingConnectionFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RabbitTemplateConfigurer rabbitTemplateConfigurer(RabbitProperties properties,
|
||||
ObjectProvider<MessageConverter> messageConverter,
|
||||
ObjectProvider<RabbitRetryTemplateCustomizer> retryTemplateCustomizers) {
|
||||
RabbitTemplateConfigurer configurer = new RabbitTemplateConfigurer(properties);
|
||||
configurer.setMessageConverter(messageConverter.getIfUnique());
|
||||
configurer.setRetryTemplateCustomizers(retryTemplateCustomizers.orderedStream().collect(Collectors.toList()));
|
||||
return configurer;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnSingleCandidate(ConnectionFactory.class)
|
||||
@ConditionalOnMissingBean(RabbitOperations.class)
|
||||
public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate template = new RabbitTemplate();
|
||||
configurer.configure(template, connectionFactory);
|
||||
return template;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2015-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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 java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.springframework.amqp.core.DeclarableCustomizer;
|
||||
import org.springframework.amqp.core.MessagePostProcessor;
|
||||
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
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.amqp.RabbitProperties;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitBinderConfigurationProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitExtendedBindingProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.provisioning.RabbitExchangeQueueProvisioner;
|
||||
import org.springframework.cloud.stream.config.ConsumerEndpointCustomizer;
|
||||
import org.springframework.cloud.stream.config.ListenerContainerCustomizer;
|
||||
import org.springframework.cloud.stream.config.MessageSourceCustomizer;
|
||||
import org.springframework.cloud.stream.config.ProducerMessageHandlerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
|
||||
import org.springframework.integration.amqp.inbound.AmqpMessageSource;
|
||||
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration class for RabbitMQ message channel binder.
|
||||
*
|
||||
* @author David Turanski
|
||||
* @author Vinicius Carvalho
|
||||
* @author Artem Bilan
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
*/
|
||||
@Configuration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ RabbitBinderConfigurationProperties.class,
|
||||
RabbitExtendedBindingProperties.class })
|
||||
public class RabbitMessageChannelBinderConfiguration {
|
||||
|
||||
@Autowired
|
||||
private ConnectionFactory rabbitConnectionFactory;
|
||||
|
||||
@Autowired
|
||||
private RabbitProperties rabbitProperties;
|
||||
|
||||
@Autowired
|
||||
private RabbitBinderConfigurationProperties rabbitBinderConfigurationProperties;
|
||||
|
||||
@Autowired
|
||||
private RabbitExtendedBindingProperties rabbitExtendedBindingProperties;
|
||||
|
||||
@Bean
|
||||
RabbitMessageChannelBinder rabbitMessageChannelBinder(
|
||||
@Nullable ListenerContainerCustomizer<MessageListenerContainer> listenerContainerCustomizer,
|
||||
@Nullable MessageSourceCustomizer<AmqpMessageSource> sourceCustomizer,
|
||||
@Nullable ProducerMessageHandlerCustomizer<AmqpOutboundEndpoint> producerMessageHandlerCustomizer,
|
||||
@Nullable ConsumerEndpointCustomizer<AmqpInboundChannelAdapter> consumerCustomizer,
|
||||
List<DeclarableCustomizer> declarableCustomizers,
|
||||
@Nullable ConnectionNameStrategy connectionNameStrategy) {
|
||||
|
||||
String connectionNamePrefix = this.rabbitBinderConfigurationProperties.getConnectionNamePrefix();
|
||||
if (this.rabbitConnectionFactory instanceof AbstractConnectionFactory && connectionNamePrefix != null && connectionNameStrategy == null) {
|
||||
final AtomicInteger nameIncrementer = new AtomicInteger();
|
||||
((AbstractConnectionFactory) this.rabbitConnectionFactory).setConnectionNameStrategy(f -> connectionNamePrefix
|
||||
+ "#" + nameIncrementer.getAndIncrement());
|
||||
}
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(
|
||||
this.rabbitConnectionFactory, this.rabbitProperties,
|
||||
provisioningProvider(declarableCustomizers), listenerContainerCustomizer, sourceCustomizer);
|
||||
binder.setAdminAddresses(
|
||||
this.rabbitBinderConfigurationProperties.getAdminAddresses());
|
||||
binder.setCompressingPostProcessor(gZipPostProcessor());
|
||||
binder.setDecompressingPostProcessor(deCompressingPostProcessor());
|
||||
binder.setNodes(this.rabbitBinderConfigurationProperties.getNodes());
|
||||
binder.setExtendedBindingProperties(this.rabbitExtendedBindingProperties);
|
||||
binder.setProducerMessageHandlerCustomizer(producerMessageHandlerCustomizer);
|
||||
binder.setConsumerEndpointCustomizer(consumerCustomizer);
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor deCompressingPostProcessor() {
|
||||
return new DelegatingDecompressingPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MessagePostProcessor gZipPostProcessor() {
|
||||
GZipPostProcessor gZipPostProcessor = new GZipPostProcessor();
|
||||
gZipPostProcessor
|
||||
.setLevel(this.rabbitBinderConfigurationProperties.getCompressionLevel());
|
||||
return gZipPostProcessor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RabbitExchangeQueueProvisioner provisioningProvider(List<DeclarableCustomizer> customizers) {
|
||||
return new RabbitExchangeQueueProvisioner(this.rabbitConnectionFactory, customizers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.deployer;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
|
||||
/**
|
||||
* Main application class to trigger the deployment of Java Functions based
|
||||
* user application with RabitMQ bindings.
|
||||
* <br>
|
||||
* <br>
|
||||
* It is based on Spring Cloud Function
|
||||
* <a href="https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_deploying_a_packaged_function">deploy feature</a>
|
||||
*
|
||||
* <br>
|
||||
* All you need to do is to provide few additional properties to assist Spring Cloud Function deployer.
|
||||
* <br>
|
||||
* For example:
|
||||
* <br>
|
||||
* <pre class="code">
|
||||
*
|
||||
* java -jar spring-cloud-stream-binder-rabbit-deployer/target/spring-cloud-stream-binder-rabbit-deployer-3.2.0-SNAPSHOT.jar\
|
||||
* --spring.cloud.function.location=/foo/bar/simplestjar-1.0.0.RELEASE.jar
|
||||
* --spring.cloud.function.function-class=function.example.UpperCaseFunction
|
||||
*
|
||||
* </pre>
|
||||
* The only required property is 'spring.cloud.function.location'. Every other property is optional and is based on the style of user application.
|
||||
* For more details on the supported styles of user application please refer to
|
||||
* <a href="https://docs.spring.io/spring-cloud-function/docs/3.1.3/reference/html/spring-cloud-function.html#_deploying_a_packaged_function">
|
||||
* Deploying a Packaged Function</a> section of Spring Cloud Function.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.1.2
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class RabbitDeployer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RabbitDeployer.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.deployer;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
class RabbitDeployerEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment,
|
||||
SpringApplication application) {
|
||||
if (!environment.containsProperty("spring.cloud.function.rsocket.enabled")) {
|
||||
environment.getSystemProperties().putIfAbsent("spring.cloud.function.rsocket.enabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
rabbit:\
|
||||
org.springframework.cloud.stream.binder.rabbit.config.RabbitBinderConfiguration
|
||||
@@ -0,0 +1,4 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration:\
|
||||
org.springframework.cloud.stream.binder.rabbit.config.ExtendedBindingHandlerMappingsProviderConfiguration
|
||||
org.springframework.boot.env.EnvironmentPostProcessor:\
|
||||
org.springframework.cloud.stream.binder.rabbit.deployer.RabbitDeployerEnvironmentPostProcessor
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
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.binder.test.junit.rabbit.RabbitTestSupport;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class LocalizedQueueConnectionFactoryIntegrationTests {
|
||||
|
||||
@RegisterExtension
|
||||
public static RabbitTestSupport rabbitAvailableRule = new RabbitTestSupport(true);
|
||||
|
||||
private LocalizedQueueConnectionFactory lqcf;
|
||||
|
||||
@BeforeEach
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
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 com.rabbitmq.http.client.Client;
|
||||
import com.rabbitmq.http.client.domain.QueueInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import org.springframework.amqp.core.Base64UrlNamingStrategy;
|
||||
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.binder.rabbit.admin.RabbitAdminException;
|
||||
import org.springframework.cloud.stream.binder.rabbit.admin.RabbitBindingCleaner;
|
||||
import org.springframework.cloud.stream.binder.test.junit.rabbit.RabbitTestSupport;
|
||||
|
||||
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.";
|
||||
|
||||
private static final Client client;
|
||||
|
||||
static {
|
||||
try {
|
||||
client = new Client("http://localhost:15672/api", "guest", "guest");
|
||||
}
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new RabbitAdminException("Couldn't create a Client", e);
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
public RabbitTestSupport rabbitWithMgmtEnabled = new RabbitTestSupport(true);
|
||||
|
||||
@Test
|
||||
public void testCleanStream() {
|
||||
final RabbitBindingCleaner cleaner = new RabbitBindingCleaner();
|
||||
final String stream1 = new Base64UrlNamingStrategy("foo").generateName();
|
||||
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;
|
||||
}
|
||||
rabbitAdmin.declareQueue(new Queue(queue1Name, true, false, false));
|
||||
rabbitAdmin.declareQueue(new Queue(queue2Name, true, false, false));
|
||||
rabbitAdmin.declareQueue(new Queue(AbstractBinder.constructDLQName(queue1Name), true, false, false));
|
||||
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<Void>() {
|
||||
|
||||
@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, long state) throws InterruptedException {
|
||||
int n = 0;
|
||||
QueueInfo queue = client.getQueue("/", queueName);
|
||||
while (n++ < 100 && (queue == null || queue.getConsumerCount() == state)) {
|
||||
Thread.sleep(100);
|
||||
queue = client.getQueue("/", queueName);
|
||||
}
|
||||
assertThat(n).withFailMessage(
|
||||
"Consumer state remained at " + state + " after 10 seconds")
|
||||
.isLessThan(100);
|
||||
}
|
||||
|
||||
});
|
||||
rabbitAdmin.deleteExchange(topic1.getName()); // easier than deleting the binding
|
||||
rabbitAdmin.declareExchange(topic1);
|
||||
rabbitAdmin.deleteQueue(foreignQueue);
|
||||
connectionFactory.destroy();
|
||||
Map<String, List<String>> cleanedMap = cleaner.clean(stream1, false);
|
||||
assertThat(cleanedMap).hasSize(2);
|
||||
List<String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2015-2018 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.AbstractPollableConsumerTestBinder;
|
||||
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.PollableSource;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitCommonProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.provisioning.RabbitExchangeQueueProvisioner;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Test support class for {@link RabbitMessageChannelBinder}.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @author Gary Russell
|
||||
* @author David Turanski
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
// @checkstyle:off
|
||||
public class RabbitTestBinder extends
|
||||
AbstractPollableConsumerTestBinder<RabbitMessageChannelBinder, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> {
|
||||
|
||||
// @checkstyle:on
|
||||
private final RabbitAdmin rabbitAdmin;
|
||||
|
||||
private final Set<String> prefixes = new HashSet<>();
|
||||
|
||||
private final Set<String> queues = new HashSet<String>();
|
||||
|
||||
private final Set<String> exchanges = new HashSet<String>();
|
||||
|
||||
private final AnnotationConfigApplicationContext applicationContext;
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory,
|
||||
RabbitProperties rabbitProperties) {
|
||||
this(connectionFactory, new RabbitMessageChannelBinder(connectionFactory,
|
||||
rabbitProperties, new RabbitExchangeQueueProvisioner(connectionFactory)));
|
||||
}
|
||||
|
||||
public RabbitTestBinder(ConnectionFactory connectionFactory,
|
||||
RabbitMessageChannelBinder binder) {
|
||||
this.applicationContext = new AnnotationConfigApplicationContext(Config.class);
|
||||
binder.setApplicationContext(this.applicationContext);
|
||||
this.setPollableConsumerBinder(binder);
|
||||
this.rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
}
|
||||
|
||||
public AnnotationConfigApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindConsumer(String name, String group,
|
||||
MessageChannel moduleInputChannel,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
captureConsumerResources(name, group, properties);
|
||||
return super.bindConsumer(name, group, moduleInputChannel, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<PollableSource<MessageHandler>> bindPollableConsumer(String name,
|
||||
String group, PollableSource<MessageHandler> inboundBindTarget,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
captureConsumerResources(name, group, properties);
|
||||
return super.bindPollableConsumer(name, group, inboundBindTarget, properties);
|
||||
}
|
||||
|
||||
private void captureConsumerResources(String name, String group,
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
|
||||
String[] names = null;
|
||||
if (group != null) {
|
||||
if (properties.getExtension().isQueueNameGroupOnly()) {
|
||||
this.queues.add(properties.getExtension().getPrefix() + group);
|
||||
}
|
||||
else {
|
||||
if (properties.isMultiplex()) {
|
||||
names = StringUtils.commaDelimitedListToStringArray(name);
|
||||
for (String nayme : names) {
|
||||
this.queues.add(properties.getExtension().getPrefix()
|
||||
+ nayme.trim() + "." + group);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.queues.add(
|
||||
properties.getExtension().getPrefix() + name + "." + group);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (names != null) {
|
||||
for (String nayme : names) {
|
||||
this.exchanges.add(properties.getExtension().getPrefix() + nayme.trim());
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.exchanges.add(properties.getExtension().getPrefix() + name);
|
||||
}
|
||||
this.prefixes.add(properties.getExtension().getPrefix());
|
||||
deadLetters(properties.getExtension());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Binding<MessageChannel> bindProducer(String name,
|
||||
MessageChannel moduleOutputChannel,
|
||||
ExtendedProducerProperties<RabbitProducerProperties> properties) {
|
||||
this.queues.add(properties.getExtension().getPrefix() + name + ".default");
|
||||
this.exchanges.add(properties.getExtension().getPrefix() + name);
|
||||
if (properties.getRequiredGroups() != null) {
|
||||
for (String group : properties.getRequiredGroups()) {
|
||||
if (properties.getExtension().isQueueNameGroupOnly()) {
|
||||
this.queues.add(properties.getExtension().getPrefix() + group);
|
||||
}
|
||||
else {
|
||||
this.queues.add(
|
||||
properties.getExtension().getPrefix() + name + "." + group);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.prefixes.add(properties.getExtension().getPrefix());
|
||||
deadLetters(properties.getExtension());
|
||||
return super.bindProducer(name, moduleOutputChannel, properties);
|
||||
}
|
||||
|
||||
private void deadLetters(RabbitCommonProperties properties) {
|
||||
if (properties.getDeadLetterExchange() != null) {
|
||||
this.exchanges.add(properties.getDeadLetterExchange());
|
||||
}
|
||||
if (properties.getDeadLetterQueueName() != null) {
|
||||
this.queues.add(properties.getDeadLetterQueueName());
|
||||
}
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
this.applicationContext.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableIntegration
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2020-2020 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.Collections;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.amqp.rabbit.connection.Connection;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.provisioning.ConsumerDestination;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.support.ErrorMessage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.0.6
|
||||
*
|
||||
*/
|
||||
public class RepublishUnitTests {
|
||||
|
||||
@Test
|
||||
public void testBadRepublishSetting() throws IOException {
|
||||
ConnectionFactory cf = mock(ConnectionFactory.class);
|
||||
Connection conn = mock(Connection.class);
|
||||
given(cf.createConnection()).willReturn(conn);
|
||||
Channel channel = mock(Channel.class);
|
||||
given(channel.isOpen()).willReturn(true);
|
||||
given(channel.exchangeDeclarePassive("DLX")).willThrow(new IOException());
|
||||
given(conn.createChannel(false)).willReturn(channel);
|
||||
RabbitProperties props = new RabbitProperties();
|
||||
RabbitMessageChannelBinder binder = new RabbitMessageChannelBinder(cf, props, null);
|
||||
RabbitConsumerProperties extension = new RabbitConsumerProperties();
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> bindingProps =
|
||||
new ExtendedConsumerProperties<RabbitConsumerProperties>(extension);
|
||||
MessageHandler handler = binder.getErrorMessageHandler(mock(ConsumerDestination.class), "foo", bindingProps);
|
||||
ErrorMessage message = new ErrorMessage(new RuntimeException("test"),
|
||||
Collections.singletonMap(IntegrationMessageHeaderAccessor.SOURCE_DATA,
|
||||
new Message("foo".getBytes(), new MessageProperties())));
|
||||
handler.handleMessage(message);
|
||||
handler.handleMessage(message);
|
||||
verify(channel, times(1)).exchangeDeclarePassive("DLX");
|
||||
verify(channel, never()).basicPublish(any(), any(), eq(false), any(), any());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.rabbitmq.http.client.Client;
|
||||
import com.rabbitmq.http.client.domain.BindingInfo;
|
||||
import com.rabbitmq.http.client.domain.ExchangeInfo;
|
||||
import com.rabbitmq.http.client.domain.QueueInfo;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.amqp.core.DeclarableCustomizer;
|
||||
import org.springframework.amqp.core.ExchangeTypes;
|
||||
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionNameStrategy;
|
||||
import org.springframework.amqp.rabbit.core.RabbitAdmin;
|
||||
import org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.utils.test.TestUtils;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.CompositeHealthContributor;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.Cloud;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.cloud.stream.annotation.Input;
|
||||
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.ExtendedConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
|
||||
import org.springframework.cloud.stream.binder.PollableMessageSource;
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.test.junit.rabbit.RabbitTestSupport;
|
||||
import org.springframework.cloud.stream.binding.BindingService;
|
||||
import org.springframework.cloud.stream.config.ConsumerEndpointCustomizer;
|
||||
import org.springframework.cloud.stream.config.ListenerContainerCustomizer;
|
||||
import org.springframework.cloud.stream.config.MessageSourceCustomizer;
|
||||
import org.springframework.cloud.stream.config.ProducerMessageHandlerCustomizer;
|
||||
import org.springframework.cloud.stream.messaging.Processor;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
|
||||
import org.springframework.integration.amqp.inbound.AmqpMessageSource;
|
||||
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Marius Bogoevici
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Soby Chacko
|
||||
*/
|
||||
public class RabbitBinderModuleTests {
|
||||
|
||||
@RegisterExtension
|
||||
public static RabbitTestSupport rabbitTestSupport = new RabbitTestSupport();
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
public static final ConnectionFactory MOCK_CONNECTION_FACTORY = mock(
|
||||
ConnectionFactory.class, Mockito.RETURNS_MOCKS);
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
context = null;
|
||||
}
|
||||
RabbitAdmin admin = new RabbitAdmin(rabbitTestSupport.getResource());
|
||||
admin.deleteExchange("input");
|
||||
admin.deleteExchange("output");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentConnectionFactoryInheritedByDefault() throws Exception {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE).run("--server.port=0",
|
||||
"--spring.cloud.stream.rabbit.binder.connection-name-prefix=foo",
|
||||
"--spring.cloud.stream.rabbit.bindings.input.consumer.single-active-consumer=true");
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?, ?, ?> binder = binderFactory.getBinder(null, MessageChannel.class);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
CachingConnectionFactory binderConnectionFactory = (CachingConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
assertThat(binderConnectionFactory).isInstanceOf(CachingConnectionFactory.class);
|
||||
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
|
||||
assertThat(binderConnectionFactory).isSameAs(connectionFactory);
|
||||
|
||||
CompositeHealthContributor bindersHealthIndicator = context
|
||||
.getBean("bindersHealthContributor", CompositeHealthContributor.class);
|
||||
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
|
||||
RabbitHealthIndicator indicator = (RabbitHealthIndicator) bindersHealthIndicator.getContributor("rabbit");
|
||||
assertThat(indicator).isNotNull();
|
||||
assertThat(indicator.health().getStatus())
|
||||
.isEqualTo(Status.UP);
|
||||
|
||||
ConnectionFactory publisherConnectionFactory = binderConnectionFactory
|
||||
.getPublisherConnectionFactory();
|
||||
assertThat(TestUtils.getPropertyValue(publisherConnectionFactory,
|
||||
"connection.target")).isNull();
|
||||
DirectChannel checkPf = new DirectChannel();
|
||||
Binding<MessageChannel> binding = ((RabbitMessageChannelBinder) binder)
|
||||
.bindProducer("checkPF", checkPf,
|
||||
new ExtendedProducerProperties<>(new RabbitProducerProperties()));
|
||||
checkPf.send(new GenericMessage<>("foo".getBytes()));
|
||||
binding.unbind();
|
||||
assertThat(TestUtils.getPropertyValue(publisherConnectionFactory,
|
||||
"connection.target")).isNotNull();
|
||||
|
||||
CachingConnectionFactory cf = this.context
|
||||
.getBean(CachingConnectionFactory.class);
|
||||
ConnectionNameStrategy cns = TestUtils.getPropertyValue(cf,
|
||||
"connectionNameStrategy", ConnectionNameStrategy.class);
|
||||
assertThat(cns.obtainNewConnectionName(cf)).isEqualTo("foo#2");
|
||||
new RabbitAdmin(rabbitTestSupport.getResource()).deleteExchange("checkPF");
|
||||
checkCustomizedArgs();
|
||||
binderConnectionFactory.resetConnection();
|
||||
binderConnectionFactory.createConnection();
|
||||
checkCustomizedArgs();
|
||||
}
|
||||
|
||||
private void checkCustomizedArgs() throws MalformedURLException, URISyntaxException, InterruptedException {
|
||||
Client client = new Client("http://guest:guest@localhost:15672/api");
|
||||
List<BindingInfo> bindings = client.getBindingsBySource("/", "input");
|
||||
int n = 0;
|
||||
while (n++ < 100 && bindings == null || bindings.size() < 1) {
|
||||
Thread.sleep(100);
|
||||
bindings = client.getBindingsBySource("/", "input");
|
||||
}
|
||||
assertThat(bindings).isNotNull();
|
||||
assertThat(bindings.get(0).getArguments()).contains(entry("added.by", "customizer"));
|
||||
ExchangeInfo exchange = client.getExchange("/", "input");
|
||||
assertThat(exchange.getArguments()).contains(entry("added.by", "customizer"));
|
||||
QueueInfo queue = client.getQueue("/", bindings.get(0).getDestination());
|
||||
assertThat(queue.getArguments()).contains(entry("added.by", "customizer"));
|
||||
assertThat(queue.getArguments()).contains(entry("x-single-active-consumer", Boolean.TRUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testParentConnectionFactoryInheritedByDefaultAndRabbitSettingsPropagated() {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE).run("--server.port=0",
|
||||
"--spring.cloud.stream.bindings.source.group=someGroup",
|
||||
"--spring.cloud.stream.bindings.input.group=someGroup",
|
||||
"--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, MessageChannel.class);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
BindingService bindingService = context.getBean(BindingService.class);
|
||||
DirectFieldAccessor channelBindingServiceAccessor = new DirectFieldAccessor(
|
||||
bindingService);
|
||||
// @checkstyle:off
|
||||
Map<String, List<Binding<MessageChannel>>> consumerBindings = (Map<String, List<Binding<MessageChannel>>>) channelBindingServiceAccessor
|
||||
.getPropertyValue("consumerBindings");
|
||||
// @checkstyle:on
|
||||
Binding<MessageChannel> inputBinding = consumerBindings.get("input").get(0);
|
||||
assertThat(TestUtils.getPropertyValue(inputBinding, "lifecycle.beanName"))
|
||||
.isEqualTo("setByCustomizer:someGroup");
|
||||
SimpleMessageListenerContainer container = TestUtils.getPropertyValue(
|
||||
inputBinding, "lifecycle.messageListenerContainer",
|
||||
SimpleMessageListenerContainer.class);
|
||||
assertThat(TestUtils.getPropertyValue(container, "beanName"))
|
||||
.isEqualTo("setByCustomizerForQueue:input.someGroup,andGroup:someGroup");
|
||||
assertThat(TestUtils.getPropertyValue(container, "transactional", Boolean.class))
|
||||
.isTrue();
|
||||
Map<String, Binding<MessageChannel>> producerBindings = (Map<String, Binding<MessageChannel>>) TestUtils
|
||||
.getPropertyValue(bindingService, "producerBindings");
|
||||
Binding<MessageChannel> outputBinding = producerBindings.get("output");
|
||||
assertThat(TestUtils.getPropertyValue(outputBinding,
|
||||
"lifecycle.amqpTemplate.transactional", Boolean.class)).isTrue();
|
||||
assertThat(TestUtils.getPropertyValue(outputBinding, "lifecycle.beanName"))
|
||||
.isEqualTo("setByCustomizer:output");
|
||||
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);
|
||||
CompositeHealthContributor bindersHealthIndicator = context
|
||||
.getBean("bindersHealthContributor", CompositeHealthContributor.class);
|
||||
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
|
||||
RabbitHealthIndicator indicator = (RabbitHealthIndicator) bindersHealthIndicator.getContributor("rabbit");
|
||||
assertThat(indicator).isNotNull();
|
||||
assertThat(indicator.health().getStatus())
|
||||
.isEqualTo(Status.UP);
|
||||
|
||||
CachingConnectionFactory cf = this.context
|
||||
.getBean(CachingConnectionFactory.class);
|
||||
ConnectionNameStrategy cns = TestUtils.getPropertyValue(cf,
|
||||
"connectionNameStrategy", ConnectionNameStrategy.class);
|
||||
assertThat(cns.obtainNewConnectionName(cf)).startsWith("rabbitConnectionFactory");
|
||||
assertThat(TestUtils.getPropertyValue(consumerBindings.get("source").get(0),
|
||||
"target.source.h.advised.targetSource.target.beanName"))
|
||||
.isEqualTo("setByCustomizer:someGroup");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentConnectionFactoryInheritedIfOverridden() {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class,
|
||||
ConnectionFactoryConfiguration.class).web(WebApplicationType.NONE)
|
||||
.run("--server.port=0");
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?, ?, ?> binder = binderFactory.getBinder(null, MessageChannel.class);
|
||||
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);
|
||||
CompositeHealthContributor bindersHealthIndicator = context
|
||||
.getBean("bindersHealthContributor", CompositeHealthContributor.class);
|
||||
assertThat(bindersHealthIndicator).isNotNull();
|
||||
RabbitHealthIndicator indicator = (RabbitHealthIndicator) bindersHealthIndicator.getContributor("rabbit");
|
||||
assertThat(indicator).isNotNull();
|
||||
// mock connection factory behaves as if down
|
||||
assertThat(indicator.health().getStatus())
|
||||
.isEqualTo(Status.DOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParentConnectionFactoryNotInheritedByCustomizedBindersAndProducerRetryBootProperties() {
|
||||
List<String> 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");
|
||||
params.add("--spring.rabbitmq.template.retry.enabled=true");
|
||||
params.add("--spring.rabbitmq.template.retry.maxAttempts=2");
|
||||
params.add("--spring.rabbitmq.template.retry.initial-interval=1000");
|
||||
params.add("--spring.rabbitmq.template.retry.multiplier=1.1");
|
||||
params.add("--spring.rabbitmq.template.retry.max-interval=3000");
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.run(params.toArray(new String[params.size()]));
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
// @checkstyle:off
|
||||
@SuppressWarnings("unchecked")
|
||||
Binder<MessageChannel, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>> binder = (Binder<MessageChannel, ExtendedConsumerProperties<RabbitConsumerProperties>, ExtendedProducerProperties<RabbitProducerProperties>>) binderFactory
|
||||
.getBinder(null, MessageChannel.class);
|
||||
// @checkstyle:on
|
||||
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);
|
||||
CompositeHealthContributor bindersHealthIndicator = context
|
||||
.getBean("bindersHealthContributor", CompositeHealthContributor.class);
|
||||
assertThat(bindersHealthIndicator);
|
||||
|
||||
RabbitHealthIndicator indicator = (RabbitHealthIndicator) bindersHealthIndicator.getContributor("custom");
|
||||
assertThat(indicator).isNotNull();
|
||||
assertThat(indicator.health().getStatus()).isEqualTo(Status.UP);
|
||||
String name = UUID.randomUUID().toString();
|
||||
Binding<MessageChannel> binding = binder.bindProducer(name, new DirectChannel(),
|
||||
new ExtendedProducerProperties<>(new RabbitProducerProperties()));
|
||||
RetryTemplate template = TestUtils.getPropertyValue(binding,
|
||||
"lifecycle.amqpTemplate.retryTemplate", RetryTemplate.class);
|
||||
assertThat(template).isNotNull();
|
||||
SimpleRetryPolicy retryPolicy = TestUtils.getPropertyValue(template,
|
||||
"retryPolicy", SimpleRetryPolicy.class);
|
||||
ExponentialBackOffPolicy backOff = TestUtils.getPropertyValue(template,
|
||||
"backOffPolicy", ExponentialBackOffPolicy.class);
|
||||
assertThat(retryPolicy.getMaxAttempts()).isEqualTo(2);
|
||||
assertThat(backOff.getInitialInterval()).isEqualTo(1000L);
|
||||
assertThat(backOff.getMultiplier()).isEqualTo(1.1);
|
||||
assertThat(backOff.getMaxInterval()).isEqualTo(3000L);
|
||||
binding.unbind();
|
||||
new RabbitAdmin(rabbitTestSupport.getResource()).deleteExchange(name);
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloudProfile() {
|
||||
this.context = new SpringApplicationBuilder(SimpleProcessor.class,
|
||||
MockCloudConfiguration.class).web(WebApplicationType.NONE)
|
||||
.profiles("cloud").run();
|
||||
BinderFactory binderFactory = this.context.getBean(BinderFactory.class);
|
||||
Binder<?, ?, ?> binder = binderFactory.getBinder(null, MessageChannel.class);
|
||||
assertThat(binder).isInstanceOf(RabbitMessageChannelBinder.class);
|
||||
DirectFieldAccessor binderFieldAccessor = new DirectFieldAccessor(binder);
|
||||
ConnectionFactory binderConnectionFactory = (ConnectionFactory) binderFieldAccessor
|
||||
.getPropertyValue("connectionFactory");
|
||||
ConnectionFactory connectionFactory = this.context
|
||||
.getBean(ConnectionFactory.class);
|
||||
|
||||
assertThat(binderConnectionFactory).isNotSameAs(connectionFactory);
|
||||
|
||||
assertThat(TestUtils.getPropertyValue(connectionFactory, "addresses"))
|
||||
.isNotNull();
|
||||
assertThat(TestUtils.getPropertyValue(binderConnectionFactory, "addresses"))
|
||||
.isNull();
|
||||
|
||||
Cloud cloud = this.context.getBean(Cloud.class);
|
||||
|
||||
verify(cloud).getSingletonServiceConnector(ConnectionFactory.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedProperties() {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE).run("--server.port=0",
|
||||
"--spring.cloud.stream.rabbit.default.producer.routing-key-expression=fooRoutingKey",
|
||||
"--spring.cloud.stream.rabbit.default.consumer.exchange-type=direct",
|
||||
"--spring.cloud.stream.rabbit.bindings.output.producer.batch-size=512",
|
||||
"--spring.cloud.stream.rabbit.default.consumer.max-concurrency=4",
|
||||
"--spring.cloud.stream.rabbit.bindings.input.consumer.exchange-type=fanout");
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
Binder<?, ?, ?> rabbitBinder = binderFactory.getBinder(null,
|
||||
MessageChannel.class);
|
||||
|
||||
RabbitProducerProperties rabbitProducerProperties = (RabbitProducerProperties) ((ExtendedPropertiesBinder) rabbitBinder)
|
||||
.getExtendedProducerProperties("output");
|
||||
|
||||
assertThat(
|
||||
rabbitProducerProperties.getRoutingKeyExpression().getExpressionString())
|
||||
.isEqualTo("fooRoutingKey");
|
||||
assertThat(rabbitProducerProperties.getBatchSize()).isEqualTo(512);
|
||||
|
||||
RabbitConsumerProperties rabbitConsumerProperties = (RabbitConsumerProperties) ((ExtendedPropertiesBinder) rabbitBinder)
|
||||
.getExtendedConsumerProperties("input");
|
||||
|
||||
assertThat(rabbitConsumerProperties.getExchangeType())
|
||||
.isEqualTo(ExchangeTypes.FANOUT);
|
||||
assertThat(rabbitConsumerProperties.getMaxConcurrency()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@EnableBinding({ Processor.class, PMS.class })
|
||||
@SpringBootApplication
|
||||
public static class SimpleProcessor {
|
||||
|
||||
@Bean
|
||||
public ListenerContainerCustomizer<MessageListenerContainer> containerCustomizer() {
|
||||
return (c, q, g) -> ((AbstractMessageListenerContainer) c).setBeanName(
|
||||
"setByCustomizerForQueue:" + q + (g == null ? "" : ",andGroup:" + g));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MessageSourceCustomizer<AmqpMessageSource> sourceCustomizer() {
|
||||
return (s, q, g) -> s.setBeanName("setByCustomizer:" + g);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ProducerMessageHandlerCustomizer<AmqpOutboundEndpoint> messageHandlerCustomizer() {
|
||||
return (handler, destinationName) -> handler.setBeanName("setByCustomizer:" + destinationName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerEndpointCustomizer<AmqpInboundChannelAdapter> adapterCustomizer() {
|
||||
return (producer, dest, grp) -> producer.setBeanName("setByCustomizer:" + grp);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DeclarableCustomizer customizer() {
|
||||
return dec -> {
|
||||
dec.addArgument("added.by", "customizer");
|
||||
return dec;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectionFactoryConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return MOCK_CONNECTION_FACTORY;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MockCloudConfiguration {
|
||||
|
||||
@Bean
|
||||
public Cloud cloud() {
|
||||
Cloud cloud = mock(Cloud.class);
|
||||
|
||||
willReturn(new CachingConnectionFactory("localhost")).given(cloud)
|
||||
.getSingletonServiceConnector(ConnectionFactory.class, null);
|
||||
|
||||
return cloud;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface PMS {
|
||||
|
||||
@Input
|
||||
PollableMessageSource source();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.stream;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractIntegrationTests {
|
||||
|
||||
static final GenericContainer<?> RABBITMQ;
|
||||
|
||||
static {
|
||||
if (System.getProperty("spring.rabbit.use.local.server") == null) {
|
||||
String image = "pivotalrabbitmq/rabbitmq-stream";
|
||||
String cache = System.getenv().get("IMAGE_CACHE");
|
||||
if (cache != null) {
|
||||
image = cache + image;
|
||||
}
|
||||
RABBITMQ = new GenericContainer<>(DockerImageName.parse(image))
|
||||
.withExposedPorts(5672, 15672, 5552)
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
RABBITMQ.start();
|
||||
}
|
||||
else {
|
||||
RABBITMQ = null;
|
||||
}
|
||||
}
|
||||
|
||||
static int amqpPort() {
|
||||
return RABBITMQ != null ? RABBITMQ.getMappedPort(5672) : 5672;
|
||||
}
|
||||
|
||||
static int managementPort() {
|
||||
return RABBITMQ != null ? RABBITMQ.getMappedPort(15672) : 15672;
|
||||
}
|
||||
|
||||
static int streamPort() {
|
||||
return RABBITMQ != null ? RABBITMQ.getMappedPort(5552) : 5552;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.stream;
|
||||
|
||||
import com.rabbitmq.stream.ConsumerBuilder;
|
||||
import com.rabbitmq.stream.Environment;
|
||||
import com.rabbitmq.stream.OffsetSpecification;
|
||||
import com.rabbitmq.stream.ProducerBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.amqp.rabbit.listener.MessageListenerContainer;
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.stream.binder.BinderFactory;
|
||||
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.rabbit.RabbitMessageChannelBinder;
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitStreamMessageHandler;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitConsumerProperties.ContainerType;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties;
|
||||
import org.springframework.cloud.stream.binder.rabbit.properties.RabbitProducerProperties.ProducerType;
|
||||
import org.springframework.cloud.stream.config.ListenerContainerCustomizer;
|
||||
import org.springframework.cloud.stream.config.ProducerMessageHandlerCustomizer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.rabbit.stream.listener.StreamListenerContainer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class RabbitStreamBinderModuleTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
context = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStreamContainer() {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.run("--server.port=0");
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
RabbitMessageChannelBinder rabbitBinder = (RabbitMessageChannelBinder) binderFactory.getBinder(null,
|
||||
MessageChannel.class);
|
||||
RabbitConsumerProperties rProps = new RabbitConsumerProperties();
|
||||
rProps.setContainerType(ContainerType.STREAM);
|
||||
ExtendedConsumerProperties<RabbitConsumerProperties> props =
|
||||
new ExtendedConsumerProperties<RabbitConsumerProperties>(rProps);
|
||||
props.setAutoStartup(false);
|
||||
Binding<MessageChannel> binding = rabbitBinder.bindConsumer("testStream", "grp", new QueueChannel(), props);
|
||||
Object container = TestUtils.getPropertyValue(binding, "lifecycle.messageListenerContainer");
|
||||
assertThat(container).isInstanceOf(StreamListenerContainer.class);
|
||||
((StreamListenerContainer) container).start();
|
||||
verify(this.context.getBean(ConsumerBuilder.class)).offset(OffsetSpecification.first());
|
||||
((StreamListenerContainer) container).stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStreamHandler() {
|
||||
context = new SpringApplicationBuilder(SimpleProcessor.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.run("--server.port=0");
|
||||
BinderFactory binderFactory = context.getBean(BinderFactory.class);
|
||||
RabbitMessageChannelBinder rabbitBinder = (RabbitMessageChannelBinder) binderFactory.getBinder(null,
|
||||
MessageChannel.class);
|
||||
RabbitProducerProperties rProps = new RabbitProducerProperties();
|
||||
rProps.setProducerType(ProducerType.STREAM_SYNC);
|
||||
ExtendedProducerProperties<RabbitProducerProperties> props =
|
||||
new ExtendedProducerProperties<RabbitProducerProperties>(rProps);
|
||||
Binding<MessageChannel> binding = rabbitBinder.bindProducer("testStream", new DirectChannel(), props);
|
||||
Object handler = TestUtils.getPropertyValue(binding, "lifecycle");
|
||||
assertThat(handler).isInstanceOf(RabbitStreamMessageHandler.class);
|
||||
}
|
||||
|
||||
@SpringBootApplication(proxyBeanMethods = false)
|
||||
public static class SimpleProcessor {
|
||||
|
||||
@Bean
|
||||
ProducerMessageHandlerCustomizer<MessageHandler> handlerCustomizer() {
|
||||
return (hand, dest) -> {
|
||||
RabbitStreamMessageHandler handler = (RabbitStreamMessageHandler) hand;
|
||||
handler.setConfirmTimeout(5000);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
ListenerContainerCustomizer<MessageListenerContainer> containerCustomizer() {
|
||||
return (cont, dest, group) -> {
|
||||
StreamListenerContainer container = (StreamListenerContainer) cont;
|
||||
container.setConsumerCustomizer((name, builder) -> {
|
||||
builder.offset(OffsetSpecification.first());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
Environment env(ConsumerBuilder consumerBuilder, ProducerBuilder producerBuilder) {
|
||||
Environment env = mock(Environment.class);
|
||||
given(env.consumerBuilder()).willReturn(consumerBuilder);
|
||||
given(env.producerBuilder()).willReturn(producerBuilder);
|
||||
return env;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ConsumerBuilder consumerBuilder() {
|
||||
return mock(ConsumerBuilder.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ProducerBuilder producerBuilder() {
|
||||
return mock(ProducerBuilder.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2021-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR 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.stream;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.rabbitmq.stream.Address;
|
||||
import com.rabbitmq.stream.Consumer;
|
||||
import com.rabbitmq.stream.Environment;
|
||||
import com.rabbitmq.stream.OffsetSpecification;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.stream.binder.rabbit.RabbitStreamMessageHandler;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.rabbit.stream.producer.RabbitStreamTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.2
|
||||
*
|
||||
*/
|
||||
public class RabbitStreamMessageHandlerTests extends AbstractIntegrationTests {
|
||||
|
||||
@Test
|
||||
void convertAndSend() throws InterruptedException {
|
||||
Environment env = Environment.builder()
|
||||
.lazyInitialization(true)
|
||||
.addressResolver(add -> new Address("localhost", streamPort()))
|
||||
.build();
|
||||
try {
|
||||
env.deleteStream("stream.stream");
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
env.streamCreator().stream("stream.stream").create();
|
||||
RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream");
|
||||
RabbitStreamMessageHandler handler = new RabbitStreamMessageHandler(streamTemplate);
|
||||
handler.setSync(true);
|
||||
handler.handleMessage(MessageBuilder.withPayload("foo")
|
||||
.setHeader("bar", "baz")
|
||||
.build());
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<com.rabbitmq.stream.Message> received = new AtomicReference<>();
|
||||
Consumer consumer = env.consumerBuilder().stream("stream.stream")
|
||||
.offset(OffsetSpecification.first())
|
||||
.messageHandler((context, msg) -> {
|
||||
received.set(msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.build();
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(received.get()).isNotNull();
|
||||
assertThat(received.get().getBodyAsBinary()).isEqualTo("foo".getBytes());
|
||||
assertThat((String) received.get().getApplicationProperties().get("bar")).isEqualTo("baz");
|
||||
consumer.close();
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void sendNative() throws InterruptedException {
|
||||
Environment env = Environment.builder()
|
||||
.lazyInitialization(true)
|
||||
.build();
|
||||
try {
|
||||
env.deleteStream("stream.stream");
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
env.streamCreator().stream("stream.stream").create();
|
||||
RabbitStreamTemplate streamTemplate = new RabbitStreamTemplate(env, "stream.stream");
|
||||
RabbitStreamMessageHandler handler = new RabbitStreamMessageHandler(streamTemplate);
|
||||
handler.setSync(true);
|
||||
handler.handleMessage(MessageBuilder.withPayload(streamTemplate.messageBuilder()
|
||||
.addData("foo".getBytes())
|
||||
.applicationProperties().entry("bar", "baz")
|
||||
.messageBuilder()
|
||||
.build())
|
||||
.build());
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<com.rabbitmq.stream.Message> received = new AtomicReference<>();
|
||||
Consumer consumer = env.consumerBuilder().stream("stream.stream")
|
||||
.offset(OffsetSpecification.first())
|
||||
.messageHandler((context, msg) -> {
|
||||
received.set(msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.build();
|
||||
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(received.get()).isNotNull();
|
||||
assertThat(received.get().getBodyAsBinary()).isEqualTo("foo".getBytes());
|
||||
assertThat((String) received.get().getApplicationProperties().get("bar")).isEqualTo("baz");
|
||||
consumer.close();
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
8
r-binder/src/checkstyle/checkstyle-suppressions.xml
Normal file
8
r-binder/src/checkstyle/checkstyle-suppressions.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE suppressions PUBLIC
|
||||
"-//Puppy Crawl//DTD Suppressions 1.1//EN"
|
||||
"https://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
|
||||
<suppressions>
|
||||
<suppress files=".*RabbitDeployer\.java" checks="HideUtilityClassConstructor"/>
|
||||
<suppress files=".*RabbitDeployer\.java" checks="FinalClass"/>
|
||||
</suppressions>
|
||||
29
r-binder/update-version.sh
Executable file
29
r-binder/update-version.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
#Execute this script from local checkout of spring cloud stream
|
||||
|
||||
./mvnw versions:update-parent -DparentVersion=[0.0.1,$2] -Pspring -DgenerateBackupPoms=false -DallowSnapshots=true
|
||||
./mvnw versions:set -DnewVersion=$1 -DgenerateBackupPoms=false
|
||||
|
||||
|
||||
|
||||
lines=$(find . -name 'pom.xml' | xargs egrep "SNAPSHOT|M[0-9]|RC[0-9]" | grep -v regex | wc -l)
|
||||
if [ $lines -eq 0 ]; then
|
||||
echo "No snapshots found"
|
||||
else
|
||||
echo "Snapshots found."
|
||||
fi
|
||||
|
||||
lines=$(find . -name 'pom.xml' | xargs egrep "M[0-9]" | grep -v regex | wc -l)
|
||||
if [ $lines -eq 0 ]; then
|
||||
echo "No milestones found"
|
||||
else
|
||||
echo "Milestones found."
|
||||
fi
|
||||
|
||||
lines=$(find . -name 'pom.xml' | xargs egrep "RC[0-9]" | grep -v regex | wc -l)
|
||||
if [ $lines -eq 0 ]; then
|
||||
echo "No release candidates found"
|
||||
else
|
||||
echo "Release candidates found."
|
||||
fi
|
||||
Reference in New Issue
Block a user