From aeb43f306943a026409dc3e89b7f3c47b39b328d Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Tue, 16 Feb 2021 17:08:24 -0500 Subject: [PATCH] JMS-in-gateway: destination to requestDestination (#3494) * JMS-in-gateway: destination to requestDestination Related to https://stackoverflow.com/questions/66174779/what-is-the-dsl-version-of-int-jmsinbound-gateway-request-destination In Java DSL, the `JmsInboundGatewaySpec` expose a `destination()` option which is not correlated what we have in XML with a `request-destination` and what we have with a `replyQueue` option * Deprecate an existing `destination()` option in the `JmsInboundGatewaySpec` in favor of newly introduced `requestDestination()` * Clean up a bit `jms.adoc` and add some code block-switch sections to show Java & Kotlin DSLs along side with existing XML configs * Fix typo. Co-authored-by: Gary Russell --- .../jms/dsl/JmsInboundGatewaySpec.java | 42 ++++- .../integration/jms/dsl/JmsTests.java | 2 +- src/reference/asciidoc/jms.adoc | 153 +++++++++++++++--- 3 files changed, 169 insertions(+), 28 deletions(-) diff --git a/spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsInboundGatewaySpec.java b/spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsInboundGatewaySpec.java index bf824be77b..1414107a4c 100644 --- a/spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsInboundGatewaySpec.java +++ b/spring-integration-jms/src/main/java/org/springframework/integration/jms/dsl/JmsInboundGatewaySpec.java @@ -197,7 +197,8 @@ public class JmsInboundGatewaySpec> * @param the target {@link JmsListenerContainerSpec} implementation type. * @param the target {@link AbstractMessageListenerContainer} implementation type. */ - public static class JmsInboundGatewayListenerContainerSpec, C extends AbstractMessageListenerContainer> + public static class JmsInboundGatewayListenerContainerSpec, + C extends AbstractMessageListenerContainer> extends JmsInboundGatewaySpec> { private final S spec; @@ -212,9 +213,22 @@ public class JmsInboundGatewaySpec> * @param destination the destination * @return the spec. * @see JmsListenerContainerSpec#destination(Destination) + * @deprecated since 5.5 in favor of {@link #requestDestination(Destination)} */ + @Deprecated public JmsInboundGatewayListenerContainerSpec destination(Destination destination) { - this.spec.destination(destination); + return requestDestination(destination); + } + + /** + * Specify a request destination for incoming messages. + * @param requestDestination the destination + * @return the spec. + * @see JmsListenerContainerSpec#destination(Destination) + * @since 5.5 + */ + public JmsInboundGatewayListenerContainerSpec requestDestination(Destination requestDestination) { + this.spec.destination(requestDestination); return _this(); } @@ -222,14 +236,32 @@ public class JmsInboundGatewaySpec> * @param destinationName the destinationName * @return the spec. * @see JmsListenerContainerSpec#destination(String) + * @deprecated since 5.5 in favor of {@link #requestDestination(String)} */ + @Deprecated public JmsInboundGatewayListenerContainerSpec destination(String destinationName) { - this.spec.destination(destinationName); + return requestDestination(destinationName); + } + + /** + * Specify a request destination for incoming messages. + * @param requestDestinationName the destination name + * @return the spec. + * @see JmsListenerContainerSpec#destination(String) + * @since 5.5 + */ + public JmsInboundGatewayListenerContainerSpec requestDestination(String requestDestinationName) { + this.spec.destination(requestDestinationName); return _this(); } - public JmsInboundGatewayListenerContainerSpec configureListenerContainer( - Consumer configurer) { + /** + * Specify a {@link Consumer} to accept a {@link JmsListenerContainerSpec} for further configuration. + * @param configurer the {@link Consumer} to accept a {@link JmsListenerContainerSpec} + * for further configuration. + * @return the spec + */ + public JmsInboundGatewayListenerContainerSpec configureListenerContainer(Consumer configurer) { Assert.notNull(configurer, "'configurer' must not be null"); configurer.accept(this.spec); return _this(); diff --git a/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java b/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java index 7b37357edb..65009ba796 100644 --- a/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java +++ b/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java @@ -444,7 +444,7 @@ public class JmsTests extends ActiveMQMultiContextTests { } })) - .destination("jmsPipelineTest") + .requestDestination("jmsPipelineTest") .configureListenerContainer(c -> c.transactionManager(mock(PlatformTransactionManager.class)))) .filter(payload -> !"junk".equals(payload)) diff --git a/src/reference/asciidoc/jms.adoc b/src/reference/asciidoc/jms.adoc index c28e217bd1..92fa9ccdfb 100644 --- a/src/reference/asciidoc/jms.adoc +++ b/src/reference/asciidoc/jms.adoc @@ -6,8 +6,8 @@ Spring Integration provides channel adapters for receiving and sending JMS messa You need to include this dependency into your project: ==== +[source, xml, subs="normal", role="primary"] .Maven -[source, xml, subs="normal"] ---- org.springframework.integration @@ -15,9 +15,8 @@ You need to include this dependency into your project: {project-version} ---- - +[source, groovy, subs="normal", role="secondary"] .Gradle -[source, groovy, subs="normal"] ---- compile "org.springframework.integration:spring-integration-jms:{project-version}" ---- @@ -52,7 +51,44 @@ The inbound channel adapter requires a reference to either a single `JmsTemplate The following example defines an inbound channel adapter with a `Destination` reference: ==== -[source,xml] +[source, java, role="primary"] +.Java DSL +---- +@Bean +public IntegrationFlow jmsInbound(ConnectionFactory connectionFactory) { + return IntegrationFlows.from( + Jms.inboundAdapter(connectionFactory) + .destination("inQueue"), + e -> e.poller(poller -> poller.fixedRate(30000))) + .handle(m -> System.out.println(m.getPayload())) + .get(); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun jmsInbound(connectionFactory: ConnectionFactory) = + integrationFlow( + Jms.inboundAdapter(connectionFactory).destination("inQueue"), + { poller { Pollers.fixedRate(30000) } }) + { + handle { m -> println(m.payload) } + } +---- +[source, java, role="secondary"] +.Java +---- +@Bean +@InboundChannelAdapter(value = "exampleChannel", poller = @Poller(fixedRate = "30000")) +public MessageSource jmsIn(ConnectionFactory connectionFactory) { + JmsDestinationPollingSource source = new JmsDestinationPollingSource(new JmsTemplate(connectionFactory)); + source.setDestinationName("inQueue"); + return source; +} +---- +[source, xml, role="secondary"] +.XML ---- @@ -72,19 +108,7 @@ However, if your JMS `ConnectionFactory` has a different bean name, you need to If `extract-payload` is set to `true` (the default), the received JMS Message is passed through the `MessageConverter`. When relying on the default `SimpleMessageConverter`, this means that the resulting Spring Integration Message has the JMS message's body as its payload. A JMS `TextMessage` produces a string-based payload, a JMS `BytesMessage` produces a byte array payload, and the serializable instance of a JMS `ObjectMessage` becomes the Spring Integration message's payload. -If you prefer to have the raw JMS message as the Spring Integration message's payload, set `extract-payload` to `false`, as the following example shows: - -==== -[source,xml] ----- - - - ----- -==== +If you prefer to have the raw JMS message as the Spring Integration message's payload, set the `extractPayload` option to `false`. Starting with version 5.0.8, a default value of the `receive-timeout` is `-1` (no wait) for the `org.springframework.jms.connection.CachingConnectionFactory` and `cacheConsumers`, otherwise it is 1 second. The JMS Inbound Channel Adapter crates a `DynamicJmsTemplate` based on the provided `ConnectionFactory` and options. @@ -119,7 +143,54 @@ The `message-driven-channel-adapter` requires a reference to either an instance The following example defines a message-driven channel adapter with a `Destination` reference: ==== -[source,xml] +[source, java, role="primary"] +.Java DSL +---- +@Bean +public IntegrationFlow jmsMessageDrivenRedeliveryFlow() { + return IntegrationFlows + .from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory()) + .destination("inQueue")) + .channel("exampleChannel") + .get(); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun jmsMessageDrivenFlowWithContainer() = + integrationFlow( + Jms.messageDrivenChannelAdapter(jmsConnectionFactory()) + .destination("inQueue")) { + channel("exampleChannel") + } +---- +[source, java, role="secondary"] +.Java +---- +@Bean +public JmsMessageDrivenEndpoint jmsIn() { + JmsMessageDrivenEndpoint endpoint = new JmsMessageDrivenEndpoint(container(), listener()); + return endpoint; +} +@Bean +public AbstractMessageListenerContainer container() { + DefaultMessageListenerContainer container = new DefaultMessageListenerContainer(); + container.setConnectionFactory(cf()); + container.setDestinationName("inQueue"); + return container; +} + +@Bean +public ChannelPublishingJmsMessageListener listener() { + ChannelPublishingJmsMessageListener listener = new ChannelPublishingJmsMessageListener(); + listener.setRequestChannelName("exampleChannel"); + return listener; +} +---- +[source, xml, role="secondary"] +.XML ---- ---- @@ -203,7 +274,46 @@ As with the inbound channel adapter, the easiest way to configure this adapter i The following configuration produces an adapter that receives Spring Integration messages from the `exampleChannel`, converts those into JMS messages, and sends them to the JMS destination reference whose bean name is `outQueue`: ==== -[source,xml] +[source, java, role="primary"] +.Java DSL +---- +@Bean +public IntegrationFlow jmsOutboundFlow() { + return f -> f + .handle(Jms.outboundAdapter(cachingConnectionFactory()) + .destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER) + .configureJmsTemplate(t -> t.id("jmsOutboundFlowTemplate"))); +} +---- +[source, kotlin, role="secondary"] +.Kotlin DSL +---- +@Bean +fun jmsOutboundFlow() = + integrationFlow { + handle(Jms.outboundAdapter(jmsConnectionFactory()) + .apply { + destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER) + deliveryModeFunction { DeliveryMode.NON_PERSISTENT } + timeToLiveExpression("10000") + configureJmsTemplate { it.explicitQosEnabled(true) } + } + ) + } +---- +[source, java, role="secondary"] +.Java +---- +@Bean +@ServiceActivator(inputChannel = "exampleChannel") +public MessageHandler jmsOut() { + JmsSendingMessageHandler handler = new JmsSendingMessageHandler(new JmsTemplate(connectionFactory)); + handler.setDestinationName("outQueue"); + return handler; +} +---- +[source, xml, role="secondary"] +.XML ---- ---- @@ -263,8 +373,7 @@ The default is 'true'. Similarly, for an inbound-gateway, the 'extract-reply-payload' property applies to the Spring Integration message that is to be converted into a reply JMS Message. If you want to pass the whole Spring Integration message (as the body of a JMS ObjectMessage), set value this to 'false'. -By default, it is also 'true' that the Spring Integration message payload is converted into a JMS Message (for example, a -`String` payload becomes a JMS TextMessage). +By default, it is also 'true' that the Spring Integration message payload is converted into a JMS Message (for example, a `String` payload becomes a JMS TextMessage). As with anything else, gateway invocation might result in error. By default, a producer is not notified of the errors that might have occurred on the consumer side and times out waiting for the reply. @@ -306,7 +415,7 @@ To revert to the previous behavior, set the `shutdownContainerOnStop` on the `Jm === Outbound Gateway The outbound gateway creates JMS messages from Spring Integration messages and sends them to a 'request-destination'. -It thens handle the JMS reply message either by using a selector to receive from the 'reply-destination' that you configure or, if no 'reply-destination' is provided, by creating JMS `TemporaryQueue` instances. +It then handles the JMS reply message either by using a selector to receive from the 'reply-destination' that you configure or, if no 'reply-destination' is provided, by creating JMS `TemporaryQueue` instances. [[jms-outbound-gateway-memory-caution]] [CAUTION]