-
Notifications
You must be signed in to change notification settings - Fork 1.1k
GH-3957: Add JmsInboundGateway.replyToExpression #8560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright 2023 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.springframework.integration.util; | ||
|
||
import java.util.function.Function; | ||
|
||
/** | ||
* A Function-like interface which allows throwing Error. | ||
* | ||
* @param <T> the input type. | ||
* @param <R> the output type. | ||
* | ||
* @author Artem Bilan | ||
* | ||
* @since 6.1 | ||
*/ | ||
@FunctionalInterface | ||
public interface CheckedFunction<T, R> { | ||
|
||
R apply(T t) throws Throwable; // NOSONAR | ||
|
||
default Function<T, R> unchecked() { | ||
return t1 -> { | ||
try { | ||
return apply(t1); | ||
} | ||
catch (Throwable t) { // NOSONAR | ||
if (t instanceof RuntimeException runtimeException) { | ||
throw runtimeException; | ||
} | ||
else if (t instanceof Error error) { | ||
throw error; | ||
} | ||
else { | ||
throw new IllegalStateException(t); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
/* | ||
* Copyright 2002-2022 the original author or authors. | ||
* Copyright 2002-2023 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
|
@@ -30,7 +30,10 @@ | |
import org.springframework.beans.factory.BeanFactoryAware; | ||
import org.springframework.beans.factory.InitializingBean; | ||
import org.springframework.core.log.LogAccessor; | ||
import org.springframework.expression.Expression; | ||
import org.springframework.expression.spel.support.StandardEvaluationContext; | ||
import org.springframework.integration.core.MessagingTemplate; | ||
import org.springframework.integration.expression.ExpressionUtils; | ||
import org.springframework.integration.gateway.MessagingGatewaySupport; | ||
import org.springframework.integration.support.DefaultMessageBuilderFactory; | ||
import org.springframework.integration.support.MessageBuilderFactory; | ||
|
@@ -42,6 +45,7 @@ | |
import org.springframework.jms.support.converter.SimpleMessageConverter; | ||
import org.springframework.jms.support.destination.DestinationResolver; | ||
import org.springframework.jms.support.destination.DynamicDestinationResolver; | ||
import org.springframework.lang.Nullable; | ||
import org.springframework.messaging.Message; | ||
import org.springframework.messaging.MessageChannel; | ||
import org.springframework.messaging.MessagingException; | ||
|
@@ -90,12 +94,16 @@ public class ChannelPublishingJmsMessageListener | |
|
||
private DestinationResolver destinationResolver = new DynamicDestinationResolver(); | ||
|
||
private Expression replyToExpression; | ||
|
||
private JmsHeaderMapper headerMapper = new DefaultJmsHeaderMapper(); | ||
|
||
private BeanFactory beanFactory; | ||
|
||
private MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory(); | ||
|
||
private StandardEvaluationContext evaluationContext; | ||
|
||
/** | ||
* Specify whether a JMS reply Message is expected. | ||
* @param expectReply true if a reply is expected. | ||
|
@@ -261,6 +269,17 @@ public void setDestinationResolver(DestinationResolver destinationResolver) { | |
this.destinationResolver = destinationResolver; | ||
} | ||
|
||
/** | ||
* Set a SpEL expression to resolve a 'replyTo' destination from a request | ||
* {@link jakarta.jms.Message} as a root evaluation object | ||
* if {@link jakarta.jms.Message#getJMSReplyTo()} is null. | ||
* @param replyToExpression the SpEL expression for 'replyTo' destination. | ||
* @since 6.1 | ||
*/ | ||
public void setReplyToExpression(Expression replyToExpression) { | ||
this.replyToExpression = replyToExpression; | ||
} | ||
|
||
/** | ||
* Provide a {@link MessageConverter} implementation to use when | ||
* converting between JMS Messages and Spring Integration Messages. | ||
|
@@ -382,6 +401,7 @@ public void afterPropertiesSet() { | |
} | ||
this.gatewayDelegate.afterPropertiesSet(); | ||
this.messageBuilderFactory = IntegrationUtils.getMessageBuilderFactory(this.beanFactory); | ||
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory); | ||
} | ||
|
||
protected void start() { | ||
|
@@ -417,21 +437,22 @@ else if (replyMessage.getJMSCorrelationID() == null) { | |
|
||
/** | ||
* Determine a reply destination for the given message. | ||
* <p> This implementation first checks the boolean 'error' flag which signifies that the reply is an error message. | ||
* If reply is not an error it will first check the JMS Reply-To {@link Destination} of the supplied request message; | ||
* if that is not <code>null</code> it is returned; if it is <code>null</code>, then the configured | ||
* {@link #resolveDefaultReplyDestination default reply destination} is returned; if this too is <code>null</code>, | ||
* It will first check the JMS Reply-To {@link Destination} | ||
* of the supplied request message; | ||
* if that is null, then the configured {@link #replyToExpression} is evaluated | ||
* (if any), then a{@link #resolveDefaultReplyDestination default reply destination} | ||
* is returned; if this too is null, | ||
* then an {@link InvalidDestinationException} is thrown. | ||
* @param request the original incoming JMS message | ||
* @param session the JMS Session to operate on | ||
* @return the reply destination (never <code>null</code>) | ||
* @return the reply destination (never null) | ||
* @throws JMSException if thrown by JMS API methods | ||
* @throws InvalidDestinationException if no {@link Destination} can be determined | ||
* @see #setDefaultReplyDestination | ||
* @see jakarta.jms.Message#getJMSReplyTo() | ||
*/ | ||
private Destination getReplyDestination(jakarta.jms.Message request, Session session) throws JMSException { | ||
Destination replyTo = request.getJMSReplyTo(); | ||
Destination replyTo = resolveReplyTo(request, session); | ||
if (replyTo == null) { | ||
replyTo = resolveDefaultReplyDestination(session); | ||
if (replyTo == null) { | ||
|
@@ -440,6 +461,24 @@ private Destination getReplyDestination(jakarta.jms.Message request, Session ses | |
} | ||
} | ||
return replyTo; | ||
|
||
} | ||
|
||
@Nullable | ||
private Destination resolveReplyTo(jakarta.jms.Message request, Session session) throws JMSException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so. Then it is probably better to make the whole |
||
Destination replyTo = request.getJMSReplyTo(); | ||
if (replyTo == null) { | ||
if (this.replyToExpression != null) { | ||
Object replyToValue = this.replyToExpression.getValue(this.evaluationContext, request); | ||
if (replyToValue instanceof Destination destination) { | ||
return destination; | ||
} | ||
else if (replyToValue instanceof String destinationName) { | ||
return this.destinationResolver.resolveDestinationName(session, destinationName, false); | ||
} | ||
} | ||
} | ||
return replyTo; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,5 @@ | ||||||||||||||
/* | ||||||||||||||
* Copyright 2016-2022 the original author or authors. | ||||||||||||||
* Copyright 2016-2023 the original author or authors. | ||||||||||||||
* | ||||||||||||||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||
* you may not use this file except in compliance with the License. | ||||||||||||||
|
@@ -19,11 +19,15 @@ | |||||||||||||
import java.util.function.Consumer; | ||||||||||||||
|
||||||||||||||
import jakarta.jms.Destination; | ||||||||||||||
import jakarta.jms.Message; | ||||||||||||||
|
||||||||||||||
import org.springframework.expression.Expression; | ||||||||||||||
import org.springframework.integration.dsl.MessagingGatewaySpec; | ||||||||||||||
import org.springframework.integration.expression.FunctionExpression; | ||||||||||||||
import org.springframework.integration.jms.ChannelPublishingJmsMessageListener; | ||||||||||||||
import org.springframework.integration.jms.JmsHeaderMapper; | ||||||||||||||
import org.springframework.integration.jms.JmsInboundGateway; | ||||||||||||||
import org.springframework.integration.util.CheckedFunction; | ||||||||||||||
import org.springframework.jms.listener.AbstractMessageListenerContainer; | ||||||||||||||
import org.springframework.jms.support.converter.MessageConverter; | ||||||||||||||
import org.springframework.jms.support.destination.DestinationResolver; | ||||||||||||||
|
@@ -137,6 +141,44 @@ public S destinationResolver(DestinationResolver destinationResolver) { | |||||||||||||
return _this(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Set a SpEL expression to resolve a 'replyTo' destination from a request {@link jakarta.jms.Message} | ||||||||||||||
* as a root evaluation object if {@link jakarta.jms.Message#getJMSReplyTo()} is null. | ||||||||||||||
* @param replyToExpression the SpEL expression for 'replyTo' destination. | ||||||||||||||
* @return the spec. | ||||||||||||||
* @since 6.1 | ||||||||||||||
* @see ChannelPublishingJmsMessageListener#setReplyToExpression(Expression) | ||||||||||||||
*/ | ||||||||||||||
public S replyToExpression(String replyToExpression) { | ||||||||||||||
return replyToExpression(PARSER.parseExpression(replyToExpression)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Set a function to resolve a 'replyTo' destination from a request {@link jakarta.jms.Message} | ||||||||||||||
* as a root evaluation object if {@link jakarta.jms.Message#getJMSReplyTo()} is null. | ||||||||||||||
* @param replyToFunction the function for 'replyTo' destination. | ||||||||||||||
* @return the spec. | ||||||||||||||
* @since 6.1 | ||||||||||||||
* @see ChannelPublishingJmsMessageListener#setReplyToExpression(Expression) | ||||||||||||||
*/ | ||||||||||||||
public S replyToFunction(CheckedFunction<Message, ?> replyToFunction) { | ||||||||||||||
return replyToExpression(new FunctionExpression<>(replyToFunction.unchecked())); | ||||||||||||||
} | ||||||||||||||
Comment on lines
+165
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this; why not just
Suggested change
?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, you are assuming that the user will use a JMS operation to determine the destination? I guess that's reasonable given that the root object is a OK. |
||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* Set a SpEL expression to resolve a 'replyTo' destination from a request {@link jakarta.jms.Message} | ||||||||||||||
* as a root evaluation object if {@link jakarta.jms.Message#getJMSReplyTo()} is null. | ||||||||||||||
* @param replyToExpression the SpEL expression for 'replyTo' destination. | ||||||||||||||
* @return the spec. | ||||||||||||||
* @since 6.1 | ||||||||||||||
* @see ChannelPublishingJmsMessageListener#setReplyToExpression(Expression) | ||||||||||||||
*/ | ||||||||||||||
public S replyToExpression(Expression replyToExpression) { | ||||||||||||||
this.target.getListener().setReplyToExpression(replyToExpression); | ||||||||||||||
return _this(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* @param messageConverter the messageConverter. | ||||||||||||||
* @return the spec. | ||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not really comfortable with institutionalizing this JVM abuse. Is there no alternative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. Will it be better if there is no that
unchecked()
then?Something like plain:
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right; that's the bit I was objecting to. You probebly need a
// NOSONAR
here too.