How to create dynamically Spring Integration MessageChannels - spring

In the Spring integration the message channel can be configured like this:
<int:channel id="get_send_channel" />
<int:channel id="get_receive_channel">
<int:queue capacity='10' />
</int:channel>
<int-http:outbound-gateway id="get.outbound.gateway"
request-channel="get_send_channel"
url="http://localhost:8080/greeting"
http-method="GET" reply-channel="get_receive_channel"
expected-response-type="java.lang.String">
</int-http:outbound-gateway>
and used like this:
#SpringBootApplication
#ImportResource("http-outbound-gateway.xml")
public class HttpApplication {
#Autowired
#Qualifier("get_send_channel")
MessageChannel getSendChannel;
#Autowired
#Qualifier("get_receive_channel")
PollableChannel getReceiveChannel;
public static void main(String[] args) {
SpringApplication.run(HttpApplication.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
Message<?> message = MessageBuilder.withPayload("").build();
getSendChannel.send(message);
System.out.println(getReceiveChannel.receive().getPayload());
};
}
How the MessageChannel can be created and registered dynamically?
Above code is from this example
I have now tried this
return IntegrationFlows.from(MessageChannels.rendezvous("getSend1"))
.handle(Http.outboundGateway("http://localhost:8080/greeting").httpMethod(HttpMethod.GET))
.channel(MessageChannels.queue("getReceive1")).get();
with default poller, but there is the message:
preReceive on channel 'getSend1'
postReceive on channel 'getSend1', message is null
Received no Message during the poll, returning 'false'
So the configuration seems to be incorrect and the message is not fetch from the URL.

It is working like this:
#Bean
public IntegrationFlow inbound() {
return IntegrationFlows
.from(this.integerMessageSource(), c -> c.poller(Pollers.fixedRate(2000)))
.handle(Http.outboundGateway("http://localhost:8055/greeting")
.httpMethod(HttpMethod.GET).expectedResponseType(String.class))
.channel(MessageChannels.queue("getReceive"))
.handle(Http.outboundGateway("http://localhost:8055/greeting").httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.channel(MessageChannels.queue("postReceive"))
.handle(Http.outboundGateway("http://localhost:8055/greeting-final").httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.channel(MessageChannels.queue("postReceiveFinal"))
.get();
}
This does get for the first URL, then posts the answer the second URL and then posts the answer to third URL and gets the final answer.

Related

Spring integration: discardChannel doesn't work for filter of integration flow

I faced with a problem when I create IntegrationFlow dynamically using DSL.
If discardChannel is defined as message channel object and if the filter returns false - nothing happens (the message is not sent to specified discard channel)
The source is:
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
...
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private MessageChannel simpleChannel;
#Bean
public IntegrationFlow simpleFlow() {
return IntegrationFlows.from(simpleChannel)
.handle(m -> System.out.println("Hello world"))
.get();
#Bean(name = "SIMPLE_CHANNEL")
public MessageChannel simpleChannel() {
return new DirectChannel();
}
But if the discard channel is defined as name of the channel, everything works.
Debuging I found that mentioned above the part of the code:
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(simpleMessageSelectorImpl, e -> e.discardChannel(simpleChannel))
.get();
returns flow object which has map with integrationComponents and one of the component which is FilterEndpointSpec has "handler" field of type MessageFilter with discardChannel = null, and discardChannelName = null;
But if discard channel is defined as name of the channel the mentioned field "handler" with discardChannel=null but discardChannelName="SIMPLE_CHANNEL", as result everything works good.
It is behavior of my running application. Also I wrote the test and in test everything works good for both cases (the test doesn't run all spring context so maybe it is related to any conflict there)
Maybe someone has idea what it can be.
The spring boot version is 2.1.8.RELEASE, spring integration is 5.1.7.RELEASE
Thanks
The behaviour you describe is indeed incorrect and made me wonder, but after testing it out I can't seem to reproduce it, so perhaps there is something missing from the information you provided. In any event, here is the complete app that I've modeled after yours which works as expected. So perhaps you can compare and see if something jumps:
#SpringBootApplication
public class IntegrationBootApp {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(IntegrationBootApp.class, args);
MessageChannel channel = context.getBean("channelName", MessageChannel.class);
PollableChannel resultChannel = context.getBean("resultChannel", PollableChannel.class);
PollableChannel discardChannel = context.getBean("SIMPLE_CHANNEL", PollableChannel.class);
channel.send(MessageBuilder.withPayload("foo").build());
System.out.println("SUCCESS: " + resultChannel.receive());
channel.send(MessageBuilder.withPayload("bar").build());
System.out.println("DISCARD: " + discardChannel.receive());
}
#Autowired
#Qualifier("SIMPLE_CHANNEL")
private PollableChannel simpleChannel;
#Bean
public IntegrationFlow integrationFlow() {
IntegrationFlow integrationFlow = IntegrationFlows.from("channelName")
.filter(v -> v.equals("foo"), e -> e.discardChannel(simpleChannel))
.channel("resultChannel")
.get();
return integrationFlow;
}
#Bean(name = "SIMPLE_CHANNEL")
public PollableChannel simpleChannel() {
return new QueueChannel();
}
#Bean
public PollableChannel resultChannel() {
return new QueueChannel(10);
}
}
with output
SUCCESS: GenericMessage [payload=foo, headers={id=cf7e2ef1-e49d-1ecb-9c92-45224d0d91c1, timestamp=1576219339077}]
DISCARD: GenericMessage [payload=bar, headers={id=bf209500-c3cd-9a7c-0216-7d6f51cd5f40, timestamp=1576219339078}]

RabbitListener annotation queue name by ConfigurationProperties

I have configured my rabbit properties via application.yaml and spring configurationProperties.
Thus, when I configure exchanges, queues and bindings, I can use the getters of my properties
#Bean Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(properties.getQueue());
}
#Bean Queue queue() {
return new Queue(properties.getQueue(), true);
}
#Bean TopicExchange exchange() {
return new TopicExchange(properties.getExchange());
}
However, when I configure a #RabbitListener to log the messages on from the queue, I have to use the full properties name like
#RabbitListener(queues = "${some.long.path.to.the.queue.name}")
public void onMessage(
final Message message, final Channel channel) throws Exception {
log.info("receiving message: {}#{}", message, channel);
}
I want to avoid this error prone hard coded String and refer to the configurationProperties bean like:
#RabbitListener(queues = "${properties.getQueue()}")
I had a similar issue once with #EventListener where using a bean reference "#bean.method()" helped, but it does not work here, the bean expression is just interpreted as queue name, which fails because a queue namde "#bean...." does not exist.
Is it possible to use ConfigurationProperty-Beans for RabbitListener queue configuration?
Something like this worked for me where I just used the Bean and SpEL.
#Autowired
Queue queue;
#RabbitListener(queues = "#{queue.getName()}")
I was finally able to accomplish what we both desired to do by taking what #David Diehl suggested, using the bean and SpEL; however, using MyRabbitProperties itself instead. I removed the #EnableConfigurationProperties(MyRabbitProperties.class) in the config class, and registered the bean the standard way:
#Configuration
//#EnableConfigurationProperties(RabbitProperties.class)
#EnableRabbit
public class RabbitConfig {
//private final MyRabbitProperties myRabbitProperties;
//#Autowired
//public RabbitConfig(MyRabbitProperties myRabbitProperties) {
//this.myRabbitProperties = myRabbitProperties;
//}
#Bean
public TopicExchange myExchange(MyRabbitProperties myRabbitProperties) {
return new TopicExchange(myRabbitProperties.getExchange());
}
#Bean
public Queue myQueueBean(MyRabbitProperties myRabbitProperties) {
return new Queue(myRabbitProperties.getQueue(), true);
}
#Bean
public Binding binding(Queue myQueueBean, TopicExchange myExchange, MyRabbitProperties myRabbitProperties) {
return BindingBuilder.bind(myQueueBean).to(myExchange).with(myRabbitProperties.getRoutingKey());
}
#Bean
public MyRabbitProperties myRabbitProperties() {
return new MyRabbitProperties();
}
}
From there, you can access the get method for that field:
#Component
public class RabbitQueueListenerClass {
#RabbitListener(queues = "#{myRabbitProperties.getQueue()}")
public void processMessage(Message message) {
}
}
#RabbitListener(queues = "#{myQueue.name}")
Listener:
#RabbitListener(queues = "${queueName}")
application.properties:
queueName=myQueue

Spring Batch Integration JobLaunchRequest from Controller

I have a controller from which I would like to run a Spring Batch job. The FilePoller acts as a poller that runs on a schedule but we'd like to run it manually, too. The FilePoller is working on the cron schedule and the JobLaunchRequest works in this fashion. But when we use JobLaunchRequest called from the controller, nothing happens -- the Spring Batch job is not launched. Here is the controller:
#Controller
public class PollerController {
#Autowired
FilePoller FilePoller;
#Autowired
private ApplicationContext appContext;
#RequestMapping(value = "ui/manualPoll.action", method = RequestMethod.GET)
public void manualPollRequest() {
Message<File> message = filePoller.fileMessageSource().receive();
filePoller.setFileParameterName(message.getPayload().getName());
filePoller.setJob((Job)appContext.getBean("myJob"));
filePoller.toRequest(message);
}
The message payload has the file name and I get the Spring Batch job to run from the application context. I have debugged and stepped through code and ensured that the file name from the message payload is not null and that the Spring Batch job is also not null. Inside the FilePoller class I have this:
#Configuration
#PropertySource("classpath:my.properties")
#EnableIntegration
#IntegrationComponentScan
public class FilePoller {
private Job job;
private String fileParameterName;
#Autowired
MyProperty myProperty;
public void setFileParameterName(String fileParameterName) {
this.fileParameterName = fileParameterName;
}
public void setJob(Job job) {
this.job = job;
}
#Bean
#InboundChannelAdapter(value = "inboundFileChannel", poller = #Poller(cron="${my/POLLER}"))
public MessageSource<File> fileMessageSource() {
FileReadingMessageSource source = initialSetUp();
source.setDirectory(new File(myProperty.getProperty(MyConstants.WORKING_DIR)))
return source;
}
private FileReadingMessageSource initialSetUp() {
FileReadingMessageSource source = new FileReadingMessageSource();
CompositeFileListFilter<File> compositeFileListFilter = new CompositeFileListFilter<File>();
SimplePatternFileListFilter simplePatternFileListFilter = new SimplePatternFileListFilter("*.done");
AcceptOnceFileListFilter<File> acceptOnceFileListFilter = new AcceptOnceFileListFilter<File>();
compositeFileListFilter.addFilter(simplePatternFileListFilter);
compositeFileListFilter.addFilter(acceptOnceFileListFilter);
source.setFilter(compositeFileListFilter);
return source;
}
#Transformer
public JobLaunchRequest toRequest(Message<File> message) {
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addString(fileParameterName, message.getPayload().getAbsolutePath());
return new JobLaunchRequest(job, jobParametersBuilder.toJobParameters());
}
The JobLaunchRequest doesn't seem to do anything. I never get into my first step, shown here in the XML config:
<int:annotation-config />
<int:channel id="inboundFileChannel" />
<int:channel id="outboundJobRequestChannel" />
<int:channel id="jobLaunchReplyChannel" />
<int:transformer input-channel="inboundFileChannel"
output-channel="outboundJobRequestChannel">
<bean
class="org.my.poller.FilePoller">
<property name="job" ref="myJob" />
<property name="fileParameterName" value="input.file.name" />
</bean>
</int:transformer>
<batch-int:job-launching-gateway request-channel="outboundJobRequestChannel" reply-channel="jobLaunchReplyChannel" />
<int:logging-channel-adapter channel="jobLaunchReplyChannel" />
<job id="myJob" xmlns="http://www.springframework.org/schema/batch">
<step id="Step1" next="Step2">
<tasklet ref="checkifFileinLogTbl"/>
UPDATE
Thanks, Gary. It works now. If anyone else is interested:
#Autowired
MessageChannel outboundJobRequestChannel;
#Autowired
MessagingTemplate template;
#RequestMapping(value = "ui/manualPoll.action", method = RequestMethod.GET)
public void manualPollRequest() {
Message<File> message = filePoller.fileMessageSource().receive();
//if message !=null, there is a file present on inboundFileChannel
if(message !=null){
filePoller.setFileParameterName("input.file.name");
filePoller.setJob((Job) appContext.getBean("myJob"));
template.convertAndSend(outboundJobRequestChannel, filePoller.toRequest(message));
}
And in the XML I added:
<bean class="org.springframework.integration.core.MessagingTemplate" />
filePoller.toRequest(message);
Just builds the request object.
You need to send it to the outboundJobRequestChannel.
Use a MessagingTemplate (convertSendAndReceive()) or a MessagingGateway to do that.

Can I use both configuring SI with annotation in java file and xml?

Last year, spring integration released 4.0 version for us to configure using annotation without configuring in XML files. But I want to use this feature using the existing XML configurations.
So I wrote the code using spring boot and integration annotation
#Configuration
#ComponentScan(basePackages ={"com.strongjoe.blue"},excludeFilters=#ComponentScan.Filter(type=FilterType.REGEX, pattern={"com.strongjoe.blue.agent.Bootstrap*"}))
#IntegrationComponentScan
#ImportResource( {"${context.agent.path}context-bean-*.xml", // Context Configuration
"${project.agent.path}context-properties.xml" } ) // Project Based Chain configuration
public class AgentStarter implements CommandLineRunner{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Lazy
#Bean
#ServiceActivator(inputChannel="blue-hub-start-channel", outputChannel="blue-hub-route-channel")
public <T> BlueMessage<T> startJob(BlueMessage<T> msg) {
logger.debug("BluE Hub Agent started :{} [phrase:{}]", System.currentTimeMillis() , "prototype-version");
return msg;
}
#Lazy
#Bean
#ServiceActivator(inputChannel="blue-hub-end-channel")
public <T> BlueMessage<T> endJob(BlueMessage<T> msg) {
logger.debug("BluE Hub Agent ended :{} [phrase:{}]", System.currentTimeMillis() , "prototype-version");
return msg;
}
#Bean
#Transformer(inputChannel="blue-normalized-channeel", outputChannel="blue-output-channel")
public org.springframework.integration.transformer.Transformer JsonToMap( ) {
return new JsonToObjectTransformer( List.class );
}
#MessageEndpoint
public static class Echo {
#ServiceActivator(inputChannel="blue-output-channel")
public void stringEcho(Message message) {
}
}
#Autowired
Gateway gateway;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(AgentStarter.class);
app.setWebEnvironment(false);
app.run(args).close();
}
#Override
public void run(String... args) throws Exception {
System.err.println("blue-hub-agent started..");
System.out.println(gateway.sendReceive("gitlab"));
}
And I wrote the definition about every channel I use in the xml.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ws="http://www.springframework.org/schema/integration/ws"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-4.0.xsd
http://www.springframework.org/schema/integration/ws http://www.springframework.org/schema/integration/ws/spring-integration-ws.xsd
http://www.springframework.org/schema/integration/xml http://www.springframework.org/schema/integration/xml/spring-integration-xml.xsd
http://www.springframework.org/schema/integration/http http://www.springframework.org/schema/integration/http/spring-integration-http-4.0.xsd">
<int:channel id="blue-normalized-channel" />
<int:channel id="blue-header-channeel" />
<int:channel id="blue-request-channel" />
<int:channel id="blue-output-channel" />
<int:channel id="blue-gitlab-request-prepare-channel" />
<int:channel id="blue-hub-start-command-channel" />
<int:channel id="blue-hub-start-channel"/>
<int:channel id="blue-hub-end-channel" />
But I got error.
Caused by: org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application:8090.blue-hub-start-channel'.
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:81)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:255)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:223)
The reason will be, I think,
that spring bean in XML file and spring bean with the annotation has different context. So I think that even if blue-hub-start-channel is subscribed by the service activator named "startJob", it got error.
How can I solve this problem?
Annotating #ServiceActivator on #Bean is not for POJO Messaging. See the documentation.
When you annotate #Beans this way, you have to provide an appropriate object (MessageHandler in this case).
For POJO style annotated methods, the annotation must be on a method in a #Bean method (like you have on this one...
#MessageEndpoint
public static class Echo {
#ServiceActivator(inputChannel="blue-output-channel")
public void stringEcho(Message message) {
}
}
... and then declare a #Bean for Echo.
You can put all your #ServiceActivator methods (and #Transformers) in the same #MessageEndpoint.

