How to register the integration flows in runtime? - spring-integration

I'm building a micro service for multiple properties. So, each property has different configuration. To do that, I've implemented something like this;
#Autowired
IntegrationFlowContext flowContext;
#Bean
public void setFlowContext() {
List<Login> loginList = DAO.getLoginList(); // a web service
loginList.forEach(e -> {
IntegrationFlow flow = IntegrationFlows.from(() -> e, c -> c.poller(Pollers.fixedRate(e.getPeriod(), TimeUnit.SECONDS, 5)))
.channel("X_CHANNEL")
.get();
flowContext.registration(flow).register();
});
}
By this implementation, I'm getting the loginList before application started. So, after application is started, I'm not able to get loginList from web service since there is no poller config. The problem is loginList could change; new logins credentials could be added or deleted. Therefore, I want to implement something will work X time period to get loginList from web service, then, by loginList I need to register the flows that are created for each loginList. To achieve, I've implemented something like this;
#Bean
public IntegrationFlow setFlowContext() {
return IntegrationFlows
.from(this::getSpecification, p -> p.poller(Pollers.fixedRate(X))) // the specification is constant.
.transform(payload -> DAO.getLoginList(payload))
.split()
.<Login>handle((payload, header) -> {
IntegrationFlow flow = IntegrationFlows.from(() -> payload, c -> c.poller(Pollers.fixedRate(payload.getPeriod(), TimeUnit.SECONDS, 5)))
.channel("X_CHANNEL")
.get();
flowContext.registration(flow).register().start();
return null;
})
.get();
}
Basically, I've used start() method, but this is not working as aspected. See this;
flowContext.registration(flow).register().start();
Lastly, I've read the Dynamic and Runtime Integration Flows, but still couldn't implement this feature.

Dynamic flow registration cannot be used within a #Bean definition.
It is designed to be used at runtime AFTER the application context is fully initialized.

Related

Spring Integration DSL: Simple way to send some messages to a Flow inputChannel

If I want to generate some sample data for testing purposes of the Spring Integration DSL functionality, one way I have come up with so far is like this:
#Bean
public IntegrationFlow myFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("numbers").get())
.scatterGather(s -> s
.applySequence(true)
.recipientFlow(f -> f.handle((a, b) -> Arrays.asList(1,2,3,4,5,6,7,8,9,10)))
)
.split() // unpack the wrapped list
.split() // unpack the elements of the list
.log()
.get();
}
Is there another/better way to do the same thing ? Using the Scatter-Gather EIP seems like overkill for something so basic...
You really can inject a specific MessageChannel from your flow to some testing component to send messages directly to that channel to be processed from the expecting endpoint in the flow. It is not clear what that scatter-gather does in your flow and where is the channel you talk in the title , but another way is to use a #MessagingGateway interface .
See more in docs:
https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#gateway
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-channels
Here's another way to get some test data injected into a flow:
#Bean
IntegrationFlow pollingFlow() {
return IntegrationFlow
.from(() -> new GenericMessage<>(List.of(1,2,3)),
e -> e.poller(Pollers.fixedRate(Duration.ofSeconds(1))))
// do something here ever second
.get();
}
I found a simpler way of doing the same thing using .transform() instead of .scatterGather()
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("test").get())
.transform(t -> Arrays.asList(1,2,3,4,5,6,7,8,9,10))
.split() // unpack the wrapped list
.split() // unpack the elements of the list
.get();
}
And here's another way you could do it using .gateway()
#Bean
public IntegrationFlow testFlow() {
return IntegrationFlows
.from(Http.inboundChannelAdapter("test").get())
.gateway(f -> f.handle((a, b) -> Arrays.asList(1,2,3,4,5,6,7,8,9,10)))
.split() // unpack the elements of the list
.get();
}
And yet another:
#Bean
IntegrationFlow fromSupplier() {
return IntegrationFlow.fromSupplier(() -> List.of(1,2,3,4),
s -> s.poller(p -> p.fixedDelay(1, TimeUnit.DAYS)))
.log(Message::getPayload)
.nullChannel();
}

Is it possible to configure poller for each entity from a data source?

I'm developing a multi property micro service by spring integration. I'm getting each property's login credentials from database like LOGIN table. LOGIN table has these fields; LOGIN.username, LOGIN.pass and LOGIN.period(poller's period). If I want to make work the micro service with different poller configurations based on LOGIN.period field, how can I do that?
#Bean
public IntegrationFlow start() {
return IntegrationFlows
.from(() -> DAO.getLoginList()) // from a web service.
.split() // splits the each login credentials for each property.
.channel("X_CHANNEL") // subscribes to a channel todo business logic.
.get();
}
Is it possible to implement a component to make work flow in different poller configurations based on the LOGIN.period value from database?
Based on the Artem Bilan's answer, I've implement the IntegrationFlowContext and IntegrationFlow instances;
#Autowired
IntegrationFlowContext flowContext;
#Bean
public void setFlowContext() {
List<Login> loginList = DAO.getLoginList(); // a web service
loginList.forEach(e -> {
IntegrationFlow flow = IntegrationFlows.from(() -> e, c -> c.poller(Pollers.fixedRate(e.getPeriod(), TimeUnit.SECONDS, 5)))
.channel("X_CHANNEL")
.get();
flowContext.registration(flow).register();
});
}
Show, please, how you get that info from the data base.
But if your point that you may have several records in DB and you want to have several pollers for all of them, then you need to take a look dynamic flow registrations: https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/dsl.html#java-dsl-runtime-flows
So, you read data from the DB, create IntegrationFlow in the loop for every record and configure their pollers according the data from the record.

What is the proper way of using bridge endpointConfigurer?

I'm looking for a component about how to connect different integration flows by root flow. I've seen this tutorial(see 5.2. Bridge); it has one main root flow, then two different flows. I've tried this in my application, but it didn't work without putting PollerMetadata.DEFAULT_POLLER. Gives an error: no default poller is available within the context. When I add PollerMetadata.DEFAULT_POLLER, times in bridge endpointConfigurer is not working as expected. Probably, It clashes by default poller configuration.
In short, how can I connect two different flows by one main root? But, different flows has to work in different times.
I don't know that I'm using right component or not, any help will be appreciated. Thank you.
In addition, I've seen this question which is kind a similar. It could help to understand my question.
UPDATE
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller() {
return Pollers.fixedRate(60, TimeUnit.SECONDS, 10).get();
}
#Bean
public IntegrationFlow fileReader() {
return IntegrationFlows
.from(sourceDirectory())
.split()
.publishSubscribeChannel(c -> c
.subscribe("fileWriter"))
.publishSubscribeChannel(c -> c
.subscribe("anotherFileWriter"))
.get();
}
#Bean
public IntegrationFlow fileWriter() {
return IntegrationFlows
.from("fileWriter")
.bridge(e -> e.poller(10, TimeUnit.SECONDS, 5))
.handle()
.get();
}
#Bean
public IntegrationFlow anotherFileWriter() {
return IntegrationFlows
.from("anotherFileWriter")
.bridge(e -> e.poller(20, TimeUnit.SECONDS, 5))
.handle()
.get();
}
You should show your code when asking questions like this.
It's not entirely clear what you mean by
In short, how can I connect two different flows by one main root?
If they both consume from the same pollable channel each message will only go to one or the other flow.
To specifically answer your question, the example you pointed to shows how to configure a specific poller on the endpoint...
>.bridge(e -> e.poller(Pollers.fixedRate(1, TimeUnit.SECONDS, 20)))
You only need a default poller if you omit the poller on the endpoint.

Spring Integration: reuse MessageProducer definition

I have an outbound gateway for soap calls (MarshallingWebServiceOutboundGateway) with elaborate setup. I need to use that gateway definition from multiple flows.
The question spring-integration: MessageProducer may only be referenced once is somewhat similar, but this question is about the proper use of the spring bean scope prototype for spring integration collaborators.
I have a separate config file which sets up the gateway and its dependencies:
#Bean
public MarshallingWebServiceOutboundGateway myServiceGateway() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("blah.*");
MarshallingWebServiceOutboundGateway gateway = new MarshallingWebServiceOutboundGateway(
serviceEndpoint, marshaller, messageFactory);
gateway.setMessageSender(messageSender);
gateway.setRequestCallback(messageCallback);
return gateway;
}
This is how I initially tried to wire up the outbound gateway from two different flows in two different config files.
In one config file:
#Bean
public IntegrationFlow flow1() {
MarshallingWebServiceOutboundGateway myServiceGateway = context.getBean("myServiceGateway", MarshallingWebServiceOutboundGateway.class);
return IntegrationFlows
.from(Http.inboundGateway("/res1")
.requestMapping(r -> r.methods(HttpMethod.GET))
.transform(soapRequestTransformer)
.handle(myServiceGateway) // wrong: cannot be same bean
.transform(widgetTransformer)
.get();
}
In a separate config file:
#Bean
public IntegrationFlow flow2() {
MarshallingWebServiceOutboundGateway myServiceGateway = context.getBean("myServiceGateway", MarshallingWebServiceOutboundGateway.class);
return IntegrationFlows
.from(Http.inboundGateway("/res2")
.requestMapping(r -> r.methods(HttpMethod.GET))
.transform(soapRequestTransformer)
.handle(myServiceGateway) // wrong: cannot be same bean
.transform(widgetTransformer)
.handle(servicePojo)
.get();
}
This is a problem because - as I understand it - myServiceGateway cannot be the same instance, since that instance has only one outbound channel and cannot belong to two different flows.
In the related question spring-integration: MessageProducer may only be referenced once, #artem-bilan advised not to create the outbound gateway in an #Bean method, rather to use a plain method which creates new instances for every call.
That works, but it is inconvenient in my case. I need to reuse the outbound gateway from several flows in different config files and I would have to copy the code to create the gateway into each config file. Also, the gateway dependencies inflate my Configuration file constructors, making Sonar bail.
Since the error message coming out of IntegrationFlowDefinition.checkReuse() says A reply MessageProducer may only be referenced once (myServiceGateway) - use #Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) on #Bean definition. I wanted to give the scope prototype another try.
So I try to make spring integration look up a prototype gateway from the context by name, hoping to get a different gateway instance in flow1 and flow2:
.handle(context.getBean("myServiceGateway",
MarshallingWebServiceOutboundGateway.class))
And I annotated the outbound gateway #Bean definition with
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
But I can see that the myServiceGateway() method is only invoked once, despite the prototype scope, and application startup still fails with the error message which advises to use the prototype scope - quite confusing, actually ;-)
Based on Mystery around Spring Integration and prototype scope I also tried:
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
The application starts, but the responses never reach the step after the gateway, the widgetTransformer. (Even more strange, exactly the widgetTransformer is skipped: in flow1 the outcome is the untransformed gateway response and in flow2 the untransformed messages hit the step after the widgetTransformer, i.e. the servicePojo). Making a proxy out of a message producer seems not to be a good idea.
I really want to get to the bottom of this. Is the exception message wrong which asks to use the prototype scope or am I just getting it wrong? How can I avoid to repeat the bean definition for message producers if I need several such producers which are all set up the same way?
Using spring-integration 5.0.9.
I am not entirely sure why the #Scope is not working, but here is a work-around...
#SpringBootApplication
public class So52453934Application {
public static void main(String[] args) {
SpringApplication.run(So52453934Application.class, args);
}
#Autowired
private HandlerConfig config;
#Bean
public IntegrationFlow flow1() {
return f -> f.handle(this.config.myHandler())
.handle(System.out::println);
}
#Bean
public IntegrationFlow flow2() {
return f -> f.handle(this.config.myHandler())
.handle(System.out::println);
}
#Bean
public ApplicationRunner runner() {
return args -> {
context.getBean("flow1.input", MessageChannel.class).send(new GenericMessage<>("foo"));
context.getBean("flow2.input", MessageChannel.class).send(new GenericMessage<>("bar"));
};
}
}
#Configuration
class HandlerConfig {
public AbstractReplyProducingMessageHandler myHandler() {
return new AbstractReplyProducingMessageHandler() {
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return ((String) requestMessage.getPayload()).toUpperCase();
}
};
}
}
i.e. do as #artem suggested, but inject the bean with the factory method.

Spring Integration DSL Dynamic Inbound Channel

Is it possible to register MessageSources at runtime with spring-integration-dsl?
In my case I want to create multiple FileReadingMessageSources (based on input from UI) and then send the payload to a specific channel/jms route (which is read from metadata or user input)
Another Question is, is it possible to dynamically register IntegrationFlows?
It's a bit tricky and requires some Spring infrastructure knowledges, but yes it is possible:
#Service
public static class MyService {
#Autowired
private AutowireCapableBeanFactory beanFactory;
#Autowired
#Qualifier("dynamicAdaptersResult")
PollableChannel dynamicAdaptersResult;
public void pollDirectories(File... directories) {
for (File directory : directories) {
StandardIntegrationFlow integrationFlow = IntegrationFlows
.from(s -> s.file(directory),
e -> e.poller(p -> p.fixedDelay(1000))
.id(directory.getName() + ".adapter"))
.transform(Transformers.fileToString(),
e -> e.id(directory.getName() + ".transformer"))
.channel(this.dynamicAdaptersResult)
.get();
this.beanFactory.initializeBean(integrationFlow, directory.getName());
this.beanFactory.getBean(directory.getName() + ".transformer", Lifecycle.class).start();
this.beanFactory.getBean(directory.getName() + ".adapter", Lifecycle.class).start();
}
}
}
Investigate this my sample, please, and let me know what isn't clear for you.

Resources