diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000000..2c223d1582 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,32 @@ +name: Deploy Docs +on: + push: + branches-ignore: [ gh-pages ] + tags: '**' + repository_dispatch: + types: request-build-reference # legacy + #schedule: + #- cron: '0 10 * * *' # Once per day at 10am UTC + workflow_dispatch: +permissions: + actions: write +jobs: + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'spring-projects' + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: docs-build + fetch-depth: 1 + - name: Dispatch (partial build) + if: github.ref_type == 'branch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }} + - name: Dispatch (full build) + if: github.ref_type == 'tag' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) diff --git a/.github/workflows/pr-build-workflow.yml b/.github/workflows/pr-build-workflow.yml index 704f7e13ca..e5bf6860a2 100644 --- a/.github/workflows/pr-build-workflow.yml +++ b/.github/workflows/pr-build-workflow.yml @@ -36,7 +36,7 @@ jobs: debug: false concurrent: true gradle-build-scan-report: false - arguments: checkAsciidocLinks check + arguments: check - name: Capture Test Results if: failure() diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index a87952cf59..daeaa2cd41 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -96,10 +96,10 @@ To build api Javadoc (results will be in `build/api`): ./gradlew api ---- -To build the reference documentation (results will be in `build/docs/asciidoc` and `build/docs/asciidocPdf`): +To build the reference documentation (results will be in `build/site`): ---- -./gradlew reference +./gradlew antora ---- To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`): diff --git a/README.md b/README.md index 65f8f5966b..fce7070663 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Please see our [Security policy](https://github.com/spring-projects/spring-integ # Documentation -The Spring Integration maintains reference documentation ([published](https://docs.spring.io/spring-integration/docs/current/reference/html/) and [source](src/reference/asciidoc)), GitHub [wiki pages](https://github.com/spring-projects/spring-integration/wiki), and an [API reference](https://docs.spring.io/spring-integration/docs/current/api/). +The Spring Integration maintains reference documentation ([published](https://docs.spring.io/spring-integration/reference/) and [source](src/reference/antora)), GitHub [wiki pages](https://github.com/spring-projects/spring-integration/wiki), and an [API reference](https://docs.spring.io/spring-integration/docs/current/api/). There are also [guides and tutorials](https://spring.io/guides) across Spring projects. @@ -90,9 +90,9 @@ To build api Javadoc (results will be in `build/api`): ./gradlew api -To build the reference documentation (results will be in `build/docs/asciidoc` and `build/docs/asciidocPdf`): +To build the reference documentation (results will be in `build/site`): - ./gradlew reference + ./gradlew antora To build complete distribution including `-dist`, `-docs`, and `-schema` zip files (results will be in `build/distributions`): diff --git a/build.gradle b/build.gradle index 069988cd88..ee3625b08b 100644 --- a/build.gradle +++ b/build.gradle @@ -15,14 +15,15 @@ buildscript { } plugins { + id 'base' id 'org.sonarqube' version '4.3.0.3225' id 'io.spring.nohttp' version '0.0.11' apply false id 'org.ajoberstar.grgit' version '4.1.1' id 'io.spring.dependency-management' version '1.1.2' id 'com.jfrog.artifactory' version '4.33.1' apply false id 'org.jetbrains.dokka' version "1.8.20" - id 'org.asciidoctor.jvm.pdf' version '3.3.2' - id 'org.asciidoctor.jvm.convert' version '3.3.2' + id 'org.antora' version '1.0.0' + id 'io.spring.antora.generate-antora-yml' version '0.0.1' } if (isCI) { @@ -44,8 +45,8 @@ ext { linkScmConnection = 'scm:git:git://github.com/spring-projects/spring-integration.git' linkScmDevConnection = 'scm:git:ssh://git@github.com:spring-projects/spring-integration.git' - modifiedFiles = - files(grgit.status().unstaged.modified).filter { f -> f.name.endsWith('.java') || f.name.endsWith('.kt') } + modifiedFiles = providers.provider { + files(grgit.status().unstaged.modified).filter { f -> f.name.endsWith('.java') || f.name.endsWith('.kt') } } apacheSshdVersion = '2.10.0' artemisVersion = '2.29.0' @@ -289,7 +290,7 @@ configure(javaProjects) { subproject -> task updateCopyrights { onlyIf { !isCI } - inputs.files(modifiedFiles.filter { f -> f.path.contains(subproject.name) }) + inputs.files(modifiedFiles.map(files -> files.filter { f -> f.path.contains(subproject.name) })) outputs.dir('build/classes') doLast { @@ -337,7 +338,7 @@ configure(javaProjects) { subproject -> } } - task testAll(type: Test, dependsOn: [':checkAsciidocLinks', 'check']) + task testAll(type: Test, dependsOn: ['check']) gradle.taskGraph.whenReady { graph -> if (graph.hasTask(testAll)) { @@ -1237,7 +1238,7 @@ task schemaZip(type: Zip) { } } -task docsZip(type: Zip, dependsOn: [reference, dokkaHtmlMultiModule]) { +task docsZip(type: Zip, dependsOn: [dokkaHtmlMultiModule]) { group = 'Distribution' archiveClassifier = 'docs' description = "Builds -${archiveClassifier} archive containing api and reference " + @@ -1251,16 +1252,6 @@ task docsZip(type: Zip, dependsOn: [reference, dokkaHtmlMultiModule]) { into 'api' } - from('build/docs/asciidoc') { - into 'reference/html' - } - - from('build/docs/asciidocPdf') { - include 'index-single.pdf' - rename 'index-single.pdf', 'spring-integration-reference.pdf' - into 'reference/pdf' - } - from(dokkaHtmlMultiModule.outputDirectory) { into 'kdoc-api' } diff --git a/gradle/docs.gradle b/gradle/docs.gradle index 9af8b6e2d0..5fd5c30a5d 100644 --- a/gradle/docs.gradle +++ b/gradle/docs.gradle @@ -1,56 +1,46 @@ ext { - backendVersion = '0.0.5' micrometerDocsVersion='1.0.1' } + +antora { + version = '3.2.0-alpha.2' + playbook = file('src/reference/antora/antora-playbook.yml') + options = ['to-dir' : project.layout.buildDirectory.dir('site').get().toString(), clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true] + dependencies = [ + '@antora/atlas-extension': '1.0.0-alpha.1', + '@antora/collector-extension': '1.0.0-alpha.3', + '@asciidoctor/tabs': '1.0.0-beta.3', + '@springio/antora-extensions': '1.4.2', + '@springio/asciidoctor-extensions': '1.0.0-alpha.8', + ] +} + +tasks.named("generateAntoraYml") { + asciidocAttributes = project.provider( { + return ['project-version' : project.version ] + } ) + baseAntoraYmlFile = file('src/reference/antora/antora.yml') +} + +tasks.create(name: 'createAntoraPartials', type: Sync) { + from { tasks.filterMetricsDocsContent.outputs } + into layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/partials') +} + +tasks.create('generateAntoraResources') { + dependsOn 'createAntoraPartials' + dependsOn 'generateAntoraYml' +} + configurations { - asciidoctorExtensions micrometerDocs } dependencies { - asciidoctorExtensions "io.spring.asciidoctor.backends:spring-asciidoctor-backends:$backendVersion" micrometerDocs "io.micrometer:micrometer-docs-generator:$micrometerDocsVersion" } -task checkAsciidocLinks { - inputs.dir('src/reference/asciidoc') - doLast { - def errors = new ArrayList<>(); - errors.add('*** Anchor reference errors found:') - inputs.files.filter{ f -> f.path.endsWith('.adoc') }.each { file -> - def doc = file.text - def anchors = new HashSet<>() - def matcher = (doc =~ /\[\[([^]]+)]]/) - while (matcher.find()) { - anchors.add(matcher.group(1)) - } - matcher = (doc =~ /<<([^>]+)>>/) - while (matcher.find()) { - def anchor = matcher.group(1); - def hasComma = anchor.contains(',') - if (!anchor.startsWith('./')) { - if (hasComma) { - anchor = anchor.substring(0, anchor.indexOf(",")); - } - if (!anchors.contains(anchor)) { - errors.add("\nAnchor '$anchor' not found in '${file.name}', " + - "if in another file, it needs to be qualified with './fileName.adoc#'") - } - } - else { - if (!hasComma) { - errors.add("\nExternal anchor '$anchor' in '${file.name}' should have a textual description (,...)") - } - } - } - } - if (errors.size() > 1) { - throw new InvalidUserDataException(errors.toString()) - } - } -} - def observationInputDir = file('spring-integration-core/src/main/java/org/springframework/integration/support/management/observation').absolutePath def generatedDocsDir = file("$buildDir/reference/generated").absolutePath @@ -69,82 +59,4 @@ task filterMetricsDocsContent(type: Copy) { into generatedDocsDir rename { filename -> filename.replace '_', '' } filter { line -> line.replaceAll('org.springframework.integration', 'o.s.i') } -} - -task prepareDocs(type: Copy) { - dependsOn checkAsciidocLinks, filterMetricsDocsContent - from 'src/reference/asciidoc' - into "$buildDir/reference" -} - -asciidoctorPdf { - dependsOn prepareDocs - - forkOptions { - jvmArgs '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED' - } - - baseDirFollowsSourceFile() - - asciidoctorj { - sourceDir "$buildDir/reference" - sources { - include 'index-single.adoc' - } - options doctype: 'book' - attributes 'icons': 'font', - 'sectanchors': '', - 'sectnums': '', - 'toc': '', - 'source-highlighter' : 'coderay', - revnumber: project.version, - 'project-version': project.version - } -} - -asciidoctor { - dependsOn asciidoctorPdf - - forkOptions { - jvmArgs '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', '--add-opens', 'java.base/java.io=ALL-UNNAMED' - } - - baseDirFollowsSourceFile() - - configurations 'asciidoctorExtensions' - sourceDir "$buildDir/reference" - outputOptions { - backends 'spring-html' - } - resources { - from(sourceDir) { - include 'images/*', 'css/**', 'js/**' - } - } - - options doctype: 'book', eruby: 'erubis' - attributes 'docinfo': 'shared', - stylesdir: 'css/', - stylesheet: 'stylesheet.css', - 'linkcss': true, - 'copycss': false, - 'icons': 'font', - 'sectanchors': '', - 'source-highlighter': 'highlight.js', - 'highlightjsdir': 'js/highlight', - 'highlightjs-theme': 'github', - 'idprefix': '', - 'idseparator': '-', - 'allow-uri-read': '', - 'toc': 'left', - 'toclevels': '4', - revnumber: project.version, - 'project-version': project.version -} - -task reference(dependsOn: asciidoctor) { - group = 'Documentation' - description = 'Generate the reference documentation' -} - -reference.onlyIf { 'true' != System.env['NO_REFERENCE_TASK'] || project.hasProperty('ignoreEnvToStopReference') } +} \ No newline at end of file diff --git a/src/reference/antora/antora-playbook.yml b/src/reference/antora/antora-playbook.yml new file mode 100644 index 0000000000..53bcf5ae93 --- /dev/null +++ b/src/reference/antora/antora-playbook.yml @@ -0,0 +1,41 @@ +# PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2 +# +# The purpose of this Antora playbook is to build the docs in the current branch. +antora: + extensions: + - '@springio/antora-extensions/partial-build-extension' + - require: '@springio/antora-extensions/latest-version-extension' + - require: '@springio/antora-extensions/inject-collector-cache-config-extension' + - '@antora/collector-extension' + - '@antora/atlas-extension' + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: 'integration' +site: + title: Spring Integration + url: https://docs.spring.io/spring-integration/reference/ +content: + sources: + - url: ./../../.. + branches: HEAD + worktrees: true + start_path: src/reference/antora +asciidoc: + attributes: + page-stackoverflow-url: https://stackoverflow.com/tags/spring-integration + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + chomp: 'all' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + sourcemap: true +urls: + latest_version_segment: '' +runtime: + log: + failure_level: warn + format: pretty +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.4/ui-bundle.zip \ No newline at end of file diff --git a/src/reference/antora/antora.yml b/src/reference/antora/antora.yml new file mode 100644 index 0000000000..f792fc87cf --- /dev/null +++ b/src/reference/antora/antora.yml @@ -0,0 +1,20 @@ +name: integration +version: true +title: Spring Integration +nav: + - modules/ROOT/nav.adoc +ext: + collector: + run: + command: gradlew -q "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :generateAntoraResources + local: true + scan: + dir: build/generated-antora-resources + +asciidoc: + attributes: + attribute-missing: 'warn' + # FIXME: the copyright is not removed + # FIXME: The package is not renamed + chomp: 'all' + snippets: example$docs-src/test/java/org/springframework/integration/docs \ No newline at end of file diff --git a/src/reference/asciidoc/images/bank-router.jpg b/src/reference/antora/modules/ROOT/assets/images/bank-router.jpg similarity index 100% rename from src/reference/asciidoc/images/bank-router.jpg rename to src/reference/antora/modules/ROOT/assets/images/bank-router.jpg diff --git a/src/reference/asciidoc/images/cafe-eip.png b/src/reference/antora/modules/ROOT/assets/images/cafe-eip.png similarity index 100% rename from src/reference/asciidoc/images/cafe-eip.png rename to src/reference/antora/modules/ROOT/assets/images/cafe-eip.png diff --git a/src/reference/asciidoc/images/chain.png b/src/reference/antora/modules/ROOT/assets/images/chain.png similarity index 100% rename from src/reference/asciidoc/images/chain.png rename to src/reference/antora/modules/ROOT/assets/images/chain.png diff --git a/src/reference/asciidoc/images/channel.jpg b/src/reference/antora/modules/ROOT/assets/images/channel.jpg similarity index 100% rename from src/reference/asciidoc/images/channel.jpg rename to src/reference/antora/modules/ROOT/assets/images/channel.jpg diff --git a/src/reference/asciidoc/images/epub-cover.png b/src/reference/antora/modules/ROOT/assets/images/epub-cover.png similarity index 100% rename from src/reference/asciidoc/images/epub-cover.png rename to src/reference/antora/modules/ROOT/assets/images/epub-cover.png diff --git a/src/reference/asciidoc/images/epub-cover.svg b/src/reference/antora/modules/ROOT/assets/images/epub-cover.svg similarity index 100% rename from src/reference/asciidoc/images/epub-cover.svg rename to src/reference/antora/modules/ROOT/assets/images/epub-cover.svg diff --git a/src/reference/asciidoc/images/gateway.jpg b/src/reference/antora/modules/ROOT/assets/images/gateway.jpg similarity index 100% rename from src/reference/asciidoc/images/gateway.jpg rename to src/reference/antora/modules/ROOT/assets/images/gateway.jpg diff --git a/src/reference/asciidoc/images/handler-endpoint.jpg b/src/reference/antora/modules/ROOT/assets/images/handler-endpoint.jpg similarity index 100% rename from src/reference/asciidoc/images/handler-endpoint.jpg rename to src/reference/antora/modules/ROOT/assets/images/handler-endpoint.jpg diff --git a/src/reference/asciidoc/images/http-inbound-gateway.png b/src/reference/antora/modules/ROOT/assets/images/http-inbound-gateway.png similarity index 100% rename from src/reference/asciidoc/images/http-inbound-gateway.png rename to src/reference/antora/modules/ROOT/assets/images/http-inbound-gateway.png diff --git a/src/reference/asciidoc/images/http-outbound-gateway.png b/src/reference/antora/modules/ROOT/assets/images/http-outbound-gateway.png similarity index 100% rename from src/reference/asciidoc/images/http-outbound-gateway.png rename to src/reference/antora/modules/ROOT/assets/images/http-outbound-gateway.png diff --git a/src/reference/asciidoc/images/loan-broker-eip.png b/src/reference/antora/modules/ROOT/assets/images/loan-broker-eip.png similarity index 100% rename from src/reference/asciidoc/images/loan-broker-eip.png rename to src/reference/antora/modules/ROOT/assets/images/loan-broker-eip.png diff --git a/src/reference/asciidoc/images/message.jpg b/src/reference/antora/modules/ROOT/assets/images/message.jpg similarity index 100% rename from src/reference/asciidoc/images/message.jpg rename to src/reference/antora/modules/ROOT/assets/images/message.jpg diff --git a/src/reference/asciidoc/images/quotes-aggregator.jpg b/src/reference/antora/modules/ROOT/assets/images/quotes-aggregator.jpg similarity index 100% rename from src/reference/asciidoc/images/quotes-aggregator.jpg rename to src/reference/antora/modules/ROOT/assets/images/quotes-aggregator.jpg diff --git a/src/reference/asciidoc/images/router.jpg b/src/reference/antora/modules/ROOT/assets/images/router.jpg similarity index 100% rename from src/reference/asciidoc/images/router.jpg rename to src/reference/antora/modules/ROOT/assets/images/router.jpg diff --git a/src/reference/asciidoc/images/source-endpoint.jpg b/src/reference/antora/modules/ROOT/assets/images/source-endpoint.jpg similarity index 100% rename from src/reference/asciidoc/images/source-endpoint.jpg rename to src/reference/antora/modules/ROOT/assets/images/source-endpoint.jpg diff --git a/src/reference/asciidoc/images/spring-integration-amqp-sample-graph.png b/src/reference/antora/modules/ROOT/assets/images/spring-integration-amqp-sample-graph.png similarity index 100% rename from src/reference/asciidoc/images/spring-integration-amqp-sample-graph.png rename to src/reference/antora/modules/ROOT/assets/images/spring-integration-amqp-sample-graph.png diff --git a/src/reference/asciidoc/images/target-endpoint.jpg b/src/reference/antora/modules/ROOT/assets/images/target-endpoint.jpg similarity index 100% rename from src/reference/asciidoc/images/target-endpoint.jpg rename to src/reference/antora/modules/ROOT/assets/images/target-endpoint.jpg diff --git a/src/reference/asciidoc/images/tickmark.png b/src/reference/antora/modules/ROOT/assets/images/tickmark.png similarity index 100% rename from src/reference/asciidoc/images/tickmark.png rename to src/reference/antora/modules/ROOT/assets/images/tickmark.png diff --git a/src/reference/antora/modules/ROOT/nav.adoc b/src/reference/antora/modules/ROOT/nav.adoc new file mode 100644 index 0000000000..145929caff --- /dev/null +++ b/src/reference/antora/modules/ROOT/nav.adoc @@ -0,0 +1,271 @@ +* xref:index.adoc[Home] +* xref:preface.adoc[] +* xref:whats-new.adoc[] +* xref:overview.adoc[Overview] +* xref:core.adoc[] +** xref:channel.adoc[] +*** xref:channel/interfaces.adoc[] +*** xref:channel/implementations.adoc[] +*** xref:channel/interceptors.adoc[] +*** xref:channel/template.adoc[] +*** xref:channel/configuration.adoc[] +*** xref:channel/special-channels.adoc[] +** xref:polling-consumer.adoc[] +** xref:channel-adapter.adoc[] +** xref:bridge.adoc[] +* xref:message.adoc[] +* xref:message-routing.adoc[] +** xref:router.adoc[] +*** xref:router/overview.adoc[] +*** xref:router/common-parameters.adoc[] +*** xref:router/implementations.adoc[] +*** xref:router/namespace.adoc[] +*** xref:router/spel.adoc[] +*** xref:router/annotation.adoc[] +*** xref:router/dynamic-routers.adoc[] +*** xref:router/routing-slip.adoc[] +*** xref:router/process-manager.adoc[] +** xref:filter.adoc[] +** xref:splitter.adoc[] +** xref:aggregator.adoc[] +** xref:resequencer.adoc[] +** xref:chain.adoc[] +** xref:scatter-gather.adoc[] +** xref:barrier.adoc[] +* xref:message-transformation.adoc[] +** xref:transformer.adoc[] +** xref:content-enrichment.adoc[] +** xref:claim-check.adoc[] +** xref:codec.adoc[] +* xref:messaging-endpoints.adoc[] +** xref:endpoint.adoc[] +** xref:endpoint-roles.adoc[] +** xref:leadership-event-handling.adoc[] +** xref:gateway.adoc[] +** xref:service-activator.adoc[] +** xref:delayer.adoc[] +** xref:scripting.adoc[] +** xref:groovy.adoc[] +** xref:handler-advice.adoc[] +*** xref:handler-advice/classes.adoc[] +*** xref:handler-advice/reactive.adoc[] +*** xref:handler-advice/context-holder.adoc[] +*** xref:handler-advice/custom.adoc[] +*** xref:handler-advice/other.adoc[] +*** xref:handler-advice/handle-message.adoc[] +*** xref:handler-advice/tx-handle-message.adoc[] +*** xref:handler-advice/advising-filters.adoc[] +*** xref:handler-advice/advising-with-annotations.adoc[] +*** xref:handler-advice/order.adoc[] +*** xref:handler-advice/advised-properties.adoc[] +*** xref:handler-advice/idempotent-receiver.adoc[] +** xref:logging-adapter.adoc[] +*** xref:functions-support.adoc[] +** xref:kotlin-functions.adoc[] +* xref:dsl.adoc[] +** xref:dsl/java-basics.adoc[] +** xref:dsl/java-channels.adoc[] +** xref:dsl/java-pollers.adoc[] +** xref:dsl/java-reactive.adoc[] +** xref:dsl/java-endpoints.adoc[] +** xref:dsl/java-transformers.adoc[] +** xref:dsl/java-inbound-adapters.adoc[] +** xref:dsl/java-routers.adoc[] +** xref:dsl/java-splitters.adoc[] +** xref:dsl/java-aggregators.adoc[] +** xref:dsl/java-handle.adoc[] +** xref:dsl/java-gateway.adoc[] +** xref:dsl/java-log.adoc[] +** xref:dsl/java-intercept.adoc[] +** xref:dsl/java-wiretap.adoc[] +** xref:dsl/java-flows.adoc[] +** xref:dsl/java-function-expression.adoc[] +** xref:dsl/java-subflows.adoc[] +** xref:dsl/java-protocol-adapters.adoc[] +** xref:dsl/java-flow-adapter.adoc[] +** xref:dsl/java-runtime-flows.adoc[] +** xref:dsl/integration-flow-as-gateway.adoc[] +** xref:dsl/java-extensions.adoc[] +** xref:dsl/integration-flows-composition.adoc[] +* xref:groovy-dsl.adoc[] +* xref:kotlin-dsl.adoc[] +* xref:system-management.adoc[] +** xref:metrics.adoc[] +** xref:message-history.adoc[] +** xref:message-store.adoc[] +** xref:meta-data-store.adoc[] +** xref:control-bus.adoc[] +** xref:shutdown.adoc[] +** xref:graph.adoc[] +** xref:integration-graph-controller.adoc[] +* xref:reactive-streams.adoc[] +* xref:native-aot.adoc[] +* xref:endpoint-summary.adoc[] +* xref:amqp.adoc[AMQP Support] +** xref:amqp/inbound-channel-adapter.adoc[] +** xref:amqp/polled-inbound-channel-adapter.adoc[] +** xref:amqp/inbound-gateway.adoc[] +** xref:amqp/inbound-ack.adoc[] +** xref:amqp/outbound-endpoints.adoc[] +** xref:amqp/outbound-channel-adapter.adoc[] +** xref:amqp/outbound-gateway.adoc[] +** xref:amqp/async-outbound-gateway.adoc[] +** xref:amqp/alternative-confirms-returns.adoc[] +** xref:amqp/conversion-inbound.adoc[] +** xref:amqp/content-type-conversion-outbound.adoc[] +** xref:amqp/user-id.adoc[] +** xref:amqp/delay.adoc[] +** xref:amqp/channels.adoc[] +** xref:amqp/message-headers.adoc[] +** xref:amqp/strict-ordering.adoc[] +** xref:amqp/samples.adoc[] +** xref:amqp/rmq-streams.adoc[] +* xref:camel.adoc[] +* xref:cassandra.adoc[] +* xref:debezium.adoc[] +* xref:event.adoc[] +* xref:feed.adoc[] +* xref:file.adoc[] +** xref:file/reading.adoc[] +** xref:file/writing.adoc[] +** xref:file/transforming.adoc[] +** xref:file/splitter.adoc[] +** xref:file/aggregator.adoc[] +** xref:file/remote-persistent-flf.adoc[] +* xref:ftp.adoc[] +** xref:ftp/session-factory.adoc[] +** xref:ftp/advanced-configuration.adoc[] +** xref:ftp/dsf.adoc[] +** xref:ftp/inbound.adoc[] +** xref:ftp/streaming.adoc[] +** xref:ftp/rotating-server-advice.adoc[] +** xref:ftp/max-fetch.adoc[] +** xref:ftp/outbound.adoc[] +** xref:ftp/outbound-gateway.adoc[] +** xref:ftp/session-caching.adoc[] +** xref:ftp/rft.adoc[] +** xref:ftp/session-callback.adoc[] +** xref:ftp/server-events.adoc[] +** xref:ftp/remote-file-info.adoc[] +* xref:graphql.adoc[] +* xref:hazelcast.adoc[] +* xref:http.adoc[] +** xref:http/inbound.adoc[] +** xref:http/outbound.adoc[] +** xref:http/namespace.adoc[] +** xref:http/java-config.adoc[] +** xref:http/timeout.adoc[] +** xref:http/proxy.adoc[] +** xref:http/header-mapping.adoc[] +** xref:http/int-graph-controller.adoc[] +** xref:http/samples.adoc[] +* xref:jdbc.adoc[] +** xref:jdbc/inbound-channel-adapter.adoc[] +** xref:jdbc/outbound-channel-adapter.adoc[] +** xref:jdbc/outbound-gateway.adoc[] +** xref:jdbc/message-store.adoc[] +** xref:jdbc/stored-procedures.adoc[] +** xref:jdbc/lock-registry.adoc[] +** xref:jdbc/metadata-store.adoc[] +* xref:jpa.adoc[] +** xref:jpa/functionality.adoc[] +** xref:jpa/supported-persistence-providers.adoc[] +** xref:jpa/java-implementation.adoc[] +** xref:jpa/namespace-support.adoc[] +** xref:jpa/inbound-channel-adapter.adoc[] +** xref:jpa/outbound-channel-adapter.adoc[] +** xref:jpa/outbound-gateways.adoc[] +* xref:jms.adoc[] +* xref:jmx.adoc[] +* xref:kafka.adoc[] +* xref:mail.adoc[] +* xref:mongodb.adoc[] +* xref:mqtt.adoc[] +* xref:r2dbc.adoc[] +* xref:redis.adoc[] +* xref:resource.adoc[] +* xref:rsocket.adoc[] +* xref:sftp.adoc[] +** xref:sftp/session-factory.adoc[] +** xref:sftp/dsf.adoc[] +** xref:sftp/session-caching.adoc[] +** xref:sftp/rft.adoc[] +** xref:sftp/inbound.adoc[] +** xref:sftp/streaming.adoc[] +** xref:sftp/rotating-server-advice.adoc[] +** xref:sftp/max-fetch.adoc[] +** xref:sftp/outbound.adoc[] +** xref:sftp/outbound-gateway.adoc[] +** xref:sftp/session-callback.adoc[] +** xref:sftp/server-events.adoc[] +** xref:sftp/remote-file-info.adoc[] +* xref:smb.adoc[] +* xref:stomp.adoc[] +* xref:stream.adoc[] +* xref:syslog.adoc[] +* xref:ip.adoc[] +** xref:ip/intro.adoc[] +** xref:ip/udp-adapters.adoc[] +** xref:ip/tcp-connection-factories.adoc[] +** xref:ip/testing-connections.adoc[] +** xref:ip/interceptors.adoc[] +** xref:ip/tcp-events.adoc[] +** xref:ip/tcp-adapters.adoc[] +** xref:ip/tcp-gateways.adoc[] +** xref:ip/correlation.adoc[] +** xref:ip/note-nio.adoc[] +** xref:ip/ssl-tls.adoc[] +** xref:ip/tcp-advanced-techniques.adoc[] +** xref:ip/endpoint-reference.adoc[] +** xref:ip/msg-headers.adoc[] +** xref:ip/annotation.adoc[] +** xref:ip/dsl.adoc[] +* xref:webflux.adoc[] +* xref:web-sockets.adoc[] +* xref:ws.adoc[] +* xref:xml.adoc[] +** xref:xml/xpath-namespace-support.adoc[] +** xref:xml/transformation.adoc[] +** xref:xml/xpath-transformer.adoc[] +** xref:xml/xpath-splitting.adoc[] +** xref:xml/xpath-routing.adoc[] +** xref:xml/xpath-header-enricher.adoc[] +** xref:xml/xpath-filter.adoc[] +** xref:xml/xpath-spel-function.adoc[] +** xref:xml/validating-filter.adoc[] +* xref:xmpp.adoc[] +* xref:zeromq.adoc[] +* xref:zookeeper.adoc[] +* xref:error-handling.adoc[] +* xref:spel.adoc[] +* xref:message-publishing.adoc[] +* xref:transactions.adoc[] +* xref:security.adoc[] +* xref:configuration.adoc[] +** xref:configuration/namespace.adoc[] +** xref:configuration/namespace-taskscheduler.adoc[] +** xref:configuration/global-properties.adoc[] +** xref:configuration/annotations.adoc[] +** xref:configuration/meta-annotations.adoc[] +** xref:configuration/message-mapping-rules.adoc[] +* xref:testing.adoc[] +* xref:samples.adoc[] +* xref:resources.adoc[] +* xref:history.adoc[] +** xref:changes-6.0-6.1.adoc[] +** xref:changes-5.5-6.0.adoc[] +** xref:changes-5.4-5.5.adoc[] +** xref:changes-5.3-5.4.adoc[] +** xref:changes-5.2-5.3.adoc[] +** xref:changes-5.1-5.2.adoc[] +** xref:changes-5.0-5.1.adoc[] +** xref:changes-4.3-5.0.adoc[] +** xref:changes-4.2-4.3.adoc[] +** xref:changes-4.1-4.2.adoc[] +** xref:changes-4.0-4.1.adoc[] +** xref:changes-3.0-4.0.adoc[] +** xref:changes-2.2-3.0.adoc[] +** xref:changes-2.1-2.2.adoc[] +** xref:changes-2.0-2.1.adoc[] +** xref:changes-1.0-2.0.adoc[] +* xref:zip.adoc[] diff --git a/src/reference/asciidoc/aggregator.adoc b/src/reference/antora/modules/ROOT/pages/aggregator.adoc similarity index 96% rename from src/reference/asciidoc/aggregator.adoc rename to src/reference/antora/modules/ROOT/pages/aggregator.adoc index 407aeb8178..8de7e67a71 100644 --- a/src/reference/asciidoc/aggregator.adoc +++ b/src/reference/antora/modules/ROOT/pages/aggregator.adoc @@ -1,5 +1,5 @@ [[aggregator]] -=== Aggregator += Aggregator Basically a mirror-image of the splitter, the aggregator is a type of message handler that receives multiple messages and combines them into a single message. In fact, an aggregator is often a downstream consumer in a pipeline that includes a splitter. @@ -9,7 +9,7 @@ It must hold the messages to be aggregated and determine when the complete group In order to do so, it requires a `MessageStore`. [[aggregator-functionality]] -==== Functionality +== Functionality The Aggregator combines a group of related messages, by correlating and storing them, until the group is deemed to be complete. At that point, the aggregator creates a single message by processing the whole group and sends the aggregated message as output. @@ -28,7 +28,7 @@ The default release strategy for the aggregator releases a group when all messag You can override this default strategy by providing a reference to a custom `ReleaseStrategy` implementation. [[aggregator-api]] -==== Programming Model +== Programming Model The Aggregation API consists of a number of classes: @@ -36,7 +36,8 @@ The Aggregation API consists of a number of classes: * The `ReleaseStrategy` interface and its default implementation: `SimpleSequenceSizeReleaseStrategy` * The `CorrelationStrategy` interface and its default implementation: `HeaderAttributeCorrelationStrategy` -===== `AggregatingMessageHandler` +[[aggregatingmessagehandler]] +=== `AggregatingMessageHandler` The `AggregatingMessageHandler` (a subclass of `AbstractCorrelatingMessageHandler`) is a `MessageHandler` implementation, encapsulating the common functionality of an aggregator (and other correlating use cases), which are as follows: @@ -51,7 +52,6 @@ The responsibility for deciding whether the message group can be released is del The following listing shows a brief highlight of the base `AbstractAggregatingMessageGroupProcessor` (the responsibility for implementing the `aggregatePayloads` method is left to the developer): -==== [source,java] ---- public abstract class AbstractAggregatingMessageGroupProcessor @@ -65,7 +65,6 @@ public abstract class AbstractAggregatingMessageGroupProcessor } ---- -==== See `DefaultAggregatingMessageGroupProcessor`, `ExpressionEvaluatingMessageGroupProcessor` and `MethodInvokingMessageGroupProcessor` as out-of-the-box implementations of the `AbstractAggregatingMessageGroupProcessor`. @@ -80,7 +79,6 @@ The `Function>` strategy is available as the ` The `CorrelationStrategy` is owned by the `AbstractCorrelatingMessageHandler` and has a default value based on the `IntegrationMessageHeaderAccessor.CORRELATION_ID` message header, as the following example shows: -==== [source,java] ---- public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, MessageGroupStore store, @@ -92,7 +90,6 @@ public AbstractCorrelatingMessageHandler(MessageGroupProcessor processor, Messag ... } ---- -==== As for the actual processing of the message group, the default implementation is the `DefaultAggregatingMessageGroupProcessor`. It creates a single `Message` whose payload is a `List` of the payloads received for a given group. @@ -123,7 +120,7 @@ If the `MessageGroupProcessor` returns a `Message`, a `MessageBuilder.popSequenc This functionality can be controlled by a new `popSequence` `boolean` property, so the `MessageBuilder.popSequenceDetails()` can be disabled in some scenarios when correlation details have not been populated by the standard splitter. This property, essentially, undoes what has been done by the nearest upstream `applySequence = true` in the `AbstractMessageSplitter`. -See <<./splitter.adoc#splitter,Splitter>> for more information. +See xref:splitter.adoc[Splitter] for more information. [[agg-message-collection]] IMPORTANT: The `SimpleMessageGroup.getMessages()` method returns an `unmodifiableCollection`. @@ -147,11 +144,11 @@ Starting with version 6.0, the splitting behaviour, described above, works only Otherwise, with any other `MessageGroupProcessor` implementation that returns a `Collection`, only a single reply message is emitted with the whole collection of messages as its payload. Such logic is dictated by the canonical purpose of an aggregator - collect request messages by some key and produce a single grouped message. -===== `ReleaseStrategy` +[[releasestrategy]] +=== `ReleaseStrategy` The `ReleaseStrategy` interface is defined as follows: -==== [source,java] ---- public interface ReleaseStrategy { @@ -160,7 +157,6 @@ public interface ReleaseStrategy { } ---- -==== In general, any POJO can implement the completion decision logic if it provides a method that accepts a single `java.util.List` as an argument (parameterized lists are supported as well) and returns a boolean value. This method is invoked after the arrival of each new message, to decide whether the group is complete or not, as follows: @@ -171,7 +167,6 @@ This method is invoked after the arrival of each new message, to decide whether The following example shows how to use the `@ReleaseStrategy` annotation for a `List` of type `Message`: -==== [source,java] ---- public class MyReleaseStrategy { @@ -180,11 +175,9 @@ public class MyReleaseStrategy { public boolean canMessagesBeReleased(List>) {...} } ---- -==== The following example shows how to use the `@ReleaseStrategy` annotation for a `List` of type `String`: -==== [source,java] ---- public class MyReleaseStrategy { @@ -193,7 +186,6 @@ public class MyReleaseStrategy { public boolean canMessagesBeReleased(List) {...} } ---- -==== Based on the signatures in the preceding two examples, the POJO-based release strategy is passed a `Collection` of not-yet-released messages (if you need access to the whole `Message`) or a `Collection` of payload objects (if the type parameter is anything other than `Message`). This satisfies the majority of use cases. @@ -221,7 +213,7 @@ IMPORTANT: To facilitate discarding of late-arriving messages, the aggregator mu This can eventually cause out-of-memory conditions. To avoid such situations, you should consider configuring a `MessageGroupStoreReaper` to remove the group metadata. The expiry parameters should be set to expire groups once a point has been reach after which late messages are not expected to arrive. -For information about configuring a reaper, see <>. +For information about configuring a reaper, see xref:aggregator.adoc#reaper[Managing State in an Aggregator: `MessageGroupStore`]. Spring Integration provides an implementation for `ReleaseStrategy`: `SimpleSequenceSizeReleaseStrategy`. This implementation consults the `SEQUENCE_NUMBER` and `SEQUENCE_SIZE` headers of each arriving message to decide when a message group is complete and ready to be aggregated. @@ -233,18 +225,18 @@ This operation can be expensive. If you are aggregating large groups, you don't need to release partial groups, and you don't need to detect/reject duplicate sequences, consider using the `SimpleSequenceSizeReleaseStrategy` instead - it is much more efficient for these use cases, and is the default since _version 5.0_ when partial group release is not specified. -===== Aggregating Large Groups +[[aggregating-large-groups]] +=== Aggregating Large Groups The 4.3 release changed the default `Collection` for messages in a `SimpleMessageGroup` to `HashSet` (it was previously a `BlockingQueue`). This was expensive when removing individual messages from large groups (an O(n) linear scan was required). Although the hash set is generally much faster to remove, it can be expensive for large messages, because the hash has to be calculated on both inserts and removes. If you have messages that are expensive to hash, consider using some other collection type. -As discussed in <<./message-store.adoc#message-group-factory,Using `MessageGroupFactory`>>, a `SimpleMessageGroupFactory` is provided so that you can select the `Collection` that best suits your needs. +As discussed in xref:message-store.adoc#message-group-factory[Using `MessageGroupFactory`], a `SimpleMessageGroupFactory` is provided so that you can select the `Collection` that best suits your needs. You can also provide your own factory implementation to create some other `Collection>`. The following example shows how to configure an aggregator with the previous implementation and a `SimpleSequenceSizeReleaseStrategy`: -==== [source, xml] ---- ---- -==== NOTE: If the filter endpoint is involved in the flow upstream of an aggregator, the sequence size release strategy (fixed or based on the `sequenceSize` header) is not going to serve its purpose because some messages from a sequence may be discarded by the filter. In this case it is recommended to choose another `ReleaseStrategy`, or use compensation messages sent from a discard sub-flow carrying some information in their content to be skipped in a custom complete group function. -See <<./filter.adoc#filter,Filter>> for more information. +See xref:filter.adoc[Filter] for more information. -===== Correlation Strategy +[[correlation-strategy]] +=== Correlation Strategy The `CorrelationStrategy` interface is defined as follows: -==== [source,java] ---- public interface CorrelationStrategy { @@ -279,7 +270,6 @@ public interface CorrelationStrategy { } ---- -==== The method returns an `Object` that represents the correlation key used for associating the message with a message group. The key must satisfy the criteria used for a key in a `Map` with respect to the implementation of `equals()` and `hashCode()`. @@ -292,7 +282,8 @@ This implementation returns the value of one of the message headers (whose name By default, the correlation strategy is a `HeaderAttributeCorrelationStrategy` that returns the value of the `CORRELATION_ID` header attribute. If you have a custom header name you would like to use for correlation, you can configure it on an instance of `HeaderAttributeCorrelationStrategy` and provide that as a reference for the aggregator's correlation strategy. -===== Lock Registry +[[lock-registry]] +=== Lock Registry Changes to groups are thread safe. So, when you send messages for the same correlation ID concurrently, only one of them will be processed in the aggregator, making it effectively as a *single-threaded per message group*. @@ -301,23 +292,20 @@ A `DefaultLockRegistry` is used by default (in-memory). For synchronizing updates across servers where a shared `MessageGroupStore` is being used, you must configure a shared lock registry. [[aggregator-deadlocks]] -===== Avoiding Deadlocks +=== Avoiding Deadlocks As discussed above, when message groups are mutated (messages added or released) a lock is held. Consider the following flow: -==== [source] ---- ...->aggregator1-> ... ->aggregator2-> ... ---- -==== If there are multiple threads, **and the aggregators share a common lock registry**, it is possible to get a deadlock. This will cause hung threads and `jstack ` might present a result such as: -==== [source] ---- Found one Java-level deadlock: @@ -329,7 +317,6 @@ Found one Java-level deadlock: waiting for ownable synchronizer 0x000000076c1ccc00, (a java.util.concurrent.locks.ReentrantLock$NonfairSync), which is held by "t2" ---- -==== There are several ways to avoid this problem: @@ -342,17 +329,16 @@ Of course, the first solution above does not apply in this case. [[aggregator-java-dsl]] -==== Configuring an Aggregator in Java DSL +== Configuring an Aggregator in Java DSL -See <<./dsl.adoc#java-dsl-aggregators,Aggregators and Resequencers>> for how to configure an aggregator in Java DSL. +See xref:dsl/java-aggregators.adoc[Aggregators and Resequencers] for how to configure an aggregator in Java DSL. [[aggregator-xml]] -===== Configuring an Aggregator with XML +=== Configuring an Aggregator with XML Spring Integration supports the configuration of an aggregator with XML through the `` element. The following example shows an example of an aggregator: -==== [source,xml] ---- @@ -419,7 +405,7 @@ Optional. <6> A reference to a `MessageGroupStore` used to store groups of messages under their correlation key until they are complete. Optional. By default, it is a volatile in-memory store. -See <<./message-store.adoc#message-store,Message Store>> for more information. +See xref:message-store.adoc[Message Store] for more information. <7> The order of this aggregator when more than one handle is subscribed to the same `DirectChannel` (use for load-balancing purposes). Optional. <8> Indicates that expired messages should be aggregated and sent to the 'output-channel' or 'replyChannel' once their containing `MessageGroup` is expired (see https://docs.spring.io/spring-integration/api/org/springframework/integration/store/MessageGroupStore.html#expireMessageGroups-long[`MessageGroupStore.expireMessageGroups(long)`]). @@ -477,7 +463,7 @@ Note that the actual time to expire an empty group is also affected by the reape It used to obtain a `Lock` based on the `groupId` for concurrent operations on the `MessageGroup`. By default, an internal `DefaultLockRegistry` is used. Use of a distributed `LockRegistry`, such as the `ZookeeperLockRegistry`, ensures only one instance of the aggregator can operate on a group concurrently. -See <<./redis.adoc#redis-lock-registry,Redis Lock Registry>> or <<./zookeeper.adoc#zk-lock-registry,Zookeeper Lock Registry>> for more information. +See xref:redis.adoc#redis-lock-registry[Redis Lock Registry] or xref:zookeeper.adoc#zk-lock-registry[Zookeeper Lock Registry] for more information. <21> A timeout (in milliseconds) to force the `MessageGroup` complete when the `ReleaseStrategy` does not release the group when the current message arrives. This attribute provides a built-in time-based release strategy for the aggregator when there is a need to emit a partial result (or discard the group) if a new message does not arrive for the `MessageGroup` within the timeout which counts from the time the last message arrived. To set up a timeout which counts from the time the `MessageGroup` was created see `group-timeout-expression` information. @@ -488,7 +474,7 @@ Doing so effectively disables the aggregator, because every message group is imm You can, however, conditionally set it to zero (or a negative value) by using an expression. See `group-timeout-expression` for information. The action taken during the completion depends on the `ReleaseStrategy` and the `send-partial-group-on-expiry` attribute. -See <> for more information. +See xref:aggregator.adoc#agg-and-group-to[Aggregator and Group Timeout] for more information. It is mutually exclusive with 'group-timeout-expression' attribute. <22> The SpEL expression that evaluates to a `groupTimeout` with the `MessageGroup` as the `#root` evaluation context object. Used for scheduling the `MessageGroup` to be forced complete. @@ -516,7 +502,6 @@ It allows the configuration of any `Advice` for the `forceComplete` operation. It is initiated from a `group-timeout(-expression)` or by a `MessageGroupStoreReaper` and is not applied to the normal `add`, `release`, and `discard` operations. Only this sub-element or `` is allowed. A transaction `Advice` can also be configured here by using the Spring `tx` namespace. -==== [[aggregator-expiring-groups]] [IMPORTANT] @@ -558,21 +543,18 @@ With the aggregator, groups expired using this technique will either be discarde We generally recommend using a `ref` attribute if a custom aggregator handler implementation may be referenced in other `` definitions. However, if a custom aggregator implementation is only being used by a single definition of the ``, you can use an inner bean definition (starting with version 1.0.3) to configure the aggregation POJO within the `` element, as the following example shows: -==== [source,xml] ---- ---- -==== NOTE: Using both a `ref` attribute and an inner bean definition in the same `` configuration is not allowed, as it creates an ambiguous condition. In such cases, an Exception is thrown. The following example shows an implementation of the aggregator bean: -==== [source,java] ---- public class PojoAggregator { @@ -586,11 +568,9 @@ public class PojoAggregator { } } ---- -==== An implementation of the completion strategy bean for the preceding example might be as follows: -==== [source,java] ---- public class PojoReleaseStrategy { @@ -604,13 +584,11 @@ public class PojoReleaseStrategy { } } ---- -==== NOTE: Wherever it makes sense to do so, the release strategy method and the aggregator method can be combined into a single bean. An implementation of the correlation strategy bean for the example above might be as follows: -==== [source,java] ---- public class PojoCorrelationStrategy { @@ -620,7 +598,6 @@ public class PojoCorrelationStrategy { } } ---- -==== The aggregator in the preceding example would group numbers by some criterion (in this case, the remainder after dividing by ten) and hold the group until the sum of the numbers provided by the payloads exceeds a certain value. @@ -628,7 +605,7 @@ NOTE: Wherever it makes sense to do so, the release strategy method, the correla (Actually, all of them or any two of them can be combined.) [[aggregator-spel]] -====== Aggregators and Spring Expression Language (SpEL) +==== Aggregators and Spring Expression Language (SpEL) Since Spring Integration 2.0, you can handle the various strategies (correlation, release, and aggregation) with https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions[SpEL], which we recommend if the logic behind such a release strategy is relatively simple. Suppose you have a legacy component that was designed to receive an array of objects. @@ -638,7 +615,6 @@ First, we need to extract individual messages from the list. Second, we need to extract the payload of each message and assemble the array of objects. The following example solves both problems: -==== [source,java] ---- public String[] processRelease(List> messages){ @@ -649,19 +625,16 @@ public String[] processRelease(List> messages){ return stringList.toArray(new String[]{}); } ---- -==== However, with SpEL, such a requirement could actually be handled relatively easily with a one-line expression, thus sparing you from writing a custom class and configuring it as a bean. The following example shows how to do so: -==== [source,xml] ---- ---- -==== In the preceding configuration, we use a https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions[collection projection] expression to assemble a new collection from the payloads of all the messages in the list and then transform it to an array, thus achieving the same result as the earlier Java code. @@ -669,12 +642,10 @@ You can apply the same expression-based approach when dealing with custom releas Instead of defining a bean for a custom `CorrelationStrategy` in the `correlation-strategy` attribute, you can implement your simple correlation logic as a SpEL expression and configure it in the `correlation-strategy-expression` attribute, as the following example shows: -==== [source,xml] ---- correlation-strategy-expression="payload.person.id" ---- -==== In the preceding example, we assume that the payload has a `person` attribute with an `id`, which is going to be used to correlate messages. @@ -684,24 +655,21 @@ The `List` of messages can be referenced by using the `message` property of the NOTE: In releases prior to version 5.0, the root object was the collection of `Message`, as the previous example shows: -==== [source,xml] ---- release-strategy-expression="!messages.?[payload==5].empty" ---- -==== In the preceding example, the root object of the SpEL evaluation context is the `MessageGroup` itself, and you are stating that, as soon as there is a message with payload of `5` in this group, the group should be released. [[agg-and-group-to]] -====== Aggregator and Group Timeout +==== Aggregator and Group Timeout Starting with version 4.0, two new mutually exclusive attributes have been introduced: `group-timeout` and `group-timeout-expression`. -See <>. +See xref:aggregator.adoc#aggregator-xml[Configuring an Aggregator with XML]. In some cases, you may need to emit the aggregator result (or discard the group) after a timeout if the `ReleaseStrategy` does not release when the current message arrives. For this purpose, the `groupTimeout` option lets scheduling the `MessageGroup` be forced to complete, as the following example shows: -==== [source,xml] ---- ---- -==== With this example, the normal release is possible if the aggregator receives the last message in sequence as defined by the `release-strategy-expression`. If that specific message does not arrive, the `groupTimeout` forces the group to complete after ten seconds, as long as the group contains at least two Messages. @@ -721,7 +688,7 @@ If the release strategy still does not release the group, it is expired. If `send-partial-result-on-expiry` is `true`, existing messages in the (partial) `MessageGroup` are released as a normal aggregator reply message to the `output-channel`. Otherwise, it is discarded. -There is a difference between `groupTimeout` behavior and `MessageGroupStoreReaper` (see <>). +There is a difference between `groupTimeout` behavior and `MessageGroupStoreReaper` (see xref:aggregator.adoc#aggregator-xml[Configuring an Aggregator with XML]). The reaper initiates forced completion for all `MessageGroup` s in the `MessageGroupStore` periodically. The `groupTimeout` does it for each `MessageGroup` individually if a new message does not arrive during the `groupTimeout`. Also, the reaper can be used to remove empty groups (empty groups are retained in order to discard late messages if `expire-groups-upon-completion` is false). @@ -729,19 +696,16 @@ Also, the reaper can be used to remove empty groups (empty groups are retained i Starting with version 5.5, the `groupTimeoutExpression` can be evaluated to a `java.util.Date` instance. This can be useful in cases like determining a scheduled task moment based on the group creation time (`MessageGroup.getTimestamp()`) instead of a current message arrival as it is calculated when `groupTimeoutExpression` is evaluated to `long`: -==== [source,xml] ---- group-timeout-expression="size() ge 2 ? new java.util.Date(timestamp + 200) : null" ---- -==== [[aggregator-annotations]] -===== Configuring an Aggregator with Annotations +=== Configuring an Aggregator with Annotations The following example shows an aggregator configured with annotations: -==== [source,java] ---- public class Waiter { @@ -770,7 +734,6 @@ It must be specified if this class is used as an aggregator. If not present on any method, the aggregator uses the `SimpleSequenceSizeReleaseStrategy`. <3> An annotation indicating that this method should be used as the correlation strategy of an aggregator. If no correlation strategy is indicated, the aggregator uses the `HeaderAttributeCorrelationStrategy` based on `CORRELATION_ID`. -==== All of the configuration options provided by the XML element are also available for the `@Aggregator` annotation. @@ -779,7 +742,6 @@ The aggregator can be either referenced explicitly from XML or, if the `@Message Annotation configuration (`@Aggregator` and others) for the Aggregator component covers only simple use cases, where most default options are sufficient. If you need more control over those options when using annotation configuration, consider using a `@Bean` definition for the `AggregatingMessageHandler` and mark its `@Bean` method with `@ServiceActivator`, as the following example shows: -==== [source,java] ---- @ServiceActivator(inputChannel = "aggregatorChannel") @@ -794,21 +756,19 @@ public MessageHandler aggregator(MessageGroupStore jdbcMessageGroupStore) { return aggregator; } ---- -==== -See <> and <<./configuration.adoc#annotations_on_beans,Annotations on `@Bean` Methods>> for more information. +See xref:aggregator.adoc#aggregator-api[Programming Model] and xref:configuration/meta-annotations.adoc#annotations_on_beans[Annotations on `@Bean` Methods] for more information. NOTE: Starting with version 4.2, the `AggregatorFactoryBean` is available to simplify Java configuration for the `AggregatingMessageHandler`. [[reaper]] -==== Managing State in an Aggregator: `MessageGroupStore` +== Managing State in an Aggregator: `MessageGroupStore` Aggregator (and some other patterns in Spring Integration) is a stateful pattern that requires decisions to be made based on a group of messages that have arrived over a period of time, all with the same correlation key. The design of the interfaces in the stateful patterns (such as `ReleaseStrategy`) is driven by the principle that the components (whether defined by the framework or by a user) should be able to remain stateless. All state is carried by the `MessageGroup` and its management is delegated to the `MessageGroupStore`. The `MessageGroupStore` interface is defined as follows: -==== [source,java] ---- public interface MessageGroupStore { @@ -836,7 +796,6 @@ public interface MessageGroupStore { int expireMessageGroups(long timeout); } ---- -==== For more information, see the https://docs.spring.io/spring-integration/api/org/springframework/integration/store/MessageGroupStore.html[Javadoc]. @@ -844,7 +803,6 @@ The `MessageGroupStore` accumulates state information in `MessageGroups` while w So, to prevent stale messages from lingering, and for volatile stores to provide a hook for cleaning up when the application shuts down, the `MessageGroupStore` lets you register callbacks to apply to its `MessageGroups` when they expire. The interface is very straightforward, as the following listing shows: -==== [source,java] ---- public interface MessageGroupCallback { @@ -853,7 +811,6 @@ public interface MessageGroupCallback { } ---- -==== The callback has direct access to the store and the message group so that it can manage the persistent state (for example, by entirely removing the group from the store). @@ -872,7 +829,6 @@ Thus, it is the user of the store that defines what is meant by message group "` As a convenience for users, Spring Integration provides a wrapper for the message expiry in the form of a `MessageGroupStoreReaper`, as the following example shows: -==== [source,xml] ---- @@ -884,7 +840,6 @@ As a convenience for users, Spring Integration provides a wrapper for the messag ---- -==== The reaper is a `Runnable`. In the preceding example, the message group store's expire method is called every ten seconds. @@ -901,7 +856,7 @@ If the flag is set to `true`, then, when the expiry callback is invoked, any unm IMPORTANT: Since the `MessageGroupStoreReaper` is called from a scheduled task, and may result in the production of a message (depending on the `sendPartialResultOnExpiry` option) to a downstream integration flow, it is recommended to supply a custom `TaskScheduler` with a `MessagePublishingErrorHandler` to handler exceptions via an `errorChannel`, as it might be expected by the regular aggregator release functionality. The same logic applies for group timeout functionality which also relies on a `TaskScheduler`. -See <<./error-handling.adoc#error-handling,Error Handling>> for more information. +See xref:scatter-gather.adoc#scatter-gather-error-handling[Error Handling] for more information. [IMPORTANT] ===== @@ -912,11 +867,11 @@ Messages with the same correlation key are stored in the same message group. Some `MessageStore` implementations allow using the same physical resources, by partitioning the data. For example, the `JdbcMessageStore` has a `region` property, and the `MongoDbMessageStore` has a `collectionName` property. -For more information about the `MessageStore` interface and its implementations, see <<./message-store.adoc#message-store,Message Store>>. +For more information about the `MessageStore` interface and its implementations, see xref:message-store.adoc[Message Store]. ===== [[flux-aggregator]] -==== Flux Aggregator +== Flux Aggregator In version 5.2, the `FluxAggregatorMessageHandler` component has been introduced. It is based on the Project Reactor `Flux.groupBy()` and `Flux.window()` operators. @@ -932,7 +887,6 @@ This `Flux` in the output message payload must be subscribed and processed downs Such a logic can be customized (or superseded) by the `setCombineFunction(Function>, Mono>>)` configuration option of the `FluxAggregatorMessageHandler`. For example, if we would like to have a `List` of payloads in the final message, we can configure a `Flux.collectList()` like this: -==== [source,java] ---- fluxAggregatorMessageHandler.setCombineFunction( @@ -942,7 +896,6 @@ fluxAggregatorMessageHandler.setCombineFunction( .collectList() .map(GenericMessage::new)); ---- -==== There are several options in the `FluxAggregatorMessageHandler` to select an appropriate window strategy: @@ -958,7 +911,6 @@ Since this component is a `MessageHandler` implementation it can simply be used With Java DSL it can be used from the `.handle()` EIP-method. The sample below demonstrates how we can register an `IntegrationFlow` at runtime and how a `FluxAggregatorMessageHandler` can be correlated with a splitter upstream: -==== [source,java] ---- IntegrationFlow fluxFlow = @@ -976,14 +928,13 @@ Flux> window = registration.getMessagingTemplate() .convertSendAndReceive(new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, Flux.class); ---- -==== [[agg-message-group-condition]] -==== Condition on the Message Group +== Condition on the Message Group Starting with version 5.5, an `AbstractCorrelatingMessageHandler` (including its Java & XML DSLs) exposes a `groupConditionSupplier` option of the `BiFunction, String, String>` implementation. This function is used on each message added to the group and a result condition sentence is stored into the group for future consideration. The `ReleaseStrategy` may consult this condition instead of iterating over all the messages in the group. -See `GroupConditionProvider` JavaDocs and <<./message-store.adoc#message-group-condition, Message Group Condition>> for more information. +See `GroupConditionProvider` JavaDocs and xref:message-store.adoc#message-group-condition[Message Group Condition] for more information. -See also <<./file.adoc#file-aggregator, File Aggregator>>. +See also xref:file/aggregator.adoc[File Aggregator]. diff --git a/src/reference/antora/modules/ROOT/pages/amqp.adoc b/src/reference/antora/modules/ROOT/pages/amqp.adoc new file mode 100644 index 0000000000..b6725a1706 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp.adoc @@ -0,0 +1,49 @@ +[[amqp]] += AMQP (RabbitMQ) Support + +Spring Integration provides channel adapters for receiving and sending messages by using the Advanced Message Queuing Protocol (AMQP). + +You need to include this dependency into your project: + +[tabs] +====== +Maven:: ++ +[source, xml, subs="normal", role="primary"] +---- + + org.springframework.integration + spring-integration-amqp + {project-version} + +---- + +Gradle:: ++ +[source, groovy, subs="normal", role="secondary"] +---- +compile "org.springframework.integration:spring-integration-amqp:{project-version}" +---- +====== + +The following adapters are available: + +* xref:amqp/inbound-channel-adapter.adoc[Inbound Channel Adapter] +* xref:amqp/inbound-gateway.adoc[Inbound Gateway] +* xref:amqp/outbound-channel-adapter.adoc[Outbound Channel Adapter] +* xref:amqp/outbound-gateway.adoc[Outbound Gateway] +* xref:amqp-async-outbound-gateway[Async Outbound Gateway] +* xref:amqp/rmq-streams.adoc#rmq-stream-inbound-channel-adapter[RabbitMQ Stream Queue Inbound Channel Adapter] +* xref:amqp/rmq-streams.adoc#rmq-stream-outbound-channel-adapter[RabbitMQ Stream Queue Outbound Channel Adapter] + +Spring Integration also provides a point-to-point message channel and a publish-subscribe message channel backed by AMQP Exchanges and Queues. + +To provide AMQP support, Spring Integration relies on (https://projects.spring.io/spring-amqp[Spring AMQP]), which applies core Spring concepts to the development of AMQP-based messaging solutions. +Spring AMQP provides similar semantics to (https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#jms[Spring JMS]). + +Whereas the provided AMQP Channel Adapters are intended for unidirectional messaging (send or receive) only, Spring Integration also provides inbound and outbound AMQP gateways for request-reply operations. + +TIP: +You should familiarize yourself with the https://docs.spring.io/spring-amqp/reference/html/[reference documentation of the Spring AMQP project]. +It provides much more in-depth information about Spring's integration with AMQP in general and RabbitMQ in particular. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/alternative-confirms-returns.adoc b/src/reference/antora/modules/ROOT/pages/amqp/alternative-confirms-returns.adoc new file mode 100644 index 0000000000..87e9e4ff7e --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/alternative-confirms-returns.adoc @@ -0,0 +1,31 @@ +[[alternative-confirms-returns]] += Alternative Mechanism for Publisher Confirms and Returns + +When the connection factory is configured for publisher confirms and returns, the sections above discuss the configuration of message channels to receive the confirms and returns asynchronously. +Starting with version 5.4, there is an additional mechanism which is generally easier to use. + +In this case, do not configure a `confirm-correlation-expression` or the confirm and return channels. +Instead, add a `CorrelationData` instance in the `AmqpHeaders.PUBLISH_CONFIRM_CORRELATION` header; you can then wait for the result(s) later, by checking the state of the future in the `CorrelationData` instances for which you have sent messages. +The `returnedMessage` field will always be populated (if a message is returned) before the future is completed. + +[source, java] +---- +CorrelationData corr = new CorrelationData("someId"); // <--- Unique "id" is required for returns +someFlow.getInputChannel().send(MessageBuilder.withPayload("test") + .setHeader("rk", "someKeyThatWontRoute") + .setHeader(AmqpHeaders.PUBLISH_CONFIRM_CORRELATION, corr) + .build()); +... +try { + Confirm Confirm = corr.getFuture().get(10, TimeUnit.SECONDS); + Message returned = corr.getReturnedMessage(); + if (returned !- null) { + // message could not be routed + } +} +catch { ... } +---- + +To improve performance, you may wish to send multiple messages and wait for the confirmations later, rather than one-at-a-time. +The returned message is the raw message after conversion; you can sub-class a `CorrelationData` with whatever additional data you need. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/async-outbound-gateway.adoc b/src/reference/antora/modules/ROOT/pages/amqp/async-outbound-gateway.adoc new file mode 100644 index 0000000000..b5402dc00f --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/async-outbound-gateway.adoc @@ -0,0 +1,185 @@ +[[amqp-async-outbound-gateway]] += Asynchronous Outbound Gateway + +The gateway discussed in the previous section is synchronous, in that the sending thread is suspended until a +reply is received (or a timeout occurs). +Spring Integration version 4.3 added an asynchronous gateway, which uses the `AsyncRabbitTemplate` from Spring AMQP. +When a message is sent, the thread returns immediately after the send operation completes, and, when the message is received, the reply is sent on the template's listener container thread. +This can be useful when the gateway is invoked on a poller thread. +The thread is released and is available for other tasks in the framework. + +The following listing shows the possible configuration options for an AMQP asynchronous outbound gateway: + +[tabs] +====== +Java DSL:: ++ +[source,java,role="primary"] +---- +@Configuration +public class AmqpAsyncApplication { + + @Bean + public IntegrationFlow asyncAmqpOutbound(AsyncRabbitTemplate asyncRabbitTemplate) { + return f -> f + .handle(Amqp.asyncOutboundGateway(asyncRabbitTemplate) + .routingKey("queue1")); // default exchange - route to queue 'queue1' + } + + @MessagingGateway(defaultRequestChannel = "asyncAmqpOutbound.input") + public interface MyGateway { + + String sendToRabbit(String data); + + } + +} +---- + +Java:: ++ +[source,java,role="secondary"] +---- +@Configuration +public class AmqpAsyncConfig { + + @Bean + @ServiceActivator(inputChannel = "amqpOutboundChannel") + public AsyncAmqpOutboundGateway amqpOutbound(AsyncRabbitTemplate asyncTemplate) { + AsyncAmqpOutboundGateway outbound = new AsyncAmqpOutboundGateway(asyncTemplate); + outbound.setRoutingKey("foo"); // default exchange - route to queue 'foo' + return outbound; + } + + @Bean + public AsyncRabbitTemplate asyncTemplate(RabbitTemplate rabbitTemplate, + SimpleMessageListenerContainer replyContainer) { + + return new AsyncRabbitTemplate(rabbitTemplate, replyContainer); + } + + @Bean + public SimpleMessageListenerContainer replyContainer() { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(ccf); + container.setQueueNames("asyncRQ1"); + return container; + } + + @Bean + public MessageChannel amqpOutboundChannel() { + return new DirectChannel(); + } + +} +---- + +XML:: ++ +[source,xml,role="secondary"] +---- + + request-channel="myRequestChannel" <2> + async-template="" <3> + exchange-name="" <4> + exchange-name-expression="" <5> + order="1" <6> + reply-channel="" <7> + reply-timeout="" <8> + requires-reply="" <9> + routing-key="" <10> + routing-key-expression="" <11> + default-delivery-mode"" <12> + confirm-correlation-expression="" <13> + confirm-ack-channel="" <14> + confirm-nack-channel="" <15> + confirm-timeout="" <16> + return-channel="" <17> + lazy-connect="true" /> <18> + +---- +====== + +<1> The unique ID for this adapter. +Optional. +<2> Message channel to which messages should be sent in order to have them converted and published to an AMQP exchange. +Required. +<3> Bean reference to the configured `AsyncRabbitTemplate`. +Optional (it defaults to `asyncRabbitTemplate`). +<4> The name of the AMQP exchange to which messages should be sent. +If not provided, messages are sent to the default, no-name exchange. +Mutually exclusive with 'exchange-name-expression'. +Optional. +<5> A SpEL expression that is evaluated to determine the name of the AMQP exchange to which messages are sent, with the message as the root object. +If not provided, messages are sent to the default, no-name exchange. +Mutually exclusive with 'exchange-name'. +Optional. +<6> The order for this consumer when multiple consumers are registered, thereby enabling load-balancing and failover. +Optional (it defaults to `Ordered.LOWEST_PRECEDENCE [=Integer.MAX_VALUE]`). +<7> Message channel to which replies should be sent after being received from an AMQP queue and converted. +Optional. +<8> The time the gateway waits when sending the reply message to the `reply-channel`. +This only applies if the `reply-channel` can block -- such as a `QueueChannel` with a capacity limit that is currently full. +The default is infinity. +<9> When no reply message is received within the `AsyncRabbitTemplate`'s `receiveTimeout` property and this setting is `true`, the gateway sends an error message to the inbound message's `errorChannel` header. +When no reply message is received within the `AsyncRabbitTemplate`'s `receiveTimeout` property and this setting is `false`, the gateway sends an error message to the default `errorChannel` (if available). +It defaults to `true`. +<10> The routing-key to use when sending Messages. +By default, this is an empty `String`. +Mutually exclusive with 'routing-key-expression'. +Optional. +<11> A SpEL expression that is evaluated to determine the routing-key to use when sending messages, +with the message as the root object (for example, 'payload.key'). +By default, this is an empty `String`. +Mutually exclusive with 'routing-key'. +Optional. +<12> The default delivery mode for messages: `PERSISTENT` or `NON_PERSISTENT`. +Overridden if the `header-mapper` sets the delivery mode. +If the Spring Integration message header (`amqp_deliveryMode`) is present, the `DefaultHeaderMapper` sets the value. +If this attribute is not supplied and the header mapper does not set it, the default depends on the underlying Spring AMQP `MessagePropertiesConverter` used by the `RabbitTemplate`. +If that is not customized, the default is `PERSISTENT`. +Optional. +<13> An expression that defines correlation data. +When provided, this configures the underlying AMQP template to receive publisher confirmations. +Requires a dedicated `RabbitTemplate` and a `CachingConnectionFactory` with its `publisherConfirms` property set to `true`. +When a publisher confirmation is received and correlation data is supplied, the confirmation is written to either the `confirm-ack-channel` or the `confirm-nack-channel`, depending on the confirmation type. +The payload of the confirmation is the correlation data as defined by this expression, and the message has its 'amqp_publishConfirm' header set to `true` (`ack`) or `false` (`nack`). +For `nack` instances, an additional header (`amqp_publishConfirmNackCause`) is provided. +Examples: `headers['myCorrelationData']`, `payload`. +If the expression resolves to a `Message` instance (such as "`#this`"), the message emitted on the `ack`/`nack` channel is based on that message, with the additional headers added. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<14> The channel to which positive (`ack`) publisher confirmations are sent. +The payload is the correlation data defined by the `confirm-correlation-expression`. +Requires the underlying `AsyncRabbitTemplate` to have its `enableConfirms` property set to `true`. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<15> Since version 4.2. +The channel to which negative (`nack`) publisher confirmations are sent. +The payload is the correlation data defined by the `confirm-correlation-expression`. +Requires the underlying `AsyncRabbitTemplate` to have its `enableConfirms` property set to `true`. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<16> When set, the gateway will synthesize a negative acknowledgment (nack) if a publisher confirm is not received within this time in milliseconds. +Pending confirms are checked every 50% of this value, so the actual time a nack is sent will be between 1x and 1.5x this value. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Default none (nacks will not be generated). +<17> The channel to which returned messages are sent. +When provided, the underlying AMQP template is configured to return undeliverable messages to the gateway. +The message is constructed from the data received from AMQP, with the following additional headers: `amqp_returnReplyCode`, `amqp_returnReplyText`, `amqp_returnExchange`, and `amqp_returnRoutingKey`. +Requires the underlying `AsyncRabbitTemplate` to have its `mandatory` property set to `true`. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<18> When set to `false`, the endpoint tries to connect to the broker during application context initialization. +Doing so allows "`fail fast`" detection of bad configuration, by logging an error message if the broker is down. +When `true` (the default), the connection is established (if it does not already exist because some other component established +it) when the first message is sent. + +See also xref:service-activator.adoc#async-service-activator[Asynchronous Service Activator] for more information. + +[IMPORTANT] +.RabbitTemplate +==== +When you use confirmations and returns, we recommend that the `RabbitTemplate` wired into the `AsyncRabbitTemplate` be dedicated. +Otherwise, unexpected side effects may be encountered. +==== + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/channels.adoc b/src/reference/antora/modules/ROOT/pages/amqp/channels.adoc new file mode 100644 index 0000000000..881697b4f2 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/channels.adoc @@ -0,0 +1,136 @@ +[[amqp-channels]] += AMQP-backed Message Channels + +There are two message channel implementations available. +One is point-to-point, and the other is publish-subscribe. +Both of these channels provide a wide range of configuration attributes for the underlying `AmqpTemplate` and +`SimpleMessageListenerContainer` (as shown earlier in this chapter for the channel adapters and gateways). +However, the examples we show here have minimal configuration. +Explore the XML schema to view the available attributes. + +A point-to-point channel might look like the following example: + +[source,xml] +---- + +---- + +Under the covers, the preceding example causes a `Queue` named `si.p2pChannel` to be declared, and this channel sends to that `Queue` (technically, by sending to the no-name direct exchange with a routing key that matches the name of this `Queue`). +This channel also registers a consumer on that `Queue`. +If you want the channel to be "`pollable`" instead of message-driven, provide the `message-driven` flag with a value of `false`, as the following example shows: + +[source,xml] +---- + +---- + +A publish-subscribe channel might look like the following: + +[source,xml] +---- + +---- + +Under the covers, the preceding example causes a fanout exchange named `si.fanout.pubSubChannel` to be declared, and this channel sends to that fanout exchange. +This channel also declares a server-named exclusive, auto-delete, non-durable `Queue` and binds that to the fanout exchange while registering a consumer on that `Queue` to receive messages. +There is no "`pollable`" option for a publish-subscribe-channel. +It must be message-driven. + +Starting with version 4.1, AMQP-backed message channels (in conjunction with `channel-transacted`) support +`template-channel-transacted` to separate `transactional` configuration for the `AbstractMessageListenerContainer` and +for the `RabbitTemplate`. +Note that, previously, `channel-transacted` was `true` by default. +Now, by default, it is `false` for the `AbstractMessageListenerContainer`. + +Prior to version 4.3, AMQP-backed channels only supported messages with `Serializable` payloads and headers. +The entire message was converted (serialized) and sent to RabbitMQ. +Now, you can set the `extract-payload` attribute (or `setExtractPayload()` when using Java configuration) to `true`. +When this flag is `true`, the message payload is converted and the headers are mapped, in a manner similar to when you use channel adapters. +This arrangement lets AMQP-backed channels be used with non-serializable payloads (perhaps with another message converter, such as the `Jackson2JsonMessageConverter`). +See xref:amqp/message-headers.adoc[AMQP Message Headers] for more about the default mapped headers. +You can modify the mapping by providing custom mappers that use the `outbound-header-mapper` and `inbound-header-mapper` attributes. +You can now also specify a `default-delivery-mode`, which is used to set the delivery mode when there is no `amqp_deliveryMode` header. +By default, Spring AMQP `MessageProperties` uses `PERSISTENT` delivery mode. + +IMPORTANT: As with other persistence-backed channels, AMQP-backed channels are intended to provide message persistence to avoid message loss. +They are not intended to distribute work to other peer applications. +For that purpose, use channel adapters instead. + +IMPORTANT: Starting with version 5.0, the pollable channel now blocks the poller thread for the specified `receiveTimeout` (the default is 1 second). +Previously, unlike other `PollableChannel` implementations, the thread returned immediately to the scheduler if no message was available, regardless of the receive timeout. +Blocking is a little more expensive than using a `basicGet()` to retrieve a message (with no timeout), because a consumer has to be created to receive each message. +To restore the previous behavior, set the poller's `receiveTimeout` to 0. + +[[configuring-with-java-configuration]] +== Configuring with Java Configuration + +The following example shows how to configure the channels with Java configuration: + +[source, java] +---- +@Bean +public AmqpChannelFactoryBean pollable(ConnectionFactory connectionFactory) { + AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(); + factoryBean.setConnectionFactory(connectionFactory); + factoryBean.setQueueName("foo"); + factoryBean.setPubSub(false); + return factoryBean; +} + +@Bean +public AmqpChannelFactoryBean messageDriven(ConnectionFactory connectionFactory) { + AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(true); + factoryBean.setConnectionFactory(connectionFactory); + factoryBean.setQueueName("bar"); + factoryBean.setPubSub(false); + return factoryBean; +} + +@Bean +public AmqpChannelFactoryBean pubSub(ConnectionFactory connectionFactory) { + AmqpChannelFactoryBean factoryBean = new AmqpChannelFactoryBean(true); + factoryBean.setConnectionFactory(connectionFactory); + factoryBean.setQueueName("baz"); + factoryBean.setPubSub(false); + return factoryBean; +} +---- + +[[configuring-with-the-java-dsl]] +== Configuring with the Java DSL + +The following example shows how to configure the channels with the Java DSL: + +[source, java] +---- +@Bean +public IntegrationFlow pollableInFlow(ConnectionFactory connectionFactory) { + return IntegrationFlow.from(...) + ... + .channel(Amqp.pollableChannel(connectionFactory) + .queueName("foo")) + ... + .get(); +} + +@Bean +public IntegrationFlow messageDrivenInFow(ConnectionFactory connectionFactory) { + return IntegrationFlow.from(...) + ... + .channel(Amqp.channel(connectionFactory) + .queueName("bar")) + ... + .get(); +} + +@Bean +public IntegrationFlow pubSubInFlow(ConnectionFactory connectionFactory) { + return IntegrationFlow.from(...) + ... + .channel(Amqp.publishSubscribeChannel(connectionFactory) + .queueName("baz")) + ... + .get(); +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/content-type-conversion-outbound.adoc b/src/reference/antora/modules/ROOT/pages/amqp/content-type-conversion-outbound.adoc new file mode 100644 index 0000000000..727a310ec5 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/content-type-conversion-outbound.adoc @@ -0,0 +1,57 @@ +[[content-type-conversion-outbound]] += Outbound Message Conversion + +Spring AMQP 1.4 introduced the `ContentTypeDelegatingMessageConverter`, where the actual converter is selected based +on the incoming content type message property. +This can be used by inbound endpoints. + +As of Spring Integration version 4.3, you can use the `ContentTypeDelegatingMessageConverter` on outbound endpoints as well, with the `contentType` header specifying which converter is used. + +The following example configures a `ContentTypeDelegatingMessageConverter`, with the default converter being the `SimpleMessageConverter` (which handles Java serialization and plain text), together with a JSON converter: + +[source, xml] +---- + + + + + + + + + + + + + + + +---- + +Sending a message to `ctRequestChannel` with the `contentType` header set to `application/json` causes the JSON converter to be selected. + +This applies to both the outbound channel adapter and gateway. + +[NOTE] +==== +Starting with version 5.0, headers that are added to the `MessageProperties` of the outbound message are never overwritten by mapped headers (by default). +Previously, this was only the case if the message converter was a `ContentTypeDelegatingMessageConverter` (in that case, the header was mapped first so that the proper converter could be selected). +For other converters, such as the `SimpleMessageConverter`, mapped headers overwrote any headers added by the converter. +This caused problems when an outbound message had some leftover `contentType` headers (perhaps from an inbound channel adapter) and the correct outbound `contentType` was incorrectly overwritten. +The work-around was to use a header filter to remove the header before sending the message to the outbound endpoint. + +There are, however, cases where the previous behavior is desired -- for example, when a `String` payload that contains JSON, the `SimpleMessageConverter` is not aware of the content and sets the `contentType` message property to `text/plain` but your application would like to override that to `application/json` by setting the `contentType` header of the message sent to the outbound endpoint. +The `ObjectToJsonTransformer` does exactly that (by default). + +There is now a property called `headersMappedLast` on the outbound channel adapter and gateway (as well as on AMQP-backed channels). +Setting this to `true` restores the behavior of overwriting the property added by the converter. + +Starting with version 5.1.9, a similar `replyHeadersMappedLast` is provided for the `AmqpInboundGateway` when we produce a reply and would like to override headers populated by the converter. +See its JavaDocs for more information. +==== + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/conversion-inbound.adoc b/src/reference/antora/modules/ROOT/pages/amqp/conversion-inbound.adoc new file mode 100644 index 0000000000..366b29c305 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/conversion-inbound.adoc @@ -0,0 +1,15 @@ +[[amqp-conversion-inbound]] += Inbound Message Conversion +:page-section-summary-toc: 1 + +Inbound messages, arriving at the channel adapter or gateway, are converted to the `spring-messaging` `Message` payload using a message converter. +By default, a `SimpleMessageConverter` is used, which handles java serialization and text. +Headers are mapped using the `DefaultHeaderMapper.inboundMapper()` by default. +If a conversion error occurs, and there is no error channel defined, the exception is thrown to the container and handled by the listener container's error handler. +The default error handler treats conversion errors as fatal and the message will be rejected (and routed to a dead-letter exchange, if the queue is so configured). +If an error channel is defined, the `ErrorMessage` payload is a `ListenerExecutionFailedException` with properties `failedMessage` (the Spring AMQP message that could not be converted) and the `cause`. +If the container `AcknowledgeMode` is `AUTO` (the default) and the error flow consumes the error without throwing an exception, the original message will be acknowledged. +If the error flow throws an exception, the exception type, in conjunction with the container's error handler, will determine whether the message is requeued. +If the container is configured with `AcknowledgeMode.MANUAL`, the payload is a `ManualAckListenerExecutionFailedException` with additional properties `channel` and `deliveryTag`. +This enables the error flow to call `basicAck` or `basicNack` (or `basicReject`) for the message, to control its disposition. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/delay.adoc b/src/reference/antora/modules/ROOT/pages/amqp/delay.adoc new file mode 100644 index 0000000000..dd6f9ac1c7 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/delay.adoc @@ -0,0 +1,10 @@ +[[amqp-delay]] += Delayed Message Exchange +:page-section-summary-toc: 1 + +Spring AMQP supports the https://docs.spring.io/spring-amqp/reference/html/#delayed-message-exchange[RabbitMQ Delayed Message Exchange Plugin]. +For inbound messages, the `x-delay` header is mapped to the `AmqpHeaders.RECEIVED_DELAY` header. +Setting the `AMQPHeaders.DELAY` header causes the corresponding `x-delay` header to be set in outbound messages. +You can also specify the `delay` and `delayExpression` properties on outbound endpoints (`delay-expression` when using XML configuration). +These properties take precedence over the `AmqpHeaders.DELAY` header. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/inbound-ack.adoc b/src/reference/antora/modules/ROOT/pages/amqp/inbound-ack.adoc new file mode 100644 index 0000000000..3d99b52bad --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/inbound-ack.adoc @@ -0,0 +1,36 @@ +[[amqp-inbound-ack]] += Inbound Endpoint Acknowledge Mode + +By default, the inbound endpoints use the `AUTO` acknowledge mode, which means the container automatically acknowledges the message when the downstream integration flow completes (or a message is handed off to another thread by using a `QueueChannel` or `ExecutorChannel`). +Setting the mode to `NONE` configures the consumer such that acknowledgments are not used at all (the broker automatically acknowledges the message as soon as it is sent). +Setting the mode to `MANUAL` lets user code acknowledge the message at some other point during processing. +To support this, with this mode, the endpoints provide the `Channel` and `deliveryTag` in the `amqp_channel` and `amqp_deliveryTag` headers, respectively. + +You can perform any valid Rabbit command on the `Channel` but, generally, only `basicAck` and `basicNack` (or `basicReject`) are used. +In order to not interfere with the operation of the container, you should not retain a reference to the channel and use it only in the context of the current message. + +NOTE: Since the `Channel` is a reference to a "`live`" object, it cannot be serialized and is lost if a message is persisted. + +The following example shows how you might use `MANUAL` acknowledgement: + +[source,java] +---- +@ServiceActivator(inputChannel = "foo", outputChannel = "bar") +public Object handle(@Payload String payload, @Header(AmqpHeaders.CHANNEL) Channel channel, + @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws Exception { + + // Do some processing + + if (allOK) { + channel.basicAck(deliveryTag, false); + + // perhaps do some more processing + + } + else { + channel.basicNack(deliveryTag, false, true); + } + return someResultForDownStreamProcessing; +} +---- + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/inbound-channel-adapter.adoc b/src/reference/antora/modules/ROOT/pages/amqp/inbound-channel-adapter.adoc new file mode 100644 index 0000000000..e8ae8d91de --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/inbound-channel-adapter.adoc @@ -0,0 +1,263 @@ +[[amqp-inbound-channel-adapter]] += Inbound Channel Adapter + +The following listing shows the possible configuration options for an AMQP Inbound Channel Adapter: + +[tabs] +====== +Java DSL:: ++ +[source, java, role="primary"] +---- +@Bean +public IntegrationFlow amqpInbound(ConnectionFactory connectionFactory) { + return IntegrationFlow.from(Amqp.inboundAdapter(connectionFactory, "aName")) + .handle(m -> System.out.println(m.getPayload())) + .get(); +} +---- + +Java:: ++ +[source, java, role="secondary"] +---- +@Bean +public MessageChannel amqpInputChannel() { + return new DirectChannel(); +} + +@Bean +public AmqpInboundChannelAdapter inbound(SimpleMessageListenerContainer listenerContainer, + @Qualifier("amqpInputChannel") MessageChannel channel) { + AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer); + adapter.setOutputChannel(channel); + return adapter; +} + +@Bean +public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory) { + SimpleMessageListenerContainer container = + new SimpleMessageListenerContainer(connectionFactory); + container.setQueueNames("aName"); + container.setConcurrentConsumers(2); + // ... + return container; +} + +@Bean +@ServiceActivator(inputChannel = "amqpInputChannel") +public MessageHandler handler() { + return new MessageHandler() { + + @Override + public void handleMessage(Message message) throws MessagingException { + System.out.println(message.getPayload()); + } + + }; +} +---- + +XML:: ++ +[source, xml, role="secondary"] +---- + + channel="inboundChannel" <2> + queue-names="si.test.queue" <3> + acknowledge-mode="AUTO" <4> + advice-chain="" <5> + channel-transacted="" <6> + concurrent-consumers="" <7> + connection-factory="" <8> + error-channel="" <9> + expose-listener-channel="" <10> + header-mapper="" <11> + mapped-request-headers="" <12> + listener-container="" <13> + message-converter="" <14> + message-properties-converter="" <15> + phase="" <16> + prefetch-count="" <17> + receive-timeout="" <18> + recovery-interval="" <19> + missing-queues-fatal="" <20> + shutdown-timeout="" <21> + task-executor="" <22> + transaction-attribute="" <23> + transaction-manager="" <24> + tx-size="" <25> + consumers-per-queue <26> + batch-mode="MESSAGES"/> <27> + +<1> The unique ID for this adapter. +Optional. +<2> Message channel to which converted messages should be sent. +Required. +<3> Names of the AMQP queues (comma-separated list) from which messages should be consumed. +Required. +<4> Acknowledge mode for the `MessageListenerContainer`. +When set to `MANUAL`, the delivery tag and channel are provided in message headers `amqp_deliveryTag` and `amqp_channel`, respectively. +The user application is responsible for acknowledgement. +`NONE` means no acknowledgements (`autoAck`). +`AUTO` means the adapter's container acknowledges when the downstream flow completes. +Optional (defaults to AUTO). +See xref:amqp/inbound-ack.adoc[Inbound Endpoint Acknowledge Mode]. +<5> Extra AOP Advices to handle cross-cutting behavior associated with this inbound channel adapter. +Optional. +<6> Flag to indicate that channels created by this component are transactional. +If true, it tells the framework to use a transactional channel and to end all operations (send or receive) with a commit or rollback, depending on the outcome, with an exception that signals a rollback. +Optional (Defaults to false). +<7> Specify the number of concurrent consumers to create. +The default is `1`. +We recommend raising the number of concurrent consumers to scale the consumption of messages coming in from a queue. +However, note that any ordering guarantees are lost once multiple consumers are registered. +In general, use one consumer for low-volume queues. +Not allowed when 'consumers-per-queue' is set. +Optional. +<8> Bean reference to the RabbitMQ `ConnectionFactory`. +Optional (defaults to `connectionFactory`). +<9> Message channel to which error messages should be sent. +Optional. +<10> Whether the listener channel (com.rabbitmq.client.Channel) is exposed to a registered `ChannelAwareMessageListener`. +Optional (defaults to true). +<11> A reference to an `AmqpHeaderMapper` to use when receiving AMQP Messages. +Optional. +By default, only standard AMQP properties (such as `contentType`) are copied to Spring Integration `MessageHeaders`. +Any user-defined headers within the AMQP `MessageProperties` are NOT copied to the message by the default `DefaultAmqpHeaderMapper`. +Not allowed if 'request-header-names' is provided. +<12> Comma-separated list of the names of AMQP Headers to be mapped from the AMQP request into the `MessageHeaders`. +This can only be provided if the 'header-mapper' reference is not provided. +The values in this list can also be simple patterns to be matched against the header names (such as "\*" or "thing1*, thing2" or "*something"). +<13> Reference to the `AbstractMessageListenerContainer` to use for receiving AMQP Messages. +If this attribute is provided, no other attribute related to the listener container configuration should be provided. +In other words, by setting this reference, you must take full responsibility for the listener container configuration. +The only exception is the `MessageListener` itself. +Since that is actually the core responsibility of this channel adapter implementation, the referenced listener container must not already have its own `MessageListener`. +Optional. +<14> The `MessageConverter` to use when receiving AMQP messages. +Optional. +<15> The `MessagePropertiesConverter` to use when receiving AMQP messages. +Optional. +<16> Specifies the phase in which the underlying `AbstractMessageListenerContainer` should be started and stopped. +The startup order proceeds from lowest to highest, and the shutdown order is the reverse of that. +By default, this value is `Integer.MAX_VALUE`, meaning that this container starts as late as possible and stops as soon as possible. +Optional. +<17> Tells the AMQP broker how many messages to send to each consumer in a single request. +Often, you can set this value high to improve throughput. +It should be greater than or equal to the transaction size (see the `tx-size` attribute, later in this list). +Optional (defaults to `1`). +<18> Receive timeout in milliseconds. +Optional (defaults to `1000`). +<19> Specifies the interval between recovery attempts of the underlying `AbstractMessageListenerContainer` (in milliseconds). +Optional (defaults to `5000`). +<20> If 'true' and none of the queues are available on the broker, the container throws a fatal exception during startup and stops if the queues are deleted when the container is running (after making three attempts to passively declare the queues). +If `false`, the container does not throw an exception and goes into recovery mode, attempting to restart according to the `recovery-interval`. +Optional (defaults to `true`). +<21> The time to wait for workers (in milliseconds) after the underlying `AbstractMessageListenerContainer` is stopped and before the AMQP connection is forced closed. +If any workers are active when the shutdown signal comes, they are allowed to finish processing as long as they can finish within this timeout. +Otherwise, the connection is closed and messages remain unacknowledged (if the channel is transactional). +Optional (defaults to `5000`). +<22> By default, the underlying `AbstractMessageListenerContainer` uses a `SimpleAsyncTaskExecutor` implementation, that fires up a new thread for each task, running it asynchronously. +By default, the number of concurrent threads is unlimited. +Note that this implementation does not reuse threads. +Consider using a thread-pooling `TaskExecutor` implementation as an alternative. +Optional (defaults to `SimpleAsyncTaskExecutor`). +<23> By default, the underlying `AbstractMessageListenerContainer` creates a new instance of the `DefaultTransactionAttribute` (it takes the EJB approach to rolling back on runtime but not checked exceptions). +Optional (defaults to `DefaultTransactionAttribute`). +<24> Sets a bean reference to an external `PlatformTransactionManager` on the underlying `AbstractMessageListenerContainer`. +The transaction manager works in conjunction with the `channel-transacted` attribute. +If there is already a transaction in progress when the framework is sending or receiving a message and the `channelTransacted` flag is `true`, the commit or rollback of the messaging transaction is deferred until the end of the current transaction. +If the `channelTransacted` flag is `false`, no transaction semantics apply to the messaging operation (it is auto-acked). +For further information, see +https://docs.spring.io/spring-amqp/reference/html/%255Freference.html#%5Ftransactions[Transactions with Spring AMQP]. +Optional. +<25> Tells the `SimpleMessageListenerContainer` how many messages to process in a single transaction (if the channel is transactional). +For best results, it should be less than or equal to the value set in `prefetch-count`. +Not allowed when 'consumers-per-queue' is set. +Optional (defaults to `1`). +<26> Indicates that the underlying listener container should be a `DirectMessageListenerContainer` instead of the default `SimpleMessageListenerContainer`. +See the https://docs.spring.io/spring-amqp/reference/html/[Spring AMQP Reference Manual] for more information. +<27> When the container's `consumerBatchEnabled` is `true`, determines how the adapter presents the batch of messages in the message payload. +When set to `MESSAGES` (default), the payload is a `List>` where each message has headers mapped from the incoming AMQP `Message` and the payload is the converted `body`. +When set to `EXTRACT_PAYLOADS`, the payload is a `List` where the elements are converted from the AMQP `Message` body. +`EXTRACT_PAYLOADS_WITH_HEADERS` is similar to `EXTRACT_PAYLOADS` but, in addition, the headers from each message are mapped from the `MessageProperties` into a `List` at the corresponding index; the header name is `AmqpInboundChannelAdapter.CONSOLIDATED_HEADERS`. +---- +====== + +[NOTE] +.container +==== +Note that when configuring an external container with XML, you cannot use the Spring AMQP namespace to define the container. +This is because the namespace requires at least one `` element. +In this environment, the listener is internal to the adapter. +For this reason, you must define the container by using a normal Spring `` definition, as the following example shows: + +[source,xml] +---- + + + + + + +---- +==== + +IMPORTANT: Even though the Spring Integration JMS and AMQP support is similar, important differences exist. +The JMS inbound channel adapter is using a `JmsDestinationPollingSource` under the covers and expects a configured poller. +The AMQP inbound channel adapter uses an `AbstractMessageListenerContainer` and is message driven. +In that regard, it is more similar to the JMS message-driven channel adapter. + +Starting with version 5.5, the `AmqpInboundChannelAdapter` can be configured with an `org.springframework.amqp.rabbit.retry.MessageRecoverer` strategy which is used in the `RecoveryCallback` when the retry operation is called internally. +See `setMessageRecoverer()` JavaDocs for more information. + +The `@Publisher` annotation also can be used in combination with a `@RabbitListener`: + +[source, java] +---- +@Configuration +@EnableIntegration +@EnableRabbit +@EnablePublisher +public static class ContextConfiguration { + + @Bean + QueueChannel fromRabbitViaPublisher() { + return new QueueChannel(); + } + + @RabbitListener(queuesToDeclare = @Queue("publisherQueue")) + @Publisher("fromRabbitViaPublisher") + @Payload("#args.payload.toUpperCase()") + public void consumeForPublisher(String payload) { + + } + +} +---- + +By default, the `@Publisher` AOP interceptor deals with a return value from a method call. +However, the return value from a `@RabbitListener` method is treated as an AMQP reply message. +Therefore, such an approach cannot be used together with a `@Publisher`, so a `@Payload` annotation with respective SpEL expression against method arguments is a recommended way for this combination. +See more information about the `@Publisher` in the xref:message-publishing.adoc#publisher-annotation[Annotation-driven Configuration] section. + +IMPORTANT: When using exclusive or single-active consumers in the listener container, it is recommended that you set the container property `forceStop` to `true`. +This will prevent a race condition where, after stopping the container, another consumer could start consuming messages before this instance has fully stopped. + +[[amqp-debatching]] +== Batched Messages + +See https://docs.spring.io/spring-amqp/docs/current/reference/html/#template-batching[the Spring AMQP Documentation] for more information about batched messages. + +To produce batched messages with Spring Integration, simply configure the outbound endpoint with a `BatchingRabbitTemplate`. + +When receiving batched messages, by default, the listener containers extract each fragment message and the adapter will produce a `Message` for each fragment. +Starting with version 5.2, if the container's `deBatchingEnabled` property is set to `false`, the de-batching is performed by the adapter instead, and a single `Message>` is produced with the payload being a list of the fragment payloads (after conversion if appropriate). + +The default `BatchingStrategy` is the `SimpleBatchingStrategy`, but this can be overridden on the adapter. + +NOTE: The `org.springframework.amqp.rabbit.retry.MessageBatchRecoverer` must be used with batches when recovery is required for retry operations. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/inbound-gateway.adoc b/src/reference/antora/modules/ROOT/pages/amqp/inbound-gateway.adoc new file mode 100644 index 0000000000..7fcdf9617e --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/inbound-gateway.adoc @@ -0,0 +1,121 @@ +[[amqp-inbound-gateway]] += Inbound Gateway + +The inbound gateway supports all the attributes on the inbound channel adapter (except that 'channel' is replaced by 'request-channel'), plus some additional attributes. +The following listing shows the available attributes: + +[tabs] +====== +Java DSL:: ++ +[source, java, role="primary"] +---- +@Bean // return the upper cased payload +public IntegrationFlow amqpInboundGateway(ConnectionFactory connectionFactory) { + return IntegrationFlow.from(Amqp.inboundGateway(connectionFactory, "foo")) + .transform(String.class, String::toUpperCase) + .get(); +} +---- + +Java:: ++ +[source, java, role="secondary"] +---- +@Bean +public MessageChannel amqpInputChannel() { + return new DirectChannel(); +} + +@Bean +public AmqpInboundGateway inbound(SimpleMessageListenerContainer listenerContainer, + @Qualifier("amqpInputChannel") MessageChannel channel) { + AmqpInboundGateway gateway = new AmqpInboundGateway(listenerContainer); + gateway.setRequestChannel(channel); + gateway.setDefaultReplyTo("bar"); + return gateway; +} + +@Bean +public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory) { + SimpleMessageListenerContainer container = + new SimpleMessageListenerContainer(connectionFactory); + container.setQueueNames("foo"); + container.setConcurrentConsumers(2); + // ... + return container; +} + +@Bean +@ServiceActivator(inputChannel = "amqpInputChannel") +public MessageHandler handler() { + return new AbstractReplyProducingMessageHandler() { + + @Override + protected Object handleRequestMessage(Message requestMessage) { + return "reply to " + requestMessage.getPayload(); + } + + }; +} +---- + +XML:: ++ +[source, xml, role="secondary"] +---- + + request-channel="myRequestChannel" <2> + header-mapper="" <3> + mapped-request-headers="" <4> + mapped-reply-headers="" <5> + reply-channel="myReplyChannel" <6> + reply-timeout="1000" <7> + amqp-template="" <8> + default-reply-to="" /> <9> + +---- +====== + +<1> The Unique ID for this adapter. +Optional. +<2> Message channel to which converted messages are sent. +Required. +<3> A reference to an `AmqpHeaderMapper` to use when receiving AMQP Messages. +Optional. +By default, only standard AMQP properties (such as `contentType`) are copied to and from Spring Integration `MessageHeaders`. +Any user-defined headers within the AMQP `MessageProperties` are not copied to or from an AMQP message by the default `DefaultAmqpHeaderMapper`. +Not allowed if 'request-header-names' or 'reply-header-names' is provided. +<4> Comma-separated list of names of AMQP Headers to be mapped from the AMQP request into the `MessageHeaders`. +This attribute can be provided only if the 'header-mapper' reference is not provided. +The values in this list can also be simple patterns to be matched against the header names (e.g. `"\*"` or `"thing1*, thing2"` or `"*thing1"`). +<5> Comma-separated list of names of `MessageHeaders` to be mapped into the AMQP message properties of the AMQP reply message. +All standard Headers (such as `contentType`) are mapped to AMQP Message Properties, while user-defined headers are mapped to the 'headers' property. +This attribute can only be provided if the 'header-mapper' reference is not provided. +The values in this list can also be simple patterns to be matched against the header names (for example, `"\*"` or `"foo*, bar"` or `"*foo"`). +<6> Message Channel where reply Messages are expected. +Optional. +<7> Sets the `receiveTimeout` on the underlying `o.s.i.core.MessagingTemplate` for receiving messages from the reply channel. +If not specified, this property defaults to `1000` (1 second). +Only applies if the container thread hands off to another thread before the reply is sent. +<8> The customized `AmqpTemplate` bean reference (to have more control over the reply messages to send). +You can provide an alternative implementation to the `RabbitTemplate`. +<9> The `replyTo` `o.s.amqp.core.Address` to be used when the `requestMessage` does not have a `replyTo` +property. +If this option is not specified, no `amqp-template` is provided, no `replyTo` property exists in the request message, and +an `IllegalStateException` is thrown because the reply cannot be routed. +If this option is not specified and an external `amqp-template` is provided, no exception is thrown. +You must either specify this option or configure a default `exchange` and `routingKey` on that template, +if you anticipate cases when no `replyTo` property exists in the request message. + +See the note in xref:amqp/inbound-channel-adapter.adoc[Inbound Channel Adapter] about configuring the `listener-container` attribute. + +Starting with version 5.5, the `AmqpInboundChannelAdapter` can be configured with an `org.springframework.amqp.rabbit.retry.MessageRecoverer` strategy which is used in the `RecoveryCallback` when the retry operation is called internally. +See `setMessageRecoverer()` JavaDocs for more information. + +[[amqp-gateway-debatching]] +== Batched Messages + +See xref:amqp/inbound-channel-adapter.adoc#amqp-debatching[Batched Messages]. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/message-headers.adoc b/src/reference/antora/modules/ROOT/pages/amqp/message-headers.adoc new file mode 100644 index 0000000000..04b3c58a20 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/message-headers.adoc @@ -0,0 +1,97 @@ +[[amqp-message-headers]] += AMQP Message Headers + +[[overview]] +== Overview + +The Spring Integration AMQP Adapters automatically map all AMQP properties and headers. +(This is a change from 4.3 - previously, only standard headers were mapped). +By default, these properties are copied to and from Spring Integration `MessageHeaders` by using the +https://docs.spring.io/spring-integration/api/org/springframework/integration/amqp/support/DefaultAmqpHeaderMapper.html[`DefaultAmqpHeaderMapper`]. + +You can pass in your own implementation of AMQP-specific header mappers, as the adapters have properties to support doing so. + +Any user-defined headers within the AMQP https://docs.spring.io/spring-amqp/api/org/springframework/amqp/core/MessageProperties.html[`MessageProperties`] are copied to or from an AMQP message, unless explicitly negated by the `requestHeaderNames` or `replyHeaderNames` properties of the `DefaultAmqpHeaderMapper`. +By default, for an outbound mapper, no `x-*` headers are mapped. +See the xref:amqp/message-headers.adoc#header-copy-caution[caution] that appears later in this section for why. + +To override the default and revert to the pre-4.3 behavior, use `STANDARD_REQUEST_HEADERS` and +`STANDARD_REPLY_HEADERS` in the properties. + +TIP: When mapping user-defined headers, the values can also contain simple wildcard patterns (such as `thing*` or `\*thing`) to be matched. +The `*` matches all headers. + +Starting with version 4.1, the `AbstractHeaderMapper` (a `DefaultAmqpHeaderMapper` superclass) lets the `NON_STANDARD_HEADERS` token be configured for the `requestHeaderNames` and `replyHeaderNames` properties (in addition to the existing `STANDARD_REQUEST_HEADERS` and `STANDARD_REPLY_HEADERS`) to map all user-defined headers. + +The `org.springframework.amqp.support.AmqpHeaders` class identifies the default headers that are used by the `DefaultAmqpHeaderMapper`: + +* `amqp_appId` +* `amqp_clusterId` +* `amqp_contentEncoding` +* `amqp_contentLength` +* `content-type` (see xref:amqp/message-headers.adoc#amqp-content-type[The `contentType` Header]) +* `amqp_correlationId` +* `amqp_delay` +* `amqp_deliveryMode` +* `amqp_deliveryTag` +* `amqp_expiration` +* `amqp_messageCount` +* `amqp_messageId` +* `amqp_receivedDelay` +* `amqp_receivedDeliveryMode` +* `amqp_receivedExchange` +* `amqp_receivedRoutingKey` +* `amqp_redelivered` +* `amqp_replyTo` +* `amqp_timestamp` +* `amqp_type` +* `amqp_userId` +* `amqp_publishConfirm` +* `amqp_publishConfirmNackCause` +* `amqp_returnReplyCode` +* `amqp_returnReplyText` +* `amqp_returnExchange` +* `amqp_returnRoutingKey` +* `amqp_channel` +* `amqp_consumerTag` +* `amqp_consumerQueue` + +[[header-copy-caution]] +CAUTION: As mentioned earlier in this section, using a header mapping pattern of `\*` is a common way to copy all headers. +However, this can have some unexpected side effects, because certain RabbitMQ proprietary properties/headers are also copied. +For example, when you use https://www.rabbitmq.com/federated-exchanges.html[federation], the received message may have a property named `x-received-from`, which contains the node that sent the message. +If you use the wildcard character `*` for the request and reply header mapping on the inbound gateway, this header is copied, which may cause some issues with federation. +This reply message may be federated back to the sending broker, which may think that a message is looping and, as a result, silently drop it. +If you wish to use the convenience of wildcard header mapping, you may need to filter out some headers in the downstream flow. +For example, to avoid copying the `x-received-from` header back to the reply you can use `` before sending the reply to the AMQP inbound gateway. +Alternatively, you can explicitly list those properties that you actually want mapped, instead of using wildcards. +For these reasons, for inbound messages, the mapper (by default) does not map any `x-*` headers. +It also does not map the `deliveryMode` to the `amqp_deliveryMode` header, to avoid propagation of that header from an inbound message to an outbound message. +Instead, this header is mapped to `amqp_receivedDeliveryMode`, which is not mapped on output. + +Starting with version 4.3, patterns in the header mappings can be negated by preceding the pattern with `!`. +Negated patterns get priority, so a list such as `STANDARD_REQUEST_HEADERS,thing1,ba*,!thing2,!thing3,qux,!thing1` does not map `thing1` (nor `thing2` nor `thing3`). +The standard headers plus `bad` and `qux` are mapped. +The negation technique can be useful for example to not map JSON type headers for incoming messages when a JSON deserialization logic is done in the receiver downstream different way. +For this purpose a `!json_*` pattern should be configured for header mapper of the inbound channel adapter/gateway. + +IMPORTANT: If you have a user-defined header that begins with `!` that you do wish to map, you need to escape it with `\`, as follows: `STANDARD_REQUEST_HEADERS,\!myBangHeader`. +The header named `!myBangHeader` is now mapped. + +NOTE: Starting with version 5.1, the `DefaultAmqpHeaderMapper` will fall back to mapping `MessageHeaders.ID` and `MessageHeaders.TIMESTAMP` to `MessageProperties.messageId` and `MessageProperties.timestamp` respectively, if the corresponding `amqp_messageId` or `amqp_timestamp` headers are not present on outbound messages. +Inbound properties will be mapped to the `amqp_*` headers as before. +It is useful to populate the `messageId` property when message consumers are using stateful retry. + +[[amqp-content-type]] +== The `contentType` Header + +Unlike other headers, the `AmqpHeaders.CONTENT_TYPE` is not prefixed with `amqp_`; this allows transparent passing of the contentType header across different technologies. +For example an inbound HTTP message sent to a RabbitMQ queue. + +The `contentType` header is mapped to Spring AMQP's `MessageProperties.contentType` property and that is subsequently mapped to RabbitMQ's `content_type` property. + +Prior to version 5.1, this header was also mapped as an entry in the `MessageProperties.headers` map; this was incorrect and, furthermore, the value could be wrong since the underlying Spring AMQP message converter might have changed the content type. +Such a change would be reflected in the first-class `content_type` property, but not in the RabbitMQ headers map. +Inbound mapping ignored the headers map value. +`contentType` is no longer mapped to an entry in the headers map. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/outbound-channel-adapter.adoc b/src/reference/antora/modules/ROOT/pages/amqp/outbound-channel-adapter.adoc new file mode 100644 index 0000000000..280c77ab85 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/outbound-channel-adapter.adoc @@ -0,0 +1,159 @@ +[[amqp-outbound-channel-adapter]] += Outbound Channel Adapter + +The following example shows the available properties for an AMQP outbound channel adapter: + +[tabs] +====== +Java DSL:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow amqpOutbound(AmqpTemplate amqpTemplate, + MessageChannel amqpOutboundChannel) { + return IntegrationFlow.from(amqpOutboundChannel) + .handle(Amqp.outboundAdapter(amqpTemplate) + .routingKey("queue1")) // default exchange - route to queue 'queue1' + .get(); +} +---- + +Java:: ++ +[source,java,role="secondary"] +---- +@Bean +@ServiceActivator(inputChannel = "amqpOutboundChannel") +public AmqpOutboundEndpoint amqpOutbound(AmqpTemplate amqpTemplate) { + AmqpOutboundEndpoint outbound = new AmqpOutboundEndpoint(amqpTemplate); + outbound.setRoutingKey("queue1"); // default exchange - route to queue 'queue1' + return outbound; +} + +@Bean +public MessageChannel amqpOutboundChannel() { + return new DirectChannel(); +} +---- + +XML:: ++ +[source,xml,role="secondary"] +---- + + channel="outboundChannel" <2> + amqp-template="myAmqpTemplate" <3> + exchange-name="" <4> + exchange-name-expression="" <5> + order="1" <6> + routing-key="" <7> + routing-key-expression="" <8> + default-delivery-mode"" <9> + confirm-correlation-expression="" <10> + confirm-ack-channel="" <11> + confirm-nack-channel="" <12> + confirm-timeout="" <13> + wait-for-confirm="" <14> + return-channel="" <15> + error-message-strategy="" <16> + header-mapper="" <17> + mapped-request-headers="" <18> + lazy-connect="true" <19> + multi-send="false"/> <20> +---- +====== + +<1> The unique ID for this adapter. +Optional. +<2> Message channel to which messages should be sent to have them converted and published to an AMQP exchange. +Required. +<3> Bean reference to the configured AMQP template. +Optional (defaults to `amqpTemplate`). +<4> The name of the AMQP exchange to which messages are sent. +If not provided, messages are sent to the default, no-name exchange. +Mutually exclusive with 'exchange-name-expression'. +Optional. +<5> A SpEL expression that is evaluated to determine the name of the AMQP exchange to which messages are sent, with the message as the root object. +If not provided, messages are sent to the default, no-name exchange. +Mutually exclusive with 'exchange-name'. +Optional. +<6> The order for this consumer when multiple consumers are registered, thereby enabling load-balancing and failover. +Optional (defaults to `Ordered.LOWEST_PRECEDENCE [=Integer.MAX_VALUE]`). +<7> The fixed routing-key to use when sending messages. +By default, this is an empty `String`. +Mutually exclusive with 'routing-key-expression'. +Optional. +<8> A SpEL expression that is evaluated to determine the routing key to use when sending messages, with the message as the root object (for example, 'payload.key'). +By default, this is an empty `String`. +Mutually exclusive with 'routing-key'. +Optional. +<9> The default delivery mode for messages: `PERSISTENT` or `NON_PERSISTENT`. +Overridden if the `header-mapper` sets the delivery mode. +If the Spring Integration message header `amqp_deliveryMode` is present, the `DefaultHeaderMapper` sets the value. +If this attribute is not supplied and the header mapper does not set it, the default depends on the underlying Spring AMQP `MessagePropertiesConverter` used by the `RabbitTemplate`. +If that is not customized at all, the default is `PERSISTENT`. +Optional. +<10> An expression that defines correlation data. +When provided, this configures the underlying AMQP template to receive publisher confirmations. +Requires a dedicated `RabbitTemplate` and a `CachingConnectionFactory` with the `publisherConfirms` property set to `true`. +When a publisher confirmation is received and correlation data is supplied, it is written to either the `confirm-ack-channel` or the `confirm-nack-channel`, depending on the confirmation type. +The payload of the confirmation is the correlation data, as defined by this expression. +The message has an 'amqp_publishConfirm' header set to `true` (`ack`) or `false` (`nack`). +Examples: `headers['myCorrelationData']` and `payload`. +Version 4.1 introduced the `amqp_publishConfirmNackCause` message header. +It contains the `cause` of a 'nack' for a publisher confirmation. +Starting with version 4.2, if the expression resolves to a `Message` instance (such as `#this`), the message emitted on the `ack`/`nack` channel is based on that message, with the additional header(s) added. +Previously, a new message was created with the correlation data as its payload, regardless of type. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<11> The channel to which positive (`ack`) publisher confirms are sent. +The payload is the correlation data defined by the `confirm-correlation-expression`. +If the expression is `#root` or `#this`, the message is built from the original message, with the `amqp_publishConfirm` header set to `true`. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<12> The channel to which negative (`nack`) publisher confirmations are sent. +The payload is the correlation data defined by the `confirm-correlation-expression` (if there is no `ErrorMessageStrategy` configured). +If the expression is `#root` or `#this`, the message is built from the original message, with the `amqp_publishConfirm` header set to `false`. +When there is an `ErrorMessageStrategy`, the message is an `ErrorMessage` with a `NackedAmqpMessageException` payload. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<13> When set, the adapter will synthesize a negative acknowledgment (nack) if a publisher confirm is not received within this time in milliseconds. +Pending confirms are checked every 50% of this value, so the actual time a nack is sent will be between 1x and 1.5x this value. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Default none (nacks will not be generated). +<14> When set to true, the calling thread will block, waiting for a publisher confirmation. +This requires a `RabbitTemplate` configured for confirms as well as a `confirm-correlation-expression`. +The thread will block for up to `confirm-timeout` (or 5 seconds by default). +If a timeout occurs, a `MessageTimeoutException` will be thrown. +If returns are enabled and a message is returned, or any other exception occurs while awaiting the confirmation, a `MessageHandlingException` will be thrown, with an appropriate message. +<15> The channel to which returned messages are sent. +When provided, the underlying AMQP template is configured to return undeliverable messages to the adapter. +When there is no `ErrorMessageStrategy` configured, the message is constructed from the data received from AMQP, with the following additional headers: `amqp_returnReplyCode`, `amqp_returnReplyText`, `amqp_returnExchange`, `amqp_returnRoutingKey`. +When there is an `ErrorMessageStrategy`, the message is an `ErrorMessage` with a `ReturnedAmqpMessageException` payload. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<16> A reference to an `ErrorMessageStrategy` implementation used to build `ErrorMessage` instances when sending returned or negatively acknowledged messages. +<17> A reference to an `AmqpHeaderMapper` to use when sending AMQP Messages. +By default, only standard AMQP properties (such as `contentType`) are copied to the Spring Integration `MessageHeaders`. +Any user-defined headers is not copied to the message by the default`DefaultAmqpHeaderMapper`. +Not allowed if 'request-header-names' is provided. +Optional. +<18> Comma-separated list of names of AMQP Headers to be mapped from the `MessageHeaders` to the AMQP Message. +Not allowed if the 'header-mapper' reference is provided. +The values in this list can also be simple patterns to be matched against the header names (e.g. `"\*"` or `"thing1*, thing2"` or `"*thing1"`). +<19> When set to `false`, the endpoint attempts to connect to the broker during application context initialization. +This allows "`fail fast`" detection of bad configuration but also causes initialization to fail if the broker is down. +When `true` (the default), the connection is established (if it does not already exist because some other component established it) when the first message is sent. +<20> When set to `true`, payloads of type `Iterable>` will be sent as discrete messages on the same channel within the scope of a single `RabbitTemplate` invocation. +Requires a `RabbitTemplate`. +When `wait-for-confirms` is true, `RabbitTemplate.waitForConfirmsOrDie()` is invoked after the messages have been sent. +With a transactional template, the sends will be performed in either a new transaction or one that has already been started (if present). + +[IMPORTANT] +.return-channel +===== +Using a `return-channel` requires a `RabbitTemplate` with the `mandatory` property set to `true` and a `CachingConnectionFactory` with the `publisherReturns` property set to `true`. +When using multiple outbound endpoints with returns, a separate `RabbitTemplate` is needed for each endpoint. +===== + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/outbound-endpoints.adoc b/src/reference/antora/modules/ROOT/pages/amqp/outbound-endpoints.adoc new file mode 100644 index 0000000000..99b6f4a609 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/outbound-endpoints.adoc @@ -0,0 +1,10 @@ +[[amqp-outbound-endpoints]] += Outbound Endpoints +:page-section-summary-toc: 1 + +The following outbound endpoints have many similar configuration options. +Starting with version 5.2, the `confirm-timeout` has been added. +Normally, when publisher confirms are enabled, the broker will quickly return an ack (or nack) which will be sent to the appropriate channel. +If a channel is closed before the confirm is received, the Spring AMQP framework will synthesize a nack. +"Missing" acks should never occur but, if you set this property, the endpoint will periodically check for them and synthesize a nack if the time elapses without a confirm being received. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/outbound-gateway.adoc b/src/reference/antora/modules/ROOT/pages/amqp/outbound-gateway.adoc new file mode 100644 index 0000000000..fffc61764d --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/outbound-gateway.adoc @@ -0,0 +1,168 @@ +[[amqp-outbound-gateway]] += Outbound Gateway + +The following listing shows the possible properties for an AMQP Outbound Gateway: + +[tabs] +====== +Java DSL:: ++ +[source,java,role="primary"] +---- +@Bean +public IntegrationFlow amqpOutbound(AmqpTemplate amqpTemplate) { + return f -> f.handle(Amqp.outboundGateway(amqpTemplate) + .routingKey("foo")) // default exchange - route to queue 'foo' + .get(); +} + +@MessagingGateway(defaultRequestChannel = "amqpOutbound.input") +public interface MyGateway { + + String sendToRabbit(String data); + +} +---- + +Java:: ++ +[source,java,role="secondary"] +---- +@Bean +@ServiceActivator(inputChannel = "amqpOutboundChannel") +public AmqpOutboundEndpoint amqpOutbound(AmqpTemplate amqpTemplate) { + AmqpOutboundEndpoint outbound = new AmqpOutboundEndpoint(amqpTemplate); + outbound.setExpectReply(true); + outbound.setRoutingKey("foo"); // default exchange - route to queue 'foo' + return outbound; +} + +@Bean +public MessageChannel amqpOutboundChannel() { + return new DirectChannel(); +} + +@MessagingGateway(defaultRequestChannel = "amqpOutboundChannel") +public interface MyGateway { + + String sendToRabbit(String data); + +} +---- + +XML:: ++ +[source,xml,role="secondary"] +---- + + request-channel="myRequestChannel" <2> + amqp-template="" <3> + exchange-name="" <4> + exchange-name-expression="" <5> + order="1" <6> + reply-channel="" <7> + reply-timeout="" <8> + requires-reply="" <9> + routing-key="" <10> + routing-key-expression="" <11> + default-delivery-mode"" <12> + confirm-correlation-expression="" <13> + confirm-ack-channel="" <14> + confirm-nack-channel="" <15> + confirm-timeout="" <16> + return-channel="" <17> + error-message-strategy="" <18> + lazy-connect="true" /> <19> + +---- +====== + +<1> The unique ID for this adapter. +Optional. +<2> Message channel to which messages are sent to have them converted and published to an AMQP exchange. +Required. +<3> Bean reference to the configured AMQP template. +Optional (defaults to `amqpTemplate`). +<4> The name of the AMQP exchange to which messages should be sent. +If not provided, messages are sent to the default, no-name cxchange. +Mutually exclusive with 'exchange-name-expression'. +Optional. +<5> A SpEL expression that is evaluated to determine the name of the AMQP exchange to which messages should be sent, with the message as the root object. +If not provided, messages are sent to the default, no-name exchange. +Mutually exclusive with 'exchange-name'. +Optional. +<6> The order for this consumer when multiple consumers are registered, thereby enabling load-balancing and failover. +Optional (defaults to `Ordered.LOWEST_PRECEDENCE [=Integer.MAX_VALUE]`). +<7> Message channel to which replies should be sent after being received from an AMQP queue and converted. +Optional. +<8> The time the gateway waits when sending the reply message to the `reply-channel`. +This only applies if the `reply-channel` can block -- such as a `QueueChannel` with a capacity limit that is currently full. +Defaults to infinity. +<9> When `true`, the gateway throws an exception if no reply message is received within the `AmqpTemplate`'s `replyTimeout` property. +Defaults to `true`. +<10> The `routing-key` to use when sending messages. +By default, this is an empty `String`. +Mutually exclusive with 'routing-key-expression'. +Optional. +<11> A SpEL expression that is evaluated to determine the `routing-key` to use when sending messages, with the message as the root object (for example, 'payload.key'). +By default, this is an empty `String`. +Mutually exclusive with 'routing-key'. +Optional. +<12> The default delivery mode for messages: `PERSISTENT` or `NON_PERSISTENT`. +Overridden if the `header-mapper` sets the delivery mode. +If the Spring Integration message header `amqp_deliveryMode` is present, the `DefaultHeaderMapper` sets the value. +If this attribute is not supplied and the header mapper does not set it, the default depends on the underlying Spring AMQP `MessagePropertiesConverter` used by the `RabbitTemplate`. +If that is not customized at all, the default is `PERSISTENT`. +Optional. +<13> Since version 4.2. +An expression defining correlation data. +When provided, this configures the underlying AMQP template to receive publisher confirms. +Requires a dedicated `RabbitTemplate` and a `CachingConnectionFactory` with the `publisherConfirms` property set to `true`. +When a publisher confirm is received and correlation data is supplied, it is written to either the `confirm-ack-channel` or the `confirm-nack-channel`, depending on the confirmation type. +The payload of the confirm is the correlation data, as defined by this expression. +The message has a header 'amqp_publishConfirm' set to `true` (`ack`) or `false` (`nack`). +For `nack` confirmations, Spring Integration provides an additional header `amqp_publishConfirmNackCause`. +Examples: `headers['myCorrelationData']` and `payload`. +If the expression resolves to a `Message` instance (such as `#this`), the message +emitted on the `ack`/`nack` channel is based on that message, with the additional headers added. +Previously, a new message was created with the correlation data as its payload, regardless of type. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<14> The channel to which positive (`ack`) publisher confirmations are sent. +The payload is the correlation data defined by `confirm-correlation-expression`. +If the expression is `#root` or `#this`, the message is built from the original message, with the `amqp_publishConfirm` header set to `true`. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<15> The channel to which negative (`nack`) publisher confirmations are sent. +The payload is the correlation data defined by `confirm-correlation-expression` (if there is no `ErrorMessageStrategy` configured). +If the expression is `#root` or `#this`, the message is built from the original message, with the `amqp_publishConfirm` header set to `false`. +When there is an `ErrorMessageStrategy`, the message is an `ErrorMessage` with a `NackedAmqpMessageException` payload. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional (the default is `nullChannel`). +<16> When set, the gateway will synthesize a negative acknowledgment (nack) if a publisher confirm is not received within this time in milliseconds. +Pending confirms are checked every 50% of this value, so the actual time a nack is sent will be between 1x and 1.5x this value. +Default none (nacks will not be generated). +<17> The channel to which returned messages are sent. +When provided, the underlying AMQP template is configured to return undeliverable messages to the adapter. +When there is no `ErrorMessageStrategy` configured, the message is constructed from the data received from AMQP, with the following additional headers: `amqp_returnReplyCode`, `amqp_returnReplyText`, `amqp_returnExchange`, and `amqp_returnRoutingKey`. +When there is an `ErrorMessageStrategy`, the message is an `ErrorMessage` with a `ReturnedAmqpMessageException` payload. +Also see xref:amqp/alternative-confirms-returns.adoc[Alternative Mechanism for Publisher Confirms and Returns]. +Optional. +<18> A reference to an `ErrorMessageStrategy` implementation used to build `ErrorMessage` instances when sending returned or negatively acknowledged messages. +<19> When set to `false`, the endpoint attempts to connect to the broker during application context initialization. +This allows "`fail fast`" detection of bad configuration by logging an error message if the broker is down. +When `true` (the default), the connection is established (if it does not already exist because some other component established it) when the first message is sent. + +[IMPORTANT] +.return-channel +===== +Using a `return-channel` requires a `RabbitTemplate` with the `mandatory` property set to `true` and a `CachingConnectionFactory` with the `publisherReturns` property set to `true`. +When using multiple outbound endpoints with returns, a separate `RabbitTemplate` is needed for each endpoint. +===== + +IMPORTANT: The underlying `AmqpTemplate` has a default `replyTimeout` of five seconds. +If you require a longer timeout, you must configure it on the `template`. + +Note that the only difference between the outbound adapter and outbound gateway configuration is the setting of the +`expectReply` property. + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/polled-inbound-channel-adapter.adoc b/src/reference/antora/modules/ROOT/pages/amqp/polled-inbound-channel-adapter.adoc new file mode 100644 index 0000000000..710213c039 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/polled-inbound-channel-adapter.adoc @@ -0,0 +1,56 @@ +[[polled-inbound-channel-adapter]] += Polled Inbound Channel Adapter + +[[overview]] +== Overview + +Version 5.0.1 introduced a polled channel adapter, letting you fetch individual messages on demand -- for example, with a `MessageSourcePollingTemplate` or a poller. +See xref:polling-consumer.adoc#deferred-acks-message-source[Deferred Acknowledgment Pollable Message Source] for more information. + +It does not currently support XML configuration. + +The following example shows how to configure an `AmqpMessageSource`: + +[tabs] +====== +Java DSL:: ++ +[source, java, role="primary"] +---- +@Bean +public IntegrationFlow flow() { + return IntegrationFlow.from(Amqp.inboundPolledAdapter(connectionFactory(), DSL_QUEUE), + e -> e.poller(Pollers.fixedDelay(1_000)).autoStartup(false)) + .handle(p -> { + ... + }) + .get(); +} +---- + +Java:: ++ +[source, java, role="secondary"] +---- +@Bean +public AmqpMessageSource source(ConnectionFactory connectionFactory) { + return new AmqpMessageSource(connectionFactory, "someQueue"); +} +---- +====== + +See the https://docs.spring.io/spring-integration/api/org/springframework/integration/amqp/inbound/AmqpMessageSource.html[Javadoc] for configuration properties. + +[source, xml, role="secondary"] +.XML +---- +This adapter currently does not have XML configuration support. +---- + +[[amqp-polled-debatching]] +== Batched Messages + +See xref:amqp/inbound-channel-adapter.adoc#amqp-debatching[Batched Messages]. + +For the polled adapter, there is no listener container, batched messages are always debatched (if the `BatchingStrategy` supports doing so). + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/rmq-streams.adoc b/src/reference/antora/modules/ROOT/pages/amqp/rmq-streams.adoc new file mode 100644 index 0000000000..79fcb85e04 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/rmq-streams.adoc @@ -0,0 +1,40 @@ +[[rmq-streams]] += RabbitMQ Stream Queue Support + +Version 6.0 introduced support for RabbitMQ Stream Queues. + +The DSL factory class for these endpoints is `RabbitStream`. + +[[rmq-stream-inbound-channel-adapter]] +== RabbitMQ Stream Inbound Channel Adapter + +[source, java] +---- +@Bean +IntegrationFlow simpleStream(Environment env) { + return IntegrationFlow.from(RabbitStream.inboundAdapter(env).streamName("my.stream")) + // ... + .get(); +} + +@Bean +IntegrationFlow superStream(Environment env) { + return IntegrationFlow.from(RabbitStream.inboundAdapter(env).superStream("my.super.stream", "my.consumer")) + // ... + .get(); +} +---- + +[[rmq-stream-outbound-channel-adapter]] +== RabbitMQ Stream Outbound Channel Adapter + +[source, java] +---- +@Bean +IntegrationFlow outbound(Environment env) { + return f -> f + // ... + .handle(RabbitStream.outboundStreamAdapter(env, "my.stream")); + +} +---- diff --git a/src/reference/antora/modules/ROOT/pages/amqp/samples.adoc b/src/reference/antora/modules/ROOT/pages/amqp/samples.adoc new file mode 100644 index 0000000000..153c3b0dc8 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/samples.adoc @@ -0,0 +1,21 @@ +[[amqp-samples]] += AMQP Samples +:page-section-summary-toc: 1 + +To experiment with the AMQP adapters, check out the samples available in the Spring Integration samples git repository at https://github.com/spring-projects/spring-integration-samples[https://github.com/SpringSource/spring-integration-samples] + +Currently, one sample demonstrates the basic functionality of the Spring Integration AMQP adapter by using an outbound channel adapter and an inbound channel adapter. +As AMQP broker implementation in the sample uses https://www.rabbitmq.com/[RabbitMQ]. + +NOTE: In order to run the example, you need a running instance of RabbitMQ. +A local installation with just the basic defaults suffices. +For detailed RabbitMQ installation procedures, see https://www.rabbitmq.com/install.html[https://www.rabbitmq.com/install.html] + +Once the sample application is started, enter some text on the command prompt and a message containing that entered text is dispatched to the AMQP queue. +In return, that message is retrieved by Spring Integration and printed to the console. + +The following image illustrates the basic set of Spring Integration components used in this sample: + +.The Spring Integration graph of the AMQP sample +image::spring-integration-amqp-sample-graph.png[align="center"] + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/strict-ordering.adoc b/src/reference/antora/modules/ROOT/pages/amqp/strict-ordering.adoc new file mode 100644 index 0000000000..0fc1d3dc82 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/strict-ordering.adoc @@ -0,0 +1,59 @@ +[[amqp-strict-ordering]] += Strict Message Ordering + +This section describes message ordering for inbound and outbound messages. + +[[inbound]] +== Inbound + +If you require strict ordering of inbound messages, you must configure the inbound listener container's `prefetchCount` property to `1`. +This is because, if a message fails and is redelivered, it arrives after existing prefetched messages. +Since Spring AMQP version 2.0, the `prefetchCount` defaults to `250` for improved performance. +Strict ordering requirements come at the cost of decreased performance. + +[[outbound]] +== Outbound + +Consider the following integration flow: + +[source, java] +---- +@Bean +public IntegrationFlow flow(RabbitTemplate template) { + return IntegrationFlow.from(Gateway.class) + .splitWith(s -> s.delimiters(",")) + .transform(String::toUpperCase) + .handle(Amqp.outboundAdapter(template).routingKey("rk")) + .get(); +} +---- + +Suppose we send messages `A`, `B` and `C` to the gateway. +While it is likely that messages `A`, `B`, `C` are sent in order, there is no guarantee. +This is because the template "`borrows`" a channel from the cache for each send operation, and there is no guarantee that the same channel is used for each message. +One solution is to start a transaction before the splitter, but transactions are expensive in RabbitMQ and can reduce performance several hundred-fold. + +To solve this problem in a more efficient manner, starting with version 5.1, Spring Integration provides the `BoundRabbitChannelAdvice` which is a `HandleMessageAdvice`. +See xref:handler-advice/handle-message.adoc[Handling Message Advice]. +When applied before the splitter, it ensures that all downstream operations are performed on the same channel and, optionally, can wait until publisher confirmations for all sent messages are received (if the connection factory is configured for confirmations). +The following example shows how to use `BoundRabbitChannelAdvice`: + +[source, java] +---- +@Bean +public IntegrationFlow flow(RabbitTemplate template) { + return IntegrationFlow.from(Gateway.class) + .splitWith(s -> s.delimiters(",") + .advice(new BoundRabbitChannelAdvice(template, Duration.ofSeconds(10)))) + .transform(String::toUpperCase) + .handle(Amqp.outboundAdapter(template).routingKey("rk")) + .get(); +} +---- + +Notice that the same `RabbitTemplate` (which implements `RabbitOperations`) is used in the advice and the outbound adapter. +The advice runs the downstream flow within the template's `invoke` method so that all operations run on the same channel. +If the optional timeout is provided, when the flow completes, the advice calls the `waitForConfirmsOrDie` method, which throws an exception if the confirmations are not received within the specified time. + +IMPORTANT: There must be no thread hands-off in the downstream flow (`QueueChannel`, `ExecutorChannel`, and others). + diff --git a/src/reference/antora/modules/ROOT/pages/amqp/user-id.adoc b/src/reference/antora/modules/ROOT/pages/amqp/user-id.adoc new file mode 100644 index 0000000000..21d1371801 --- /dev/null +++ b/src/reference/antora/modules/ROOT/pages/amqp/user-id.adoc @@ -0,0 +1,14 @@ +[[amqp-user-id]] += Outbound User ID +:page-section-summary-toc: 1 + +Spring AMQP version 1.6 introduced a mechanism to allow the specification of a default user ID for outbound messages. +It has always been possible to set the `AmqpHeaders.USER_ID` header, which now takes precedence over the default. +This might be useful to message recipients. +For inbound messages, if the message publisher sets the property, it is made available in the `AmqpHeaders.RECEIVED_USER_ID` header. +Note that RabbitMQ https://www.rabbitmq.com/validated-user-id.html[validates that the user ID is the actual user ID for the connection or that the connection allows impersonation]. + +To configure a default user ID for outbound messages, configure it on a `RabbitTemplate` and configure the outbound adapter or gateway to use that template. +Similarly, to set the user ID property on replies, inject an appropriately configured template into the inbound gateway. +See the https://docs.spring.io/spring-amqp/reference/html/_reference.html#template-user-id[Spring AMQP documentation] for more information. + diff --git a/src/reference/asciidoc/barrier.adoc b/src/reference/antora/modules/ROOT/pages/barrier.adoc similarity index 98% rename from src/reference/asciidoc/barrier.adoc rename to src/reference/antora/modules/ROOT/pages/barrier.adoc index 7f7ad58085..95ce680881 100644 --- a/src/reference/asciidoc/barrier.adoc +++ b/src/reference/antora/modules/ROOT/pages/barrier.adoc @@ -1,5 +1,5 @@ [[barrier]] -=== Thread Barrier += Thread Barrier Sometimes, we need to suspend a message flow thread until some other asynchronous event occurs. For example, consider an HTTP request that publishes a message to RabbitMQ. @@ -35,9 +35,11 @@ An exception is thrown if a second thread arrives with the same correlation. The following example shows how to use a custom header for correlation: -==== +[tabs] +====== +Java:: ++ [source, java, role="primary"] -.Java ---- @ServiceActivator(inputChannel="in") @Bean @@ -54,8 +56,10 @@ public MessageHandler releaser(MessageTriggerAction barrier) { return barrier::trigger; } ---- + +XML:: ++ [source, xml, role="secondary"] -.XML ---- ---- -==== +====== Depending on which one has a message arrive first, either the thread sending a message to `in` or the thread sending a message to `release` waits for up to ten seconds until the other message arrives. When the message is released, the `out` channel is sent a message that combines the result of invoking the custom `MessageGroupProcessor` bean, named `myOutputProcessor`. diff --git a/src/reference/asciidoc/bridge.adoc b/src/reference/antora/modules/ROOT/pages/bridge.adoc similarity index 95% rename from src/reference/asciidoc/bridge.adoc rename to src/reference/antora/modules/ROOT/pages/bridge.adoc index d812aff435..481287448d 100644 --- a/src/reference/asciidoc/bridge.adoc +++ b/src/reference/antora/modules/ROOT/pages/bridge.adoc @@ -1,5 +1,5 @@ [[bridge]] -=== Messaging Bridge += Messaging Bridge A messaging bridge is a relatively trivial endpoint that connects two message channels or channel adapters. For example, you may want to connect a `PollableChannel` to a `SubscribableChannel` so that the subscribing endpoints do not have to worry about any polling configuration. @@ -15,34 +15,29 @@ In that case, the channels can be provided as the 'input-channel' and 'output-ch If data format translation is not required, the messaging bridge may indeed be sufficient. [[bridge-namespace]] -==== Configuring a Bridge with XML +== Configuring a Bridge with XML You can use the `` element is used to create a messaging bridge between two message channels or channel adapters. To do so, provide the `input-channel` and `output-channel` attributes, as the following example shows: -==== [source,xml] ---- ---- -==== As mentioned above, a common use case for the messaging bridge is to connect a `PollableChannel` to a `SubscribableChannel`. When performing this role, the messaging bridge may also serve as a throttler: -==== [source,xml] ---- ---- -==== You can use a similar mechanism to connecting channel adapters. The following example shows a simple "`echo`" between the `stdin` and `stdout` adapters from Spring Integration's `stream` namespace: -==== [source,xml] ---- @@ -51,7 +46,6 @@ The following example shows a simple "`echo`" between the `stdin` and `stdout` a ---- -==== Similar configurations work for other (potentially more useful) Channel Adapter bridges, such as file-to-JMS or mail-to-file. Upcoming chapters cover the various channel adapters. @@ -60,11 +54,10 @@ NOTE: If no 'output-channel' is defined on a bridge, the reply channel provided If neither an output nor a reply channel is available, an exception is thrown. [[bridge-annot]] -==== Configuring a Bridge with Java Configuration +== Configuring a Bridge with Java Configuration The following example shows how to configure a bridge in Java by using the `@BridgeFrom` annotation: -==== [source, java] ---- @Bean @@ -78,7 +71,6 @@ public SubscribableChannel direct() { return new DirectChannel(); } ---- -==== The following example shows how to configure a bridge in Java by using the `@BridgeTo` annotation: @@ -111,7 +103,7 @@ public BridgeHandler bridge() { ---- [[bridge-dsl]] -==== Configuring a Bridge with the Java DSL +== Configuring a Bridge with the Java DSL You can use the Java Domain Specific Language (DSL) to configure a bridge, as the following example shows: diff --git a/src/reference/asciidoc/camel.adoc b/src/reference/antora/modules/ROOT/pages/camel.adoc similarity index 98% rename from src/reference/asciidoc/camel.adoc rename to src/reference/antora/modules/ROOT/pages/camel.adoc index e32e0c55b3..d2dd2e2897 100644 --- a/src/reference/asciidoc/camel.adoc +++ b/src/reference/antora/modules/ROOT/pages/camel.adoc @@ -1,13 +1,15 @@ [[camel]] -== Apache Camel Support += Apache Camel Support Spring Integration provides an API and configuration to communicate with https://camel.apache.org[Apache Camel] endpoints declared in the same application context. You need to include this dependency into your project: -==== +[tabs] +====== +Maven:: ++ [source, xml, subs="normal", role="primary"] -.Maven ---- org.springframework.integration @@ -15,12 +17,14 @@ You need to include this dependency into your project: {project-version} ---- + +Gradle:: ++ [source, groovy, subs="normal", role="secondary"] -.Gradle ---- compile "org.springframework.integration:spring-integration-camel:{project-version}" ---- -==== +====== Spring Integration and Apache Camel implement Enterprise Integration Patterns and provide a convenient way to compose them, but the projects use a different approach for their API and abstractions implementation. Spring Integration fully relies on a dependency injection container from Spring Core. @@ -44,7 +48,7 @@ For a similar developer experience, Spring Integration now provides a channel ad There is no inbound channel adapter because subscribing to a `MessageChannel` for consuming Apache Camel messages is enough from the Spring Integration API and abstractions perspective. [[camel-channel-adapter]] -=== Outbound Channel Adapter for Apache Camel +== Outbound Channel Adapter for Apache Camel The `CamelMessageHandler` is an `AbstractReplyProducingMessageHandler` implementation and can work in both one-way (default) and request-reply modes. It uses an `org.apache.camel.ProducerTemplate` to send (or send and receive) into an `org.apache.camel.Endpoint`. @@ -62,7 +66,6 @@ The `exchangeProperties` can be customized via a SpEL expression, which must eva If a `ProducerTemplate` is not provided, it is created via a `CamelContext` bean resolved from the application context. -==== [source, java] ---- @Bean @@ -79,11 +82,9 @@ CamelMessageHandler camelService(ProducerTemplate producerTemplate) { return camelMessageHandler; } ---- -==== For Java DSL flow definitions this channel adapter can be configured with a few variants provided by the `Camel` factory: -==== [source, java] ---- @Bean @@ -98,4 +99,3 @@ private void camelRoute(RouteBuilder routeBuilder) { routeBuilder.from("direct:inbound").transform(routeBuilder.simple("${body.toUpperCase()}")); } ---- -==== diff --git a/src/reference/asciidoc/cassandra.adoc b/src/reference/antora/modules/ROOT/pages/cassandra.adoc similarity index 97% rename from src/reference/asciidoc/cassandra.adoc rename to src/reference/antora/modules/ROOT/pages/cassandra.adoc index f814daad45..5a2554b5db 100644 --- a/src/reference/asciidoc/cassandra.adoc +++ b/src/reference/antora/modules/ROOT/pages/cassandra.adoc @@ -1,14 +1,16 @@ [[cassandra]] -== Apache Cassandra Support += Apache Cassandra Support Spring Integration provides channel adapters (starting with version 6.0) for performing database operations against an Apache Cassandra cluster. It is fully based on the https://spring.io/projects/spring-data-cassandra[Spring Data for Apache Cassandra] project. You need to include this dependency into your project: -==== +[tabs] +====== +Maven:: ++ [source, xml, subs="normal", role="primary"] -.Maven ---- org.springframework.integration @@ -16,15 +18,17 @@ You need to include this dependency into your project: {project-version} ---- + +Gradle:: ++ [source, groovy, subs="normal", role="secondary"] -.Gradle ---- compile "org.springframework.integration:spring-integration-cassandra:{project-version}" ---- -==== +====== [[cassandra-outbound]] -=== Cassandra Outbound Components +== Cassandra Outbound Components The `CassandraMessageHandler` is an `AbstractReplyProducingMessageHandler` implementation and can work in both one-way (default) and request-reply modes (a `producesReply` option). It is asynchronous by default (`setAsync(false)` to reset) and performs reactive `INSERT`, `UPDATE`, `DELETE` or `STATEMENT` operations against the provided `ReactiveCassandraOperations`. @@ -33,9 +37,11 @@ The `ingestQuery` sets the mode into an `INSERT`; the `query` or `statementExpre The following code snippets demonstrate various configuration for this channel adapter or gateway: -==== +[tabs] +====== +Java DSL:: ++ [source, java, role="primary"] -.Java DSL ---- @Bean IntegrationFlow cassandraSelectFlow(ReactiveCassandraOperations cassandraOperations) { @@ -47,8 +53,10 @@ IntegrationFlow cassandraSelectFlow(ReactiveCassandraOperations cassandraOperati .channel(c -> c.flux("resultChannel")); } ---- + +Kotlin DSL:: ++ [source, kotlin, role="secondary"] -.Kotlin DSL ---- @Bean fun outboundReactive(cassandraOperations: ReactiveCassandraOperations) = @@ -59,8 +67,10 @@ fun outboundReactive(cassandraOperations: ReactiveCassandraOperations) = ) { async(false) } } ---- + +Java:: ++ [source, java, role="secondary"] -.Java ---- @ServiceActivator(inputChannel = "cassandraSelectChannel") @Bean @@ -79,8 +89,10 @@ public MessageHandler cassandraMessageHandler() { return cassandraMessageHandler; } ---- + +XML:: ++ [source, xml, role="secondary"] -.XML ---- ---- -==== +====== If a `CassandraMessageHandler` is used as a gateway in the default async mode, a `Mono` is produced, which is handled according to the provided `MessageChannel` implementation. For true reactive processing a `FluxMessageChannel` is recommended for the output channel configuration. @@ -112,7 +124,6 @@ If the payload is a list of entities, then the respective batch operation is per The `ingestQuery` mode expects the payload to be present as a matrix of values to insert - `List>`. For example, if the entity is like this: -==== [source,java] ---- @Table("book") @@ -125,11 +136,9 @@ public record Book(@PrimaryKey String isbn, } ---- -==== And channel adapter has this configuration: -==== [source,java] ---- @Bean @@ -141,11 +150,9 @@ public MessageHandler cassandraMessageHandler3() { return cassandraMessageHandler; } ---- -==== The request message payload must be converted like this: -==== [source,java] ---- List> ingestBooks = @@ -160,7 +167,6 @@ List> ingestBooks = book.isInStock())) .toList(); ---- -==== For more sophisticated use-cases, the payload can be as an instance of `com.datastax.oss.driver.api.core.cql.Statement`. The `com.datastax.oss.driver.api.querybuilder.QueryBuilder` API is recommended to build various statements to execute against Apache Cassandra. @@ -168,12 +174,10 @@ For example, to remove all the data from the `Book` table, a message with a payl Alternatively, for logic based on a request message, a `statementExpression` or `statementProcessor` can be provided for the `CassandraMessageHandler` to build a `Statement` based on that message. For convenience, a `com.datastax.oss.driver.api.querybuilder` is registered as an `import` into a SpEL evaluation context, so a target expression can be as simple as this: -==== [source,xml] ---- statement-expression="T(QueryBuilder).selectFrom("book").all()" ---- -==== The `setParameterExpressions(Map parameterExpressions)` represents bindable named query parameters and is used only with a `setQuery(String query)` option. See Java and XML samples mentioned above. diff --git a/src/reference/asciidoc/chain.adoc b/src/reference/antora/modules/ROOT/pages/chain.adoc similarity index 96% rename from src/reference/asciidoc/chain.adoc rename to src/reference/antora/modules/ROOT/pages/chain.adoc index 7382ad841b..a248556d89 100644 --- a/src/reference/asciidoc/chain.adoc +++ b/src/reference/antora/modules/ROOT/pages/chain.adoc @@ -1,5 +1,5 @@ [[chain]] -=== Message Handler Chain += Message Handler Chain The `MessageHandlerChain` is an implementation of `MessageHandler` that can be configured as a single message endpoint while actually delegating to a chain of other handlers, such as filters, transformers, splitters, and so on. When several handlers need to be connected in a fixed, linear progression, this can lead to a much simpler configuration. @@ -9,7 +9,7 @@ In either case, the chain requires only a single `input-channel` and a single `o NOTE: The `MessageHandlerChain` is mostly designed for an XML configuration. For Java DSL, an `IntegrationFlow` definition can be treated as a chain component, but it has nothing to do with concepts and principles described in this chapter below. -See <<./dsl.adoc#java-dsl,Java DSL>> for more information. +See xref:dsl.adoc#java-dsl[Java DSL] for more information. TIP: Spring Integration's `Filter` provides a boolean property: `throwExceptionOnRejection`. When you provide multiple selective consumers on the same point-to-point channel with different acceptance criteria, you should set this value 'true' (the default is `false`) so that the dispatcher knows that the message was rejected and, as a result, tries to pass the message on to other subscribers. @@ -33,7 +33,7 @@ The next section focuses on namespace support for the chain element. Most Spring Integration endpoints, such as service activators and transformers, are suitable for use within a `MessageHandlerChain`. [[chain-namespace]] -==== Configuring a Chain +== Configuring a Chain The `` element provides an `input-channel` attribute. If the last element in the chain is capable of producing reply messages (optional), it also supports an `output-channel` attribute. @@ -41,7 +41,6 @@ The sub-elements are then filters, transformers, splitters, and service-activato The last element may also be a router or an outbound channel adapter. The following example shows a chain definition: -==== [source,xml] ---- @@ -52,7 +51,6 @@ The following example shows a chain definition: ---- -==== The `` element used in the preceding example sets a message header named `thing1` with a value of `thing2` on the message. A header enricher is a specialization of `Transformer` that touches only header values. @@ -61,7 +59,6 @@ You could obtain the same result by implementing a `MessageHandler` that did the The `` can be configured as the last "`closed-box`" consumer of the message flow. For this solution, you can to put it at the end of the some , as the following example shows: -==== [source,xml] ---- @@ -73,7 +70,6 @@ For this solution, you can to put it at the end of the some ---- -==== .Disallowed Attributes and Elements [IMPORTANT] @@ -88,13 +84,13 @@ These XML namespace parser constraints were added with Spring Integration 2.2. If you try to use disallowed attributes and elements, the XML namespace parser throws a `BeanDefinitionParsingException`. ===== -==== Using the 'id' Attribute +[[using-the-id-attribute]] +== Using the 'id' Attribute Beginning with Spring Integration 3.0, if a chain element is given an `id` attribute, the bean name for the element is a combination of the chain's `id` and the `id` of the element itself. Elements without `id` attributes are not registered as beans, but each one is given a `componentName` that includes the chain `id`. Consider the following example: -==== [source,xml] ---- @@ -102,7 +98,6 @@ Consider the following example: ---- -==== In the preceding example: @@ -125,18 +120,17 @@ In this case, it is 'somethingChain$child#1'. Note, this transformer is not registered as a bean within the application context, so it does not get a `beanName`. However, its `componentName` has a value that is useful for logging and other purposes. -The `id` attribute for `` elements lets them be eligible for <<./jmx.adoc#jmx-mbean-exporter,JMX export>>, and they are trackable in the <<./message-history.adoc#message-history,message history>>. +The `id` attribute for `` elements lets them be eligible for xref:jmx.adoc#jmx-mbean-exporter[JMX export], and they are trackable in the xref:message-history.adoc[message history]. You can access them from the `BeanFactory` by using the appropriate bean name, as discussed earlier. TIP: It is useful to provide an explicit `id` attribute on `` elements to simplify the identification of sub-components in logs and to provide access to them from the `BeanFactory` etc. [[chain-gateway]] -==== Calling a Chain from within a Chain +== Calling a Chain from within a Chain Sometimes, you need to make a nested call to another chain from within a chain and then come back and continue execution within the original chain. To accomplish this, you can use a messaging gateway by including a element, as the following example shows: -==== [source,xml] ---- @@ -168,7 +162,6 @@ To accomplish this, you can use a messaging gateway by including a ele ---- -==== In the preceding example, `nested-chain-a` is called at the end of `main-chain` processing by the 'gateway' element configured there. While in `nested-chain-a`, a call to a `nested-chain-b` is made after header enrichment. diff --git a/src/reference/asciidoc/changes-1.0-2.0.adoc b/src/reference/antora/modules/ROOT/pages/changes-1.0-2.0.adoc similarity index 72% rename from src/reference/asciidoc/changes-1.0-2.0.adoc rename to src/reference/antora/modules/ROOT/pages/changes-1.0-2.0.adoc index 9e0081cb67..fcf2f9378d 100644 --- a/src/reference/asciidoc/changes-1.0-2.0.adoc +++ b/src/reference/antora/modules/ROOT/pages/changes-1.0-2.0.adoc @@ -1,176 +1,176 @@ [[migration-1.0-2.0]] -=== Changes between Versions 1.0 and 2.0 += Changes between Versions 1.0 and 2.0 See the https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-1.0-to-2.0-Migration-Guide[Migration Guide] for important changes that might affect your applications. [[migration-spring-30-support]] -==== Spring 3 support +== Spring 3 support Spring Integration 2.0 is built on top of Spring 3.0.5 and makes many of its features available to our users. [[2.0-spel-support]] -===== Support for the Spring Expression Language (SpEL) +=== Support for the Spring Expression Language (SpEL) You can now use SpEL expressions within the transformer, router, filter, splitter, aggregator, service-activator, header-enricher, and many more elements of the Spring Integration core namespace as well as within various adapters. This guide includes many samples. [[conversion-support]] -===== Conversion Service and Converter +=== Conversion Service and Converter You can now benefit from the conversion service support provided with Spring while configuring many Spring Integration components, such as a https://www.enterpriseintegrationpatterns.com/DatatypeChannel.html[Datatype channel]. -See <<./channel.adoc#channel-implementations,Message Channel Implementations>> and <<./service-activator.adoc#service-activator,Service Activator>>. +See xref:channel/implementations.adoc[Message Channel Implementations] and xref:service-activator.adoc[Service Activator]. Also, the SpEL support mentioned in the previous point also relies upon the conversion service. Therefore, you can register converters once and take advantage of them anywhere you use SpEL expressions. [[task-scheduler-poller-support]] -===== `TaskScheduler` and `Trigger` +=== `TaskScheduler` and `Trigger` Spring 3.0 defines two new strategies related to scheduling: `TaskScheduler` and `Trigger`. Spring Integration (which uses a lot of scheduling) now builds upon these. In fact, Spring Integration 1.0 had originally defined some of the components (such as `CronTrigger`) that have now been migrated into Spring 3.0's core API. Now you can benefit from reusing the same components within the entire application context (not just Spring Integration configuration). We also greatly simplified configuration of Spring Integration pollers by providing attributes for directly configuring rates, delays, cron expressions, and trigger references. -See <<./channel-adapter.adoc#channel-adapter,Channel Adapter>> for sample configurations. +See xref:overview.adoc#overview-endpoints-channeladapter[Channel Adapter] for sample configurations. [[rest-support]] -===== `RestTemplate` and `HttpMessageConverter` +=== `RestTemplate` and `HttpMessageConverter` Our outbound HTTP adapters now delegate to Spring's `RestTemplate` for executing the HTTP request and handling its response. This also means that you can reuse any custom `HttpMessageConverter` implementations. -See <<./http.adoc#http-outbound,HTTP Outbound Components>> for more details. +See xref:http/outbound.adoc[HTTP Outbound Components] for more details. [[new-eip]] -==== Enterprise Integration Pattern Additions +== Enterprise Integration Pattern Additions Also in 2.0, we have added support for even more of the patterns described in Hohpe and Woolf's https://www.enterpriseintegrationpatterns.com/[Enterprise Integration Patterns] book. [[new-message-history]] -===== Message History +=== Message History We now provide support for the https://www.enterpriseintegrationpatterns.com/MessageHistory.html[message history] pattern, letting you keep track of all traversed components, including the name of each channel and endpoint as well as the timestamp of that traversal. -See <<./message-history.adoc#message-history,Message History>> for more details. +See xref:message-history.adoc[Message History] for more details. [[new-message-store]] -===== Message Store +=== Message Store We now provide support for the https://www.enterpriseintegrationpatterns.com/MessageStore.html[message store] pattern. The message store provides a strategy for persisting messages on behalf of any process whose scope extends beyond a single transaction, such as the aggregator and the resequencer. Many sections of this guide include samples of how to use a message store, as it affects several areas of Spring Integration. -See <<./message-store.adoc#message-store,Message Store>>, <<./claim-check.adoc#claim-check,Claim Check>>, <<./channel.adoc#channel,Message Channels>>, <<./aggregator.adoc#aggregator,Aggregator>>, <<./jdbc.adoc#jdbc,JDBC Support>>`", and <<./resequencer.adoc#resequencer,Resequencer>> for more details. +See xref:message-store.adoc[Message Store], xref:claim-check.adoc[Claim Check], xref:channel.adoc[Message Channels], xref:overview.adoc#overview-endpoints-aggregator[Aggregator], xref:jdbc.adoc[JDBC Support]`", and xref:resequencer.adoc[Resequencer] for more details. [[new-claim-check]] -===== Claim Check +=== Claim Check We have added an implementation of the https://www.enterpriseintegrationpatterns.com/StoreInLibrary.html[claim check] pattern. The idea behind the claim check pattern is that you can exchange a message payload for a "`claim ticket`". This lets you reduce bandwidth and avoid potential security issues when sending messages across channels. -See <<./claim-check.adoc#claim-check,Claim Check>> for more details. +See xref:claim-check.adoc[Claim Check] for more details. [[new-control-bus]] -===== Control Bus +=== Control Bus We have provided implementations of the https://www.enterpriseintegrationpatterns.com/ControlBus.html[control bus] pattern, which lets you use messaging to manage and monitor endpoints and channels. The implementations include both a SpEL-based approach and one that runs Groovy scripts. -See <<./control-bus.adoc#control-bus,Control Bus>> and <<./groovy.adoc#groovy-control-bus,Control Bus>> for more details. +See xref:groovy.adoc#groovy-control-bus[Control Bus] and xref:groovy.adoc#groovy-control-bus[Control Bus] for more details. [[new-adapters]] -==== New Channel Adapters and Gateways +== New Channel Adapters and Gateways We have added several new channel adapters and messaging gateways in Spring Integration 2.0. [[new-ip]] -===== TCP and UDP Adapters +=== TCP and UDP Adapters We have added channel adapters for receiving and sending messages over the TCP and UDP internet protocols. -See <<./ip.adoc#ip,TCP and UDP Support>> for more details. +See xref:ip.adoc[TCP and UDP Support] for more details. See also the following blog: https://spring.io/blog/2010/03/29/using-udp-and-tcp-adapters-in-spring-integration-2-0-m3/["`Using UDP and TCP Adapters in Spring Integration 2.0 M3`"]. [[new-twitter]] -===== Twitter Adapters +=== Twitter Adapters Twitter adapters provides support for sending and receiving Twitter status updates as well as direct messages. You can also perform Twitter Searches with an inbound channel adapter. See https://github.com/spring-projects/spring-integration-extensions/tree/main/spring-integration-social-twitter[Spring Integration Social Twitter] for more details. [[new-xmpp]] -===== XMPP Adapters +=== XMPP Adapters The new XMPP adapters support both chat messages and presence events. -See <<./xmpp.adoc#xmpp,XMPP Support>> for more details. +See xref:xmpp.adoc[XMPP Support] for more details. [[new-ftp]] -===== FTP and FTPS Adapters +=== FTP and FTPS Adapters Inbound and outbound file transfer support over FTP and FTPS is now available. -See <<./ftp.adoc#ftp,FTP/FTPS Adapters>> for more details. +See xref:ftp.adoc[FTP/FTPS Adapters] for more details. [[new-sftp]] -===== SFTP Adapters +=== SFTP Adapters Inbound and outbound file transfer support over SFTP is now available. -See <<./sftp.adoc#sftp,SFTP Adapters>> for more details. +See xref:sftp.adoc[SFTP Adapters] for more details. [[new-feed]] -===== Feed Adapters +=== Feed Adapters We have also added channel adapters for receiving news feeds (ATOM and RSS). -See <<./feed.adoc#feed,Feed Adapter>> for more details. +See xref:feed.adoc[Feed Adapter] for more details. [[new-other]] -==== Other Additions +== Other Additions Spring Integration adds a number of other features. This section describes them. [[new-groovy]] -===== Groovy Support +=== Groovy Support Spring Integration 2.0 added Groovy support, letting you use the Groovy scripting language to provide integration and business logic. -See <<./groovy.adoc#groovy,Groovy support>> for more details. +See xref:groovy.adoc[Groovy support] for more details. [[new-map-transformer]] -===== Map Transformers +=== Map Transformers These symmetrical transformers convert payload objects to and from `Map` objects. -See <<./transformer.adoc#transformer,Transformer>> for more details. +See xref:transformer.adoc[Transformer] for more details. [[new-json-transformer]] -===== JSON Transformers +=== JSON Transformers These symmetrical transformers convert payload objects to and from JSON. -See <<./transformer.adoc#transformer,Transformer>> for more details. +See xref:transformer.adoc[Transformer] for more details. [[new-serialize-transformer]] -===== Serialization Transformers +=== Serialization Transformers These symmetrical transformers convert payload objects to and from byte arrays. They also support the serializer and deserializer strategy interfaces that Spring 3.0.5 added. -See <<./transformer.adoc#transformer,Transformer>> for more details. +See xref:transformer.adoc[Transformer] for more details. [[new-refactoring]] -==== Framework Refactoring +== Framework Refactoring The core API went through some significant refactoring to make it simpler and more usable. Although we anticipate that the impact to developers should be minimal, you should read through this document to find what was changed. -Specifically, you should read <<./router.adoc#dynamic-routers,Dynamic Routers>>, <<./gateway.adoc#gateway,Messaging Gateways>>, <<./http.adoc#http-outbound,HTTP Outbound Components>>, <<./message.adoc#message,Message>>, and <<./aggregator.adoc#aggregator,Aggregator>>. +Specifically, you should read xref:router/dynamic-routers.adoc[Dynamic Routers], xref:gateway.adoc[Messaging Gateways], xref:http/outbound.adoc[HTTP Outbound Components], xref:overview.adoc#overview-components-message[Message], and xref:overview.adoc#overview-endpoints-aggregator[Aggregator]. If you directly depend on some of the core components (`Message`, `MessageHeaders`, `MessageChannel`, `MessageBuilder`, and others), you need to update any import statements. We restructured some packaging to provide the flexibility we needed for extending the domain model while avoiding any cyclical dependencies (it is a policy of the framework to avoid such "`tangles`"). [[new-infrastructure]] -==== New Source Control Management and Build Infrastructure +== New Source Control Management and Build Infrastructure With Spring Integration 2.0, we switched our build environment to use Git for source control. To access our repository, visit https://git.springsource.org/spring-integration. We have also switched our build system to https://gradle.org/[Gradle]. [[new-samples]] -==== New Spring Integration Samples +== New Spring Integration Samples With Spring Integration 2.0, we have decoupled the samples from our main release distribution. Please read the following blog to get more information: https://spring.io/blog/2010/09/29/new-spring-integration-samples/[New Spring Integration Samples]. We have also created many new samples, including samples for every new adapter. [[new-sts]] -==== Spring Tool Suite Visual Editor for Spring Integration +== Spring Tool Suite Visual Editor for Spring Integration There is an amazing new visual editor for Spring Integration included within the latest version of SpringSource Tool Suite. If you are not already using STS, you can download it at https://spring.io/tools/sts[Spring Tool Suite]. diff --git a/src/reference/asciidoc/changes-2.0-2.1.adoc b/src/reference/antora/modules/ROOT/pages/changes-2.0-2.1.adoc similarity index 83% rename from src/reference/asciidoc/changes-2.0-2.1.adoc rename to src/reference/antora/modules/ROOT/pages/changes-2.0-2.1.adoc index 309e657370..d48bd50862 100644 --- a/src/reference/asciidoc/changes-2.0-2.1.adoc +++ b/src/reference/antora/modules/ROOT/pages/changes-2.0-2.1.adoc @@ -1,15 +1,15 @@ [[migration-2.0-2.1]] -=== Changes between 2.0 and 2.1 += Changes between 2.0 and 2.1 See the https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-2.0-to-2.1-Migration-Guide[Migration Guide] for important changes that might affect your applications. [[x2.1-new-components]] -==== New Components +== New Components Version 2.1 added a number of new components. [[x2.1-new-scripting-support]] -===== JSR-223 Scripting Support +=== JSR-223 Scripting Support In Spring Integration 2.0, we added support for https://groovy.codehaus.org/[Groovy]. With Spring Integration 2.1, we expanded support for additional languages substantially by implementing support for https://www.jcp.org/en/jsr/detail?id=223[JSR-223] ("`Scripting for the Javaâ„¢ Platform`"). @@ -20,40 +20,40 @@ Now you have the ability to use any scripting language that supports JSR-223 inc * Python and Jython * Groovy -For further details, see <<./scripting.adoc#scripting,Scripting Support>>. +For further details, see xref:scripting.adoc[Scripting Support]. [[x2.1-new-amqp-support]] -===== AMQP Support +=== AMQP Support Spring Integration 2.1 added several channel adapters for receiving and sending messages by using the https://www.amqp.org/[Advanced Message Queuing Protocol] (AMQP). Furthermore, Spring Integration also provides a point-to-point message channel and a publish-subscribe message channel, both of which are backed by AMQP Exchanges and Queues. -For further details, see <<./amqp.adoc#amqp,AMQP Support>>. +For further details, see xref:amqp.adoc[AMQP Support]. [[x2.1-new-mongodb-support]] -===== MongoDB Support +=== MongoDB Support As of version 2.1, Spring Integration provides support for https://www.mongodb.org/[MongoDB] by providing a MongoDB-based `MessageStore`. -For further details, see <<./mongodb.adoc#mongodb,MongoDb Support>>. +For further details, see xref:mongodb.adoc[MongoDb Support]. [[x2.1-new-redis-support]] -===== Redis Support +=== Redis Support As of version 2.1, Spring Integration supports https://redis.io/[Redis], an advanced key-value store, by providing a Redis-based `MessageStore` as well as publish-subscribe messaging adapters. -For further details, see <<./redis.adoc#redis,Redis Support>>. +For further details, see xref:redis.adoc[Redis Support]. [[x2.1-new-resource-support]] -===== Support for Spring's Resource abstraction +=== Support for Spring's Resource abstraction In version 2.1, we introduced a new resource inbound channel adapter that builds upon Spring's resource abstraction to support greater flexibility across a variety of actual types of underlying resources, such as a file, a URL, or a classpath resource. Therefore, it is similar to but more generic than the file inbound channel adapter. -For further details, see <<./resource.adoc#resource-inbound-channel-adapter,Resource Inbound Channel Adapter>>. +For further details, see xref:resource.adoc#resource-inbound-channel-adapter[Resource Inbound Channel Adapter]. [[x2.1-new-stored-proc-support]] -===== Stored Procedure Components +=== Stored Procedure Components With Spring Integration 2.1, the `JDBC` Module also provides stored procedure support by adding several new components, including inbound and outbound channel adapters and an outbound gateway. The stored procedure support leverages Spring's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/simple/SimpleJdbcCall.html[`SimpleJdbcCall`] class and consequently supports stored procedures for: @@ -73,54 +73,54 @@ The stored procedure components also support SQL functions for the following dat * Oracle * PostgreSQL -For further details, see <<./jdbc.adoc#stored-procedures,Stored Procedures>>. +For further details, see xref:jdbc/stored-procedures.adoc[Stored Procedures]. [[x2.1-new-xpath-filter-support]] -===== XPath and XML Validating Filter +=== XPath and XML Validating Filter Spring Integration 2.1 provides a new XPath-based message filter. It is part of the `XML` module. The XPath filter lets you filter messages by using XPath Expressions. We also added documentation for the XML validating filter. -For more details, see <<./xml.adoc#xml-xpath-filter,Using the XPath Filter>> and <<./xml.adoc#xml-validating-filter,XML Validating Filter>>. +For more details, see xref:xml/xpath-filter.adoc[Using the XPath Filter] and xref:xml/validating-filter.adoc[XML Validating Filter]. [[x2.1-new-payload-enricher-support]] -===== Payload Enricher +=== Payload Enricher Since Spring Integration 2.1, we added the payload enricher. A payload enricher defines an endpoint that typically passes a https://docs.spring.io/spring-integration/api/org/springframework/integration/Message.html[`Message`] to the exposed request channel and then expects a reply message. The reply message then becomes the root object for evaluation of expressions to enrich the target payload. -For further details, see <<./content-enrichment.adoc#payload-enricher,Payload Enricher>>. +For further details, see xref:content-enrichment.adoc#payload-enricher[Payload Enricher]. [[x2.1-new-ftp-outbound-gateway]] -===== FTP and SFTP Outbound Gateways +=== FTP and SFTP Outbound Gateways Spring Integration 2.1 provides two new outbound gateways to interact with remote File Transfer Protocol (FTP) or Secure File Transfer Protocol (SFT) servers. These two gateways let you directly execute a limited set of remote commands. For instance, you can use these outbound gateways to list, retrieve, and delete remote files and have the Spring Integration message flow continue with the remote server's response. -For further details, see <<./ftp.adoc#ftp-outbound-gateway,FTP Outbound Gateway>> and <<./sftp.adoc#sftp-outbound-gateway,SFTP Outbound Gateway>>. +For further details, see xref:ftp/outbound-gateway.adoc[FTP Outbound Gateway] and xref:sftp/outbound-gateway.adoc[SFTP Outbound Gateway]. [[x2.1-new-ftp-session-caching]] -===== FTP Session Caching +=== FTP Session Caching As of version 2.1, we have exposed more flexibility with regards to session management for remote file adapters (for example, FTP, SFTP, and others). Specifically, we deprecated the `cache-sessions` attribute (which is available via the XML namespace support). As an alternative, we added the `sessionCacheSize` and `sessionWaitTimeout` attributes on the `CachingSessionFactory`. -For further details, see <<./ftp.adoc#ftp-session-caching,FTP Session Caching>> and <<./sftp.adoc#sftp-session-caching,SFTP Session Caching>>. +For further details, see xref:ftp/session-caching.adoc[FTP Session Caching] and xref:sftp/session-caching.adoc[SFTP Session Caching]. [[x2.1-framework-refactorings]] -==== Framework Refactoring +== Framework Refactoring We refactored the Spring Integration framework in a number of ways, all described in this section. [[x2.1-router-standardization]] -===== Standardizing Router Configuration +=== Standardizing Router Configuration We standardized router parameters across all router implementations with Spring Integration 2.1 to provide a more consistent user experience. @@ -134,13 +134,13 @@ If, however, you do want to drop messages silently, you can set `default-output- IMPORTANT: With the standardization of router parameters and the consolidation of the parameters described earlier, older Spring Integration based applications may break. -For further details, see `<<./router.adoc#router,Routers>>`. +For further details, see `xref:router.adoc[Routers]`. [[x2.1-schema-updated]] -===== XML Schemas updated to 2.1 +=== XML Schemas updated to 2.1 Spring Integration 2.1 ships with an updated XML Schema (version 2.1). -It provides many improvements, such as the Router standardizations <>. +It provides many improvements, such as the Router standardizations xref:changes-2.0-2.1.adoc#x2.1-router-standardization[discussed earlier]. From now on, developers must always declare the latest XML schema (currently version 2.1). Alternatively, they can use the version-less schema. @@ -148,7 +148,6 @@ Generally, the best option is to use version-less namespaces, as these automatic The following example declares a version-less Spring Integration namespace: -==== [source,xml] ---- @@ -162,11 +161,9 @@ The following example declares a version-less Spring Integration namespace: ... ---- -==== The following example declares a Spring Integration namespace with an explicit version: -==== [source,xml] ---- @@ -180,19 +177,18 @@ The following example declares a Spring Integration namespace with an explicit v ... ---- -==== The old 1.0 and 2.0 schemas are still there. However, if an application context still references one of those deprecated schemas, the validator fails on initialization. [[x2.1-source-control-infrastructure]] -==== Source Control Management and Build Infrastructure +== Source Control Management and Build Infrastructure Version 2.1 introduced a number of changes to source control management and build infrastructure. This section covers those changes. [[x2.1-move-to-github]] -===== Source Code Now Hosted on Github +=== Source Code Now Hosted on Github Since version 2.0, the Spring Integration project uses https://git-scm.com/[Git] for version control. To increase community visibility even further, the project was moved from SpringSource hosted Git repositories to https://www.github.com/[Github]. @@ -204,13 +200,13 @@ In fact, core committers now follow the same process as contributors. For more details, see https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc[Contributing]. [[x2.1-sonar]] -===== Improved Source Code Visibility with Sonar +=== Improved Source Code Visibility with Sonar In an effort to provide better source code visibility and consequently to monitor the quality of Spring Integration's source code, we set up an instance of https://www.sonarqube.org/[Sonar]. We gather metrics nightly and make them available at https://sonar.spring.io/dashboard?id=org.springframework.integration%3Aspring-integration%3Amain[sonar.spring.io]. [[x2.1-new-samples]] -==== New Samples +== New Samples For the 2.1 release of Spring Integration, we also expanded the Spring Integration Samples project and added many new samples, such as samples that cover AMQP support, a sample that showcases the new payload enricher, a sample illustrating techniques for testing Spring Integration flow fragments, and a sample for executing stored procedures against Oracle databases. For details, visit https://github.com/spring-projects/spring-integration-samples[spring-integration-samples]. diff --git a/src/reference/asciidoc/changes-2.1-2.2.adoc b/src/reference/antora/modules/ROOT/pages/changes-2.1-2.2.adoc similarity index 71% rename from src/reference/asciidoc/changes-2.1-2.2.adoc rename to src/reference/antora/modules/ROOT/pages/changes-2.1-2.2.adoc index e6c5bace2a..ac50fed0b4 100644 --- a/src/reference/asciidoc/changes-2.1-2.2.adoc +++ b/src/reference/antora/modules/ROOT/pages/changes-2.1-2.2.adoc @@ -1,51 +1,51 @@ [[migration-2.1-2.2]] -=== Changes between 2.1 and 2.2 += Changes between 2.1 and 2.2 See the https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-2.1-to-2.2-Migration-Guide[Migration Guide] for important changes that might affect your applications. You can find migration guides for all versions back to 2.1 on the https://github.com/spring-projects/spring-integration/wiki[wiki]. [[x2.2-new-components]] -==== New Components +== New Components Version 2.2 added a number of new components. [[x2.2-redis-store-adapters]] -===== `RedisStore` Inbound and Outbound Channel Adapters +=== `RedisStore` Inbound and Outbound Channel Adapters Spring Integration now has `RedisStore` Inbound and Outbound Channel Adapters, letting you write and read `Message` payloads to and from Redis collections. -For more information, see <<./redis.adoc#redis-store-outbound-channel-adapter,RedisStore Outbound Channel Adapter>> and <<./redis.adoc#redis-store-inbound-channel-adapter,Redis Store Inbound Channel Adapter>>. +For more information, see xref:redis.adoc#redis-store-outbound-channel-adapter[RedisStore Outbound Channel Adapter] and xref:redis.adoc#redis-store-inbound-channel-adapter[Redis Store Inbound Channel Adapter]. [[x2.2-mongo-adapters]] -===== MongoDB Inbound and Outbound Channel Adapters +=== MongoDB Inbound and Outbound Channel Adapters Spring Integration now has MongoDB inbound and outbound channel adapters, letting you write and read `Message` payloads to and from a MongoDB document store. -For more information, see <<./mongodb.adoc#mongodb-outbound-channel-adapter,MongoDB Outbound Channel Adapter>> and <<./mongodb.adoc#mongodb-inbound-channel-adapter,MongoDB Inbound Channel Adapter>>. +For more information, see xref:mongodb.adoc#mongodb-outbound-channel-adapter[MongoDB Outbound Channel Adapter] and xref:mongodb.adoc#mongodb-inbound-channel-adapter[MongoDB Inbound Channel Adapter]. [[x2.2-jpa]] -===== JPA Endpoints +=== JPA Endpoints Spring Integration now includes components for the Java Persistence API (JPA) for retrieving and persisting JPA entity objects. The JPA Adapter includes the following components: -* <<./jpa.adoc#jpa-inbound-channel-adapter,Inbound channel adapter>> -* <<./jpa.adoc#jpa-outbound-channel-adapter,Outbound channel adapter>> -* <<./jpa.adoc#jpa-updating-outbound-gateway,Updating outbound gateway>> -* <<./jpa.adoc#jpa-retrieving-outbound-gateway,Retrieving outbound gateway>> +* xref:jpa/inbound-channel-adapter.adoc[Inbound channel adapter] +* xref:jpa/outbound-channel-adapter.adoc[Outbound channel adapter] +* xref:jpa/outbound-gateways.adoc#jpa-updating-outbound-gateway[Updating outbound gateway] +* xref:jpa/outbound-gateways.adoc#jpa-retrieving-outbound-gateway[Retrieving outbound gateway] -For more information, see <<./jpa.adoc#jpa,JPA Support>>. +For more information, see xref:jpa.adoc[JPA Support]. [[x2.2-general]] -==== General Changes +== General Changes This section describes general changes from version 2.1 to version 2.2. [[x2.2-spring-31]] -===== Spring 3.1 Used by Default +=== Spring 3.1 Used by Default Spring Integration now uses Spring 3.1. [[x2.2-handler-advice]] -===== Adding Behavior to Endpoints +=== Adding Behavior to Endpoints The ability to add an `` to a poller has been available for some time. However, the behavior added by this affects the entire integration flow. @@ -58,20 +58,20 @@ In addition, we added three standard advice classes for this purpose: * `MessageHandlerCircuitBreakerAdvice` * `ExpressionEvaluatingMessageHandlerAdvice` -For more information, see <<./handler-advice.adoc#message-handler-advice-chain,Adding Behavior to Endpoints>>. +For more information, see xref:handler-advice.adoc[Adding Behavior to Endpoints]. [[x2.2-transaction-sync]] -===== Transaction Synchronization and Pseudo Transactions +=== Transaction Synchronization and Pseudo Transactions Pollers can now participate in Spring's Transaction Synchronization feature. This allows for synchronizing such operations as renaming files by an inbound channel adapter, depending on whether the transaction commits or rolls back. In addition, you can enable these features when no "`real`" transaction is present, by means of a `PseudoTransactionManager`. -For more information, see <<./transactions.adoc#transaction-synchronization,Transaction Synchronization>>. +For more information, see xref:transactions.adoc#transaction-synchronization[Transaction Synchronization]. [[x2.2-file-adapter]] -===== File Adapter: Improved File Overwrite and Append Handling +=== File Adapter: Improved File Overwrite and Append Handling When using the file outbound channel adapter or the file outbound gateway, you can use a new `mode` property. Prior to Spring Integration 2.2, target files were replaced when they existed. @@ -82,10 +82,10 @@ Now you can specify the following options: * `FAIL` * `IGNORE` -For more information, see <<./file.adoc#file-writing-destination-exists,Dealing with Existing Destination Files>>. +For more information, see xref:file/writing.adoc#file-writing-destination-exists[Dealing with Existing Destination Files]. [[x2.2-outbound-gateways]] -===== Reply-Timeout Added to More Outbound Gateways +=== Reply-Timeout Added to More Outbound Gateways The XML Namespace support adds the reply-timeout attribute to the following outbound gateways: @@ -96,7 +96,7 @@ The XML Namespace support adds the reply-timeout attribute to the following outb * WS Outbound Gateway [[x2.2-amqp-11]] -===== Spring-AMQP 1.1 +=== Spring-AMQP 1.1 Spring Integration now uses Spring AMQP 1.1. This enables several features to be used within a Spring Integration application, including the following: @@ -108,17 +108,19 @@ This enables several features to be used within a Spring Integration application * Support for dead letter exchanges and dead letter queues [[x2.2-jdbc-11]] -===== JDBC Support - Stored Procedures Components +=== JDBC Support - Stored Procedures Components -====== SpEL Support +[[spel-support]] +==== SpEL Support When using the stored procedure components of the Spring Integration JDBC Adapter, you can now provide stored procedure names or stored function names by using the Spring Expression Language (SpEL). Doing so lets you specify the stored procedures to be invoked at runtime. For example, you can provide stored procedure names that you would like to execute through message headers. -For more information, see <<./jdbc.adoc#stored-procedures,Stored Procedures>>. +For more information, see xref:jdbc/stored-procedures.adoc[Stored Procedures]. -====== JMX Support +[[jmx-support]] +==== JMX Support The stored procedure components now provide basic JMX support, exposing some of their properties as MBeans: @@ -127,37 +129,37 @@ The stored procedure components now provide basic JMX support, exposing some of * `JdbcCallOperations` cache statistics [[x2.2-jdbc-gateway-update-optional]] -===== JDBC Support: Outbound Gateway +=== JDBC Support: Outbound Gateway When you use the JDBC outbound gateway, the update query is no longer mandatory. You can now provide only a select query by using the request message as a source of parameters. [[x2.2-jdbc-message-store-channels]] -===== JDBC Support: Channel-specific Message Store Implementation +=== JDBC Support: Channel-specific Message Store Implementation We added a new message channel-specific message store implementation, providing a more scalable solution using database-specific SQL queries. -For more information, see <<./jdbc.adoc#jdbc-message-store-channels,Backing Message Channels>>. +For more information, see xref:jdbc/message-store.adoc#jdbc-message-store-channels[Backing Message Channels]. [[x2.2-shutdown]] -===== Orderly Shutdown +=== Orderly Shutdown We added a method called `stopActiveComponents()` to the `IntegrationMBeanExporter`. It allows a Spring Integration application to be shut down in an orderly manner, disallowing new inbound messages to certain adapters and waiting for some time to allow in-flight messages to complete. [[x2.2-jms-og]] -===== JMS Outbound Gateway Improvements +=== JMS Outbound Gateway Improvements You can now configure the JMS outbound gateway to use a `MessageListener` container to receive replies. Doing so can improve performance of the gateway. [[x2.2-o-t-j-t]] -===== `ObjectToJsonTransformer` +=== `ObjectToJsonTransformer` By default, the `ObjectToJsonTransformer` now sets the `content-type` header to `application/json`. -For more information, see <<./transformer.adoc#transformer,Transformer>>. +For more information, see xref:transformer.adoc[Transformer]. [[httpChanges]] -===== HTTP Support +=== HTTP Support Java serialization over HTTP is no longer enabled by default. Previously, when setting an `expected-response-type` on a `Serializable` object, the `Accept` header was not properly set up. diff --git a/src/reference/asciidoc/changes-2.2-3.0.adoc b/src/reference/antora/modules/ROOT/pages/changes-2.2-3.0.adoc similarity index 82% rename from src/reference/asciidoc/changes-2.2-3.0.adoc rename to src/reference/antora/modules/ROOT/pages/changes-2.2-3.0.adoc index 7f250bf9c3..18b900bc5a 100644 --- a/src/reference/asciidoc/changes-2.2-3.0.adoc +++ b/src/reference/antora/modules/ROOT/pages/changes-2.2-3.0.adoc @@ -1,16 +1,16 @@ [[migration-2.2-3.0]] -=== Changes Between 2.2 and 3.0 += Changes Between 2.2 and 3.0 See the https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-2.2-to-3.0-Migration-Guide[Migration Guide] for important changes that might affect your applications. You can find migration guides for all versions back to 2.1 on the https://github.com/spring-projects/spring-integration/wiki[wiki]. [[x3.0-new-components]] -==== New Components +== New Components Version 3.0 added a number of new components. [[x3.0-request-mapping]] -===== HTTP Request Mapping +=== HTTP Request Mapping The HTTP module now provides powerful request mapping support for inbound endpoints. We replaced the `UriPathHandlerMapping` class with `IntegrationRequestMappingHandlerMapping`, which is registered under the bean name of `integrationRequestMappingHandlerMapping` in the application context. @@ -18,29 +18,29 @@ Upon parsing of the HTTP inbound endpoint, either a new `IntegrationRequestMappi To achieve flexible request mapping configuration, Spring Integration provides the `` child element for `` and the ``. Both HTTP inbound endpoints are now fully based on the request mapping infrastructure that was introduced with Spring MVC 3.1. For example, multiple paths are supported on a single inbound endpoint. -For more information see <<./http.adoc#http-namespace,HTTP Namespace Support>>. +For more information see xref:http/namespace.adoc[HTTP Namespace Support]. [[x3.0-spel-customization]] -===== Spring Expression Language (SpEL) Configuration +=== Spring Expression Language (SpEL) Configuration We added a new `IntegrationEvaluationContextFactoryBean` to allow configuration of custom `PropertyAccessor` implementations and functions for use in SpEL expressions throughout the framework. -For more information, see <<./spel.adoc#spel,Spring Expression Language (SpEL)>>. +For more information, see xref:spel.adoc[Spring Expression Language (SpEL)]. [[x3.0-spel-functions]] -===== SpEL Functions Support +=== SpEL Functions Support To customize the SpEL `EvaluationContext` with static `Method` functions, we introduced the `` component. We also added two built-in functions: `#jsonPath` and `#xpath`. -For more information, see <<./spel.adoc#spel-functions,SpEL Functions>>. +For more information, see xref:spel.adoc#spel-functions[SpEL Functions]. [[x3.0-spel-property-accessors]] -===== SpEL PropertyAccessors Support +=== SpEL PropertyAccessors Support To customize the SpEL `EvaluationContext` with `PropertyAccessor` implementations, we added the `` component. -For more information, see <<./spel.adoc#spel-property-accessors,Property Accessors>>. +For more information, see xref:spel.adoc#spel-property-accessors[Property Accessors]. [[x3.0-redis-new-components]] -===== Redis: New Components +=== Redis: New Components We added a new Redis-based https://docs.spring.io/spring-integration/api/org/springframework/integration/metadata/MetadataStore.html[`MetadataStore`] implementation. You can use the `RedisMetadataStore` to maintain the state of a `MetadataStore` across application restarts. @@ -52,38 +52,38 @@ This new `MetadataStore` implementation can be used with adapters, such as: We added new queue-based components. We added the `` and `` components to perform 'right pop' and 'left push' operations, respectively, on a Redis List. -For more information, "`see <<./redis.adoc#redis,Redis Support>>`". +For more information, "`see xref:redis.adoc[Redis Support]`". [[x3.0-hcr]] -===== Header Channel Registry +=== Header Channel Registry You can now instruct the framework to store reply channels and error channels in a registry for later resolution. This is useful for cases where the `replyChannel` or `errorChannel` might be lost (for example, when serializing a message). -See <<./content-enrichment.adoc#header-enricher,Header Enricher>> for more information. +See xref:content-enrichment.adoc#header-enricher[Header Enricher] for more information. [[x3.0-configurable-mongo-MS]] -===== MongoDB support: New `ConfigurableMongoDbMessageStore` +=== MongoDB support: New `ConfigurableMongoDbMessageStore` In addition to the existing `eMongoDbMessageStore`, we introduced a new `ConfigurableMongoDbMessageStore`. This provides a more robust and flexible implementation of `MessageStore` for MongoDB. It does not have backward compatibility with the existing store, but we recommend using it for new applications. Existing applications can use it, but messages in the old store are not available. -See <<./mongodb.adoc#mongodb,MongoDb Support>> for more information. +See xref:mongodb.adoc[MongoDb Support] for more information. [[x3.0-syslog]] -===== Syslog Support +=== Syslog Support Building on the 2.2 `SyslogToMapTransformer`, Spring Integration 3.0 introduces `UDP` and `TCP` inbound channel adapters especially tailored for receiving SYSLOG messages. -For more information, see <<./syslog.adoc#syslog,Syslog Support>>. +For more information, see xref:syslog.adoc[Syslog Support]. [[x3.0-tail]] -===== `tail` Support +=== `tail` Support We added file inbound channel adapters that use the `tail` command to generate messages when lines are added to the end of text files. -See <<./file.adoc#file-tailing,'tail'ing Files>>. +See xref:file/reading.adoc#file-tailing['tail'ing Files]. [[x3.0-jmx]] -===== JMX Support +=== JMX Support We added ``. This adapter queries the JMX MBean tree and sends a message with a payload that is the graph of objects that match the query. @@ -92,10 +92,10 @@ It permits simple transformation to, for example, JSON. The `IntegrationMBeanExporter` now allows the configuration of a custom `ObjectNamingStrategy` by using the `naming-strategy` attribute. -For more information, see <<./jmx.adoc#jmx,JMX Support>>. +For more information, see xref:jmx.adoc[JMX Support]. [[x3.0-tcp-events]] -===== TCP/IP Connection Events and Connection Management +=== TCP/IP Connection Events and Connection Management `TcpConnection` instances now emit `ApplicationEvent` instances (specifically `TcpConnectionEvent` instances) when connections are opened or closed or when an exception occurs. This change lets applications be informed of changes to TCP connections by using the normal Spring `ApplicationListener` mechanism. @@ -112,35 +112,35 @@ It lets applications broadcast to all open connections, among other uses. Finally, the connection factories also provide a new method called `closeConnection(String connectionId)`, which lets applications explicitly close a connection by using its ID. -For more information see <<./ip.adoc#tcp-events,TCP Connection Events>>. +For more information see xref:changes-4.1-4.2.adoc#x4.2-tcp-events[TCP Connection Events]. [[x3.0-inbound-script]] -===== Inbound Channel Adapter Script Support +=== Inbound Channel Adapter Script Support The `` now supports using `` and `