I am trying to configure integration flow with gateway. Using Java DSL on kotlin.
Gateway config:
#MessagingGateway(name = "tdiOutSenderGateway")
interface TdiOutSenderGateway {
fun send(packet: PhasorEnricher.Packet)
}
Flow config:
#Bean
open fun tdiOutSendFlow() = IntegrationFlows
.from(TdiOutSenderGateway::class.java)
.transform(tdiOutSenderRouter())
.get()!!
got send is not supported, because no request channel has been configured
docs: request channel will be autoconfigured.
#IntegrationComponentScan exists
https://docs.spring.io/spring-integration/reference/htmlsingle/#java-dsl-gateway readed and used as base
double checked: I am using exactly this gateway
Is there any additional setting I missed?
Of course, transform should return something, there should be route or handle.
But even I fixed #1 I faced with that: Void kotlin functions return Unit. Spring integrations checks Unit == null which is false, tries to find the next channel and throw an error. The fix is to use kotlin lambda and return null explicitly.
After working with Spring Integration and kotlin these 8 months I decided to try to create Kotlin DSL for Spring Integration (https://github.com/spring-projects/spring-integration/issues/3016)
Kotlin Unit: https://kotlinlang.org/docs/reference/functions.html#unit-returning-functions
P.S.: Of course I solved it earlier, not after 8 months.
Related
I am trying to test a Spring Integration flow that starts off from a message-driven-channel-adapter configured as:
<int-jms:message-driven-channel-adapter id="myAdapter" ... />
My test goes like:
#SpringJUnitConfig(locations = {"my-app-context.xml"})
#SpringIntegrationTest(noAutoStartup = {"myAdapter"})
public class MyIntegrationFlowTest {
#Autowired
private MockIntegrationContext mockIntegrationContext;
#Test
public void myTest() {
...
MessageSource<String> messageSource = () -> new GenericMessage<>("foo");
mockIntegrationContext.substituteMessageSourceFor("myAdapter", messageSource);
...
}
}
I am however getting the following error:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'myAdapter' is expected to be of type 'org.springframework.integration.endpoint.SourcePollingChannelAdapter' but was actually of type 'org.springframework.integration.jms.JmsMessageDrivenEndpoint'
How should one specify an alternate source for the channel adapter for testing using the MockIntegrationContext, or by some other method?
The Message Driver Channel Adapter is really not a Source Polling Channel Adapter. So, the substituteMessageSourceFor() is indeed cannot be used for that type of components, which, essentially is a MessageProducerSupport implementation, not a SourcePollingChannelAdapter for a MessageSource.
The difference exists because not all protocols provides a listener-like hooks to spawn some self-managed task to subscribe to. The good example is JDBC, which is only passive system expecting requests. Therefore a polling channel adapter with a JdbcPollingChannelAdapter (which is a MessageSource) implementation must be used to interact with DB in event-driven manner.
Other systems (like JMS in your case) provides some listener (or consumer) API for what we can spawn a while task (see MessageListenerContainer in spring-jms) and let its MessageProducerSupport to emit messages to the channel.
Therefore you need to distinguish for yourself with what type of component you interact before choosing a testing strategy.
Since there is no extra layer in case of message-driver channel adapter, but rather some specific, self-managed MessageProducerSupport impl, we not only provide a particular mocking API, but even don't require to know anything else, but just standard unit testing feature and a message channel this endpoint is producing in the configuration.
So, the solution for you is something like:
#SpringIntegrationTest(noAutoStartup = {"myAdapter"}) - that's fully correct in your code: we really have to stop the real channel adapter to not pollute our testing environment.
You just need to inject into your test class a MessageChannel that id="myAdapter" is producing to. In your test code you just build a Message and send it into this channel. No need to worry about a MockIntegrationContext at all.
My beans/listeners are built using IntegrationFlows
ex.
#Bean
IntegrationFlow registerDevices() {
return IntegrationFlows
.from(adapter)
.channel(channel)
.get();
}
there is such a way to stop listeners after something happens, endpoint call/event
RabbitListenerEndpointRegistry does not work, because I'm not using #RabbitListener
There is the possibility to set autostart up - false, but then how to manipulate it nicely during runtime?
First of all, please, be careful when you choose tags for your questions over here. You concern so far really belongs to Spring Integration and has nothing to do with Spring AMQP. Although you are right: the lifecycle control really goes in the end to the ListenerContainer from Spring AMQP.
Anyway: the end-user API for your use-case is a part of Spring Integration logic.
See the second argument of that from():
.from(adapter, e -> e.id("myAmqpAdapter"))
So, having that id yo are able to reach your AmqpInboundChannelAdapter (implement a Lifecycle) at runtime and stop & start it whenever you need.
See more in docs:
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-endpoints
And this pattern is also good to have on board:
https://docs.spring.io/spring-integration/docs/current/reference/html/system-management.html#control-bus
I am trying to upgrade the spring integration flow in one of my existing application with Reactive Streams Support. The approach taken is to change the parameter and return type of the Gateway method as Mono. The flow executes smoothly and when the reply reaches the Gateway, it results in java.lang.IllegalArgumentException: 'beanFactory' must not be null
I am using Spring Boot 2.3.0.
inputChannel is a DirectChannel
gatewayReplyChannel is a FluxMessageChannel
#MessagingGateway(name = "reactiveGateway")
public interface EntryGate {
#Gateway(requestChannel = "inputChannel", replyChannel = "gatewayReplyChannel")
Mono<String> process(final Mono<String> input);
}
Exceptions trace
Caused by: java.lang.IllegalArgumentException: 'beanFactory' must not be null
at org.springframework.util.Assert.notNull(Assert.java:198)
at org.springframework.integration.channel.ChannelUtils.getErrorHandler(ChannelUtils.java:51)
at org.springframework.integration.endpoint.ReactiveStreamsConsumer.onInit(ReactiveStreamsConsumer.java:155)
at org.springframework.integration.context.IntegrationObjectSupport.afterPropertiesSet(IntegrationObjectSupport.java:214)
at org.springframework.integration.gateway.MessagingGatewaySupport.registerReplyMessageCorrelatorIfNecessary(MessagingGatewaySupport.java:806)
at org.springframework.integration.gateway.MessagingGatewaySupport.sendAndReceiveMessageReactive(MessagingGatewaySupport.java:609)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.sendOrSendAndReceive(GatewayProxyFactoryBean.java:639)
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:573)
Can anyone help?
The MessagingGatewaySupport.registerReplyMessageCorrelatorIfNecessary() has to be fixed to call endpoint.setBeanFactory(beanFactory); for the ReactiveStreamsConsumer. Apparently we just don't have a test case to cover the replyChannel as a FluxMessageChannel.
Consider to not use that replyChannel at all as a workaround. It is really doesn't matter for this kind of flow: it is already reactive by the Mono return type. No reason to shift to other reactive stream internally for the reply correlation.
I have set up a simple Spring Integration flow which is composed of such steps:
poll a rest api periodically then
do some processing on the payload
and land it on a Kafka topic.
Please observe the code below:
#Component
public class MyIntegrationFlow extends IntegrationFlowAdapter {
#Override
protected IntegrationFlowDefinition<?> buildFlow() {
return from(() -> List.of("pathVariable1", "pathVariable2"), c -> c.poller(Pollers.fixedDelay(5, TimeUnit.SECONDS)))
.split()
.handle(httpRequest(), c -> c.advice(new RequestHandlerRetryAdvice()))
.transform(Tranformers.fromJson(Foo.class))
.filter(payload -> payload.isValid())
.log()
.transform(Tranformers.toJson())
.channel(Source.OUTPUT); // output channel for kafka topic
}
private HttpMessageHandlerSpec httpRequest() {
return Http.outboundGateway("http://somehost:8080/{pathVariable}")
.httpMethod(GET)
.uriVariable("pathVariable", Message::getPayload)
.expectedResponseType(String.class);
}
}
This works brilliantly, however, I am struggling to come up with some good tests.
How am I supposed to mock the external REST API?
How am I supposed to test that the retry policy does kick in and the desired number of http requests are made?
How do I change the MessageSource of the flow (list of path vars) that is polled periodically?
How do I check if the payload has successfully made it to the Kafka topic?
Too much questions and some of them requires too broad explanation. Anyway I think you can start from Spring Integration Testing Framework and its documentation.
How am I supposed to mock the external REST API?
I think you can just consider to use a Mock MVC from Spring Framework and its MockMvcClientHttpRequestFactory to inject into the HttpRequestExecutingMessageHandler based on the HttpMessageHandlerSpec.
retry policy does kick
Well, I guess the same mocked MVC endpoint can verify how many times it has been called and fail for first several times to initiate that retry.
How do I change the MessageSource
This is exactly a part of Spring Integration Testing Framework with its MockIntegration.mockMessageSource() and MockIntegrationContext: https://docs.spring.io/spring-integration/docs/5.1.6.RELEASE/reference/html/#mockintegration
made it to the Kafka topic?
Or you the mentioned MockIntegration.mockMessageHandler() to verify that endpoint for Kafka is called. Or use an Embedded Kafka from Spring Kafka project: https://docs.spring.io/spring-kafka/docs/2.2.7.RELEASE/reference/html/#embedded-kafka-annotation
I need to throttle the movement of messages between some JMS (activeMQ) queues to ensure I dont overrun an external service used during message processing.
I have done this in the past with Camel but given that this project is otherwise entirely Spring based, I figured I'd give spring-integration a whirl.
I am happy to see that in 5.0.7 the Java DSL is in core and would really like to use it instead of xml.
But...I cant seem to find good/current docs for using the DSL to do even simple things like create the input and output messageChannels for JMS.
Could anyone point me to any current example of using the java DSL to create channels that I can use to consume and produce messages with...and then later use in a bridge with some throttling applied?
Well, looks like our JMS chapter in the Reference Manual leaks of the Java DSL samples, similar to what we have so far with AMQP, for example:
https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/amqp.html#_configuring_with_the_java_dsl_2
https://docs.spring.io/spring-integration/docs/5.0.7.RELEASE/reference/html/amqp.html#_configuring_with_the_java_dsl_4
I believe we can add similar paragraphs into the JMS chapter as well. Please, raise a JIRA on the matter and we will address it soon. Meanwhile I suggest you to open a org.springframework.integration.jms.dsl.Jms factory for appropriate builder to use.
On the other hand I can suggest you to take a look into the existing test-case of some possible configurations: https://github.com/spring-projects/spring-integration/blob/master/spring-integration-jms/src/test/java/org/springframework/integration/jms/dsl/JmsTests.java
For example to read from the queue you need a configuration like this:
#Bean
public IntegrationFlow jmsMessageDrivenFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory(), DefaultMessageListenerContainer.class)
.outputChannel(jmsMessageDrivenInputChannel())
.destination("jmsMessageDriven")
.configureListenerContainer(c -> c.clientId("foo")))
.<String, String>transform(String::toLowerCase)
.channel(jmsOutboundInboundReplyChannel())
.get();
}
To send to the JMS you need something like this:
#Bean
public IntegrationFlow jmsOutboundFlow() {
return f -> f
.handle(Jms.outboundAdapter(jmsConnectionFactory())
.destinationExpression("headers." + SimpMessageHeaderAccessor.DESTINATION_HEADER)
.configureJmsTemplate(t -> t.id("jmsOutboundFlowTemplate")));
}