Spring SimpMessagingTemplate

I have an application which receive some data from RabbitMQ. Everything works fine, I mean in class where I have annotation #EnableScheduling.
#Scheduled(fixedDelay = 5000)
public void volumeGraphData() {
Random r = new Random();
Graph graph = new Graph();
graph.setVolume(r.nextInt(500));
String json = gson.toJson(graph);
MessageBuilder<byte[]> messageBuilder = MessageBuilder.withPayload(json.getBytes());
simpMessagingTemplate.send("/" + volumeGraph, messageBuilder.build());
}
But when I would like to process messages received by Queue Listener from RabbitMQ (this works too) and pass them through to specific context for Stomp WebSocket using SimpMessagingTemplate I cannot do that. SimpMessagingTemplate is defined in dispatcher-servlet.xml, but configuration related with RabbitMQ is in root context. I tried to move everything to one context, but it does not work. Anyone has similar case that one I have ?
I finally managed to fix this. So, basically you need move your beans related with Spring Messaging/WebSocket to one common bean.
That's why in my root context I have such lines :
<!-- Fix for IntelliJ 13.1 https://youtrack.jetbrains.com/issue/IDEA-123964 -->
<context:component-scan base-package="org.springframework.web.socket.config"/>
where in package pl.garciapl.program.service.config is located class responsible for configuration of WebSockets :
#Configuration
#EnableWebSocketMessageBroker
#Component("messageBroker")
public class MessageBrokerConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/test").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
}
#Override
public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public void configureClientOutboundChannel(ChannelRegistration channelRegistration) {
}
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(new MappingJackson2MessageConverter());
return false;
}
}
Remember to store your beans which use SimpMessagingTemplate in the same context where you defined this above class.

Resources