EnpointSpec fails in Spring Integration Java DSL - spring-integration

#SpringBootApplication
#EnableIntegration
public class SpringIntegrationHttpApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIntegrationHttpApplication.class, args);
}
#Bean
public HttpRequestHandlerEndpointSpec httpInboundAdapter() {
return Http
.inboundChannelAdapter("/failing-test")
.requestMapping(r -> r.methods(HttpMethod.GET)
.params("rObjectId"))
.payloadExpression("#requestParams.rObjectId[0]")
;
}
#Bean
public IntegrationFlow myFlow() {
return IntegrationFlows.from(Http
.inboundChannelAdapter("/test")
.requestMapping(r -> r.methods(HttpMethod.GET)
.params("rObjectId"))
.payloadExpression("#requestParams.rObjectId[0]"))
.transform(p -> p)
.handle(p -> System.out.println(p))
.get();
}
#Bean
public IntegrationFlow yourFlow() {
return IntegrationFlows.from(httpInboundAdapter())
.transform(p -> p)
.handle(p -> System.out.println(p))
.get();
}
}
For the above code, the link: /test works but not /failing-test.
I get "Endpoint is stopped" on chrome.
What might be the reason?

I am not sure why, yet, but remove the #Bean from the spec (just use a simple method that returns the Spec).

Related

Why is my timeToLiveFunction never called?

I have the following flow:
#Bean
public IntegrationFlow workerInfoJmsOut(ConnectionFactory connectionFactory, WorkerProperties properties) {
return IntegrationFlows
.from(ChannelNames.WORKER_INFO)
.handle(Jms.outboundAdapter(connectionFactory)
.timeToLiveFunction(message -> properties.getHeartbeatTtl().toMillis())
.destination(JmsQueueNames.WORKER_INFO))
.get();
}
And the following gateway:
#MessagingGateway
public interface DispatcherGateway {
#Gateway(requestChannel = ChannelNames.WORKER_INFO)
void submit(WorkerMessage message);
}
Which is called every 1s:
#Scheduled(fixedDelay = 1000)
public void beat() {
dispatcherGateway.submit(WorkerInfoNotice.of(...));
}
However, the breakpoint in the timeToLiveFunction is never hit:
Did I misunderstand something or why is it never hit?
spring-integration-jms:5.3.1.RELEASE
I found out that I need to enable explicit QoS
#Bean
public IntegrationFlow workerInfoJmsOut(ConnectionFactory connectionFactory, WorkerProperties properties) {
return IntegrationFlows
.from(ChannelNames.WORKER_INFO)
.handle(Jms.outboundAdapter(connectionFactory)
.configureJmsTemplate(spec -> spec.explicitQosEnabled(true))
.timeToLiveFunction(message -> properties.getHeartbeatTtl().toMillis())
.destination(JmsQueueNames.WORKER_INFO))
.get();
}

publishSubscribeChannel unit test can't work well

my integration config class is below,when i do some unit test on them,found that:
when i send message to UserRecipientSubscribeCacheChannel,it work well;
when i send a message to an upper level of channel userReportWriteCompletedRouteChannel, it work failed,and it don't throws any exceptions yet. i can't understand it. the messages that i sent is same,of course.
because of the fail section, the next handler can't work ok.
ty!!
it work ok below, it print ===>ip location channel message:GenericMessage [payload=[MailRecipientActionDocumen...and ===>user recipient channel message:GenericMessage [payload=[UserRecipientSubscribeDataRedisStructure...
#Test
public void test_sendMessageUserRecipientSubscribeCacheChannel(){
UserRecipientSubscribeCacheChannel.send(createMessageWithIp());
}
it work fail below, it print ===>ip location channel message:GenericMessage [payload=[MailRecipientActionDocumen... only
notice that: the fail section, In front of handler has a transformer.
#Test
public void test_sendMessageToRouteChannel() {
userReportWriteCompletedRouteChannel.send(createMessageWithIp());
}
my code config below:
#Bean
public SubscribableChannel userReportWriteCompletedSubscribeChannel() {
return new DirectChannel();
}
#Bean
public QueueChannel userReportWriteCompletedRouteChannel() {
return new QueueChannel();
}
#Bean
public MessageChannel ipLocationResolveCacheChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel userRecipientSubscribeCacheChannel() {
return new DirectChannel();
}
#MessagingGateway(name = "userReportWriteCompletedListener",
defaultRequestChannel = "userReportWriteCompletedRouteChannel")
public interface UserReportWriteCompletedListener {
#Gateway
void receive(List<UserMailRecipientActionDocument> docs);
}
#Bean
public IntegrationFlow bridgeFlow() {
return flow -> flow.channel("userReportWriteCompletedRouteChannel")
.bridge(bridgeSpe -> bridgeSpe
.poller(pollerFactory -> pollerFactory.fixedRate(500).maxMessagesPerPoll(1)))
.channel("userReportWriteCompletedSubscribeChannel")
;
}
#Bean
public IntegrationFlow subscribeFlow() {
return IntegrationFlows.from("userReportWriteCompletedSubscribeChannel")
.publishSubscribeChannel(publishSubscribeSpec -> publishSubscribeSpec
.subscribe(flow -> flow
.channel(IP_LOCATION_RESOLVE_CACHE_CHANNEL)
)
.subscribe(flow -> flow
.channel(USER_RECIPIENT_SUBSCRIBE_CACHE_CHANNEL)
))
.get();
}
#Bean
public RedisStoreWritingMessageHandler ipLocationResolveCacheHandler(RedisTemplate<String, ?> redisTemplate) {
final RedisStoreWritingMessageHandler ipLocationResolveCacheHandler =
new RedisStoreWritingMessageHandler(redisTemplate);
ipLocationResolveCacheHandler.setKey("IP_LOCATION_RESOLVE_CACHE");
return ipLocationResolveCacheHandler;
}
#Bean
public RedisStoreWritingMessageHandler userRecipientSubscribeCacheHandler(RedisTemplate<String, ?> redisTemplate) {
final RedisStoreWritingMessageHandler userRecipientSubscribeCacheHandler =
new RedisStoreWritingMessageHandler(redisTemplate);
userRecipientSubscribeCacheHandler.setKey("USER_RECIPIENT_SUBSCRIBE_CACHE");
return userRecipientSubscribeCacheHandler;
}
#Bean
public IpLocationResolveRedisStructureFilterAndTransformer recipientActionHasIpFilterAndTransformer() {
return new IpLocationResolveRedisStructureFilterAndTransformer();
}
#Bean
public UserRecipientSubscribeDataRedisStructureTransformer subscribeDataRedisStructureTransformer(
IpLocationClient ipLocationClient) {
return new UserRecipientSubscribeDataRedisStructureTransformer(ipLocationClient);
}
#Bean
public IntegrationFlow ipLocationResolveCacheFlow(
#Qualifier("ipLocationResolveCacheHandler") RedisStoreWritingMessageHandler writingMessageHandler) {
return flow -> flow.channel(IP_LOCATION_RESOLVE_CACHE_CHANNEL)
.handle(message -> {
System.out.println("===>ip location channel message:" + message);
})
;
}
#Bean
public IntegrationFlow userRecipientActionDataCacheFlow(
#Qualifier("userRecipientSubscribeCacheHandler") RedisStoreWritingMessageHandler messageHandler,
UserRecipientSubscribeDataRedisStructureTransformer transformer) {
return flow -> flow.channel(USER_RECIPIENT_SUBSCRIBE_CACHE_CHANNEL)
.transform(transformer)
.handle(message -> {
System.out.println("===>user recipient channel message:" + message);
})
}
i expect 2 print message info ,but print 1 only.
Today, i found that the bridge flow may had some problem, When I move the handler behind the channeluserReportWriteCompletedSubscribeChannel, it can't print any message;
when i remove channel and add handler directly, it will print message.
does i use the bridge wrong?
#Bean
public IntegrationFlow bridgeFlow() {
return flow -> flow.channel("userReportWriteCompletedRouteChannel")
.bridge(bridgeSpe -> bridgeSpe
.poller(pollerFactory -> pollerFactory.fixedRate(100).maxMessagesPerPoll(1)))
.handle(message -> {
System.out.println("===>route channel message:" + message);
}) // handle ok , will print message
.channel("userReportWriteCompletedSubscribeChannel")
// .handle(message -> {
// System.out.println("===>route channel message:" + message);
// }) // handle fail , will not printing message
;
}
test:
#Test
//invalid
public void test_sendMessageToRouteChannel() {
userReportWriteCompletedRouteChannel.send(createMessageWithIp());
}

Async split/aggregate gateway flows

I'm trying to build a recipe for asynchronous orchestration using spring integration gateways (both inbound and outbound). After seeing an example here, I tried using scatter-gather like this:
#Configuration
public class IntegrationComponents {
#Value("${rest.endpoint.base}")
private String endpointBase;
#Bean
public HttpRequestHandlingMessagingGateway inboundGateway() {
return Http.inboundGateway("/test-inbound-gateway-resource")
.requestMapping(mapping -> mapping.methods(HttpMethod.POST))
.requestTimeout(3000)
.replyTimeout(3000)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway1() {
return Http.outboundGateway(endpointBase + "/test-resource-1")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway2() {
return Http.outboundGateway(endpointBase + "/test-resource-2")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public StandardIntegrationFlow integrationFlow() {
ExecutorService executor = Executors.newCachedThreadPool();
IntegrationFlow flow1 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway1())
.get();
IntegrationFlow flow2 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway2())
.get();
return IntegrationFlows
.from(inboundGateway())
.transform(String.class, String::toUpperCase)
.channel(MessageChannels.executor(executor))
.scatterGather(
scatterer -> scatterer
.applySequence(true)
.recipientFlow(flow1)
.recipientFlow(flow2),
gatherer -> gatherer
.outputProcessor(messageGroup -> {
List<Message<?>> list = new ArrayList<>(messageGroup.getMessages());
String payload1 = (String) list.get(0).getPayload();
String payload2 = (String) list.get(1).getPayload();
return MessageBuilder.withPayload(payload1 + "+" + payload2).build();
}))
.get();
}
}
This executes, but my payloads are swapped, because in this case outboundGateway1 takes longer to execute than outboundGateway2. Payload 2 comes first, then payload 1.
Is there a way to tell scatter-gather to define/maintain order when sending to the output processor?
On a similar note, maybe split/aggregate and/or using a router is a better pattern here? But if so, what would that look like?
I tried the following split/route/aggregate, but it failed saying "The 'currentComponent' (org.springframework.integration.router.RecipientListRouter#b016b4e) is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'. This is the end of the integration flow.":
#Configuration
public class IntegrationComponents {
#Value("${rest.endpoint.base}")
private String endpointBase;
#Bean
public HttpRequestHandlingMessagingGateway inboundGateway() {
return Http.inboundGateway("/test-inbound-gateway-resource")
.requestMapping(mapping -> mapping.methods(HttpMethod.POST))
.requestTimeout(3000)
.replyTimeout(3000)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway1() {
return Http.outboundGateway(endpointBase + "/test-resource-1")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public HttpRequestExecutingMessageHandler outboundGateway2() {
return Http.outboundGateway(endpointBase + "/test-resource-2")
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.get();
}
#Bean
public StandardIntegrationFlow integrationFlow() {
ExecutorService executor = Executors.newCachedThreadPool();
IntegrationFlow flow1 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway1())
.get();
IntegrationFlow flow2 = IntegrationFlows.from(MessageChannels.executor(executor))
.handle(outboundGateway2())
.get();
return IntegrationFlows
.from(inboundGateway())
.transform(String.class, String::toUpperCase)
.split()
.channel(MessageChannels.executor(executor))
.routeToRecipients(r -> r
.recipientFlow(flow1)
.recipientFlow(flow2))
.aggregate()
.get();
}
}
Can you not simply Collections.sort() the list in the output processor? Each message will have a IntegrationMessageHeaderAccessor.SEQUENCE_NUMBER header since you set applySequence.

Breaking up DSL IntegrationFlows

I've been playing around with Spring Integration (SI) DSL. I have a Rest service with the following Async Gateway defined :
#MessagingGateway
public interface Provision {
#Async
#Gateway(requestChannel = "provision.input")
ListenableFuture<List<ResultDto>> provision(List<ItemsDto> stuff);
}
From the Line-by-line walk-through I have the follow example IntegrationFlow.
#Bean
public IntegrationFlow provision() {
return f -> f
.split(ArrayList.class, List::toArray)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<ItemsDto, String>route(ItemsDto::getType, m -> m
.subFlowMapping("IPTV", sf -> sf
.<ItemsDto, String>route(ItemsDto::getAction, m2 -> m2
.subFlowMapping("OPEN", sf2 -> sf2
.handle((p, h) -> iptvService.open((ItemsDto) p))))
)
)
.aggregate();
}
I have several layers of routing as you can see. I need to break things up a bit. I've tried several things which don't work (here I don't get a response...the thread doesn't wait):
#Bean(name = "routerInput")
private MessageChannel routerInput() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow provision() {
return f -> f
.split(ArrayList.class, List::toArray)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<ItemsDto, String>route(ItemsDto::getType, m ->
m.subFlowMapping("IPTV", sf -> sf.channel("routerInput"))
)
.aggregate();
}
#Bean
public IntegrationFlow action() {
return IntegrationFlows.from("routerInput")
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.subFlowMapping("OPEN", sf -> sf
.handle(p -> iptvService.open((ItemsDto) p.getPayload())))).get();
}
I'm obviously conceptually missing something :) Can someone perhaps assist with a "how to and why" opinion?
I have a list of items that need to be split, routed by "type", then routed by "action", and finally aggregated (containing response of handler). Each handled item needs to process in parallel.
Thanks in advance
Update:
From Artem's suggestion I removed all the async stuff. I trimmed it down to almost nothing...
#Bean(name = "routerInput")
private MessageChannel routerInput() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow provision() {
return f -> f
.split()
.<ItemDto, String>route(ItemDto::getType, m ->
m.subFlowMapping("IPTV", sf -> sf.channel("routerInput")))
.aggregate();
}
#Bean
public IntegrationFlow action() {
return IntegrationFlows.from("routerInput")
.<ItemDto, String>route(ItemDto::getAction, m -> m
.subFlowMapping("OPEN", sf -> sf
.handle((p, h) -> iptvService.open((ItemDto) p)))).get();
}
I got it to respond by changing
.handle(p ->
to this
.handle((p, h) ->
So it at least responds, but it does not aggregate the 3 test items that were split. Output consists of 1 item. Do I need to use a stream collect? Release policy? Shouldn't this be fine?
It's probably simpler to use channelMapping rather than subflowMapping if you want to break it apart...
#Bean
public IntegrationFlow typeRoute() {
return IntegrationFlows.from(foo())
.split()
.<ItemsDto, String>route(ItemsDto::getType, m -> m
.channelMapping("foo", "channel1")
.channelMapping("bar", "channel2"))
.get();
}
#Bean
public IntegrationFlow fooActionRoute() {
return IntegrationFlows.from(channel1())
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.channelMapping("foo", "channel3")
.channelMapping("bar", "channel4"))
.get();
}
#Bean
public IntegrationFlow barActionRoute() {
return IntegrationFlows.from(channel1())
.<ItemsDto, String>route(ItemsDto::getAction, m -> m
.channelMapping("foo", "channel5")
.channelMapping("bar", "channel6"))
.get();
}
#Bean
public IntegrationFlow fooFooHandle() {
return IntegrationFlows.from(channel3())
// handle
.channel(aggChannel())
.get();
}
Create flows for the other options and aggregate each result:
// fooBarHandle(), barFooHandle(), barBarHandle()
#Bean IntegrationFlow agg() {
return IntegrationFlows.from(aggChannel())
.aggregate()
.get();
}
The parallelism is handled by using ExecutorChannels...
#Bean
public MessageChannel channel1() {
return new ExecutorChannel(exec());
}
#Bean
public MessageChannel channel2() {
return new ExecutorChannel(exec());
}
#Bean
public MessageChannel channel3() {
return new DirectChannel();
}
#Bean
public MessageChannel channel4() {
return new DirectChannel();
}
#Bean
public MessageChannel channel5() {
return new DirectChannel();
}
#Bean
public MessageChannel channel6() {
return new DirectChannel();
}
#Bean
public MessageChannel aggChannel() {
return new DirectChannel();
}
I don't see any big problems in your configuration and it really should work.
Want I don't like there is:
If you use async Gateway (ListenableFuture<List<ResultDto>>) you don't need #Async annotation there, because it will be already by the Gateway contract.
You don't need convert List::toArray if your payload is a List already. It is just enough to use .split() without params.
That's for the design style.
I'm not sure yet what's the problem there, but would you mind to make your whole flow sync and share with us here the DEBUG for the flow and point out where you see the problem.
Moved the aggregate to the "action" Bean and it worked.
Thanks for your patience :)

How to wire tap a message channel in java dsl?

I am trying Java dsl in spring integration. I am trying to wiretap a channel. But getting error,
#ContextConfiguration
#EnableIntegration
#IntegrationComponentScan
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(DemoApplication.class);
CustomGtwy service = ctx.getBean(CustomGtwy.class);
service.pushMessage("Manoj");
}
#Bean
public MessageChannel loggerChannel(){
return MessageChannels.direct().get();
}
#Bean
public MessageChannel pushAssetIdChannel() {
return MessageChannels.direct()
.interceptor(new WireTap(loggerChannel()))
.get();
}
#Bean
public IntegrationFlow pushAssetIdFlow() {
return IntegrationFlows.from("pushAssetIdChannel")
.handle(new GenericHandler() {
#Override
public String handle(Object arg0, Map arg1) {
// TODO Auto-generated method stub
return "Success";
}})
.get();
}
#MessagingGateway
public interface CustomGtwy{
#Gateway(requestChannel="pushAssetIdChannel")
String pushMessage(String s);
}
#Bean
public IntegrationFlow logger(){
return IntegrationFlows.from("loggerChannel").handle(new GenericHandler() {
#Override
public String handle(Object arg0, Map arg1) {
// TODO Auto-generated method stub
return "Success";
}}).channel("nullChannel").get();
}
}
In the above code, if i try to put message in the pushAssetIdChannel, i am getting Dispatcher has no subscribers for channel 'unknown.channel.name'
If the interceptor is not there, it is working.
Not sure what's going on with your case, but that works for me with the latest 1.0.2 version:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext
public class SO31348246Tests {
#Autowired
private MessageChannel pushAssetIdChannel;
#Test
public void testIt() {
this.pushAssetIdChannel.send(new GenericMessage<>("foo"));
}
#Configuration
#EnableIntegration
public static class ContextConfiguration {
#Bean
public MessageChannel loggerChannel() {
return MessageChannels.direct().get();
}
#Bean
public MessageChannel pushAssetIdChannel() {
return MessageChannels.direct()
.interceptor(new WireTap(loggerChannel()))
.get();
}
#Bean
public IntegrationFlow pushAssetIdFlow() {
return IntegrationFlows.from("pushAssetIdChannel")
.handle(System.out::println)
.get();
}
#Bean
public IntegrationFlow logger() {
return IntegrationFlows.from("loggerChannel")
.handle((p, h) -> {
System.out.println(p);
return p;
})
.channel("nullChannel")
.get();
}
}
}
I see both SOUTs in logs.
UPDATE
Your class works for me after fixing #ContextConfiguration to the normal #Configuration annotation :-).
Without the last one the framework considers your DemoApplication as lite configuration, just because you have there #Bean methods, but it does not do it like the full one and doesn't proxy it to allow to use bean method reference like you do with loggerChannel() in the WireTap constructor.
So, with lite we jsut invoke that method and get a fresh MessageChannel object, but it isn't a bean in the application context. That's why you end up with the Dispatcher has no subscribers.

Resources