Handle expected return from Spring Integration File - java

I have the following configuration file for Spring Integration File:
#Configuration
#EnableIntegration
public class MyIntegrationConfiguration {
private static final String FILE_CHANNEL_PROCESSING = "processingfileChannel";
private static final String INTERVAL_PROCESSING = "5000";
private static final String FILE_PATTERN = "*.txt";
#Value("${import.path.source}")
private String sourceDir;
#Value("${import.path.output}")
private String outputDir;
#Bean
#InboundChannelAdapter(value = FILE_CHANNEL_PROCESSING, poller = #Poller(fixedDelay = INTERVAL_PROCESSING))
public MessageSource<File> sourceFiles() {
FileReadingMessageSource source = new FileReadingMessageSource();
source.setAutoCreateDirectory(true);
source.setDirectory(new File(sourceDir));
source.setFilter(new SimplePatternFileListFilter(FILE_PATTERN));
return source;
}
#Bean
#ServiceActivator(inputChannel = FILE_CHANNEL_PROCESSING)
public MessageHandler processedFiles() {
FileWritingMessageHandler handler = new FileWritingMessageHandler(new File(outputDir));
handler.setFileExistsMode(FileExistsMode.REPLACE);
handler.setDeleteSourceFiles(true);
handler.setExpectReply(true);
return handler;
}
#Bean
public IntegrationFlow processFileFlow() {
return IntegrationFlows
.from(FILE_CHANNEL_PROCESSING)
.transform(fileToStringTransformer())
.handle("fileProcessor", "processFile").get();
}
#Bean
public MessageChannel fileChannel() {
return new DirectChannel();
}
#Bean
public FileProcessor fileProcessor() {
return new FileProcessor();
}
#Bean
public FileToStringTransformer fileToStringTransformer() {
return new FileToStringTransformer();
}
}
For FileWritingMessageHandler from this documentation it says that if setExpectReply(true) is set:
Specify whether a reply Message is expected. If not, this handler will simply return null for a successful response or throw an Exception for a non-successful response.
My question is: where can I catch these exceptions or where can I retrieve this message/response?

The #ServiceActivator has an outputChannel attribute:
/**
* Specify the channel to which this service activator will send any replies.
* #return The channel name.
*/
String outputChannel() default "";
That's for successful replies.
Any exceptions (independently of the setExpectReply()) are just thrown to the caller. In your case the story is about an #InboundChannelAdapter. In this case the exception is caught and wrapped to the ErrorMessage to be sent to the errorChannel on the #Poller. It is a global errorChannel by default: https://docs.spring.io/spring-integration/docs/5.0.4.RELEASE/reference/html/configuration.html#namespace-errorhandler
However you have some other problem in your code. I see the second subscriber to the FILE_CHANNEL_PROCESSING. If it's not a PublishSubscribeChannel, you are going to have a round-robin distribution for messages sent to this channel. But that's already a different story. Just don't ask that question here, please!

Related

Cause: Cannot determine reply destination: Request message does not contain reply-to destination, and no default reply destination set

Using JMSConfig i am creating MQ Connection factory and i have InboundGatewayConfig and OutboundGatewayConfig , in Inbound Config i am reading the Message payload from one queue and in Outbound config sending the message to another queue and my goad is toset reply channel to acknowledge the sender queue once queue receive the message.
InboundGatewayConfig:
#Configuration
#EnableIntegration
public class InboundGatewayConfig {
#Autowired
JmsConfig jmsConfig;
#Value("${fcb.inbound.receiver.queue.name}")
private String orderRequestDestination;
#Bean
public MessageChannel inboundOrderRequestChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel inboundOrderResponseChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "inboundOrderRequestChannel")
public OrderService orderService() {
return new OrderService();
}
#Bean
public JmsInboundGateway jmsInboundGateway() {
JmsInboundGateway gateway = new JmsInboundGateway(
defaultMessageListenerContainer(),
channelPublishingJmsMessageListener());
gateway.setRequestChannel(inboundOrderRequestChannel());
return gateway;
}
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(jmsConfig.fcbCachingConnectionFactory());
container.setDestinationName(orderRequestDestination);
container.setRecoveryInterval(5000);
container.setErrorHandler(t -> System.out.println("Error in JMS Configurations \t" + t.getCause()));
return container;
}
#Bean
public ChannelPublishingJmsMessageListener channelPublishingJmsMessageListener() {
ChannelPublishingJmsMessageListener channelPublishingJmsMessageListener = new ChannelPublishingJmsMessageListener();
channelPublishingJmsMessageListener.setExpectReply(true);
return channelPublishingJmsMessageListener;
}
}
OutboundGatewayConfig :
#Configuration
public class OutboundGatewayConfig {
#Value("${fcb.inbound.receiver.queue.name}")
private String orderRequestDestination;
#Value("${fcb.inbound.response.queue.name}")
private String orderResponseDestination;
#Autowired
JmsConfig jmsConfig;
#Bean
public MessageChannel outboundOrderRequestChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel outboundOrderResponseChannel() {
return new QueueChannel();
}
#Bean
#ServiceActivator(inputChannel = "outboundOrderRequestChannel")
public JmsOutboundGateway jmsOutboundGateway() {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setConnectionFactory(jmsConfig.fcbCachingConnectionFactory());
gateway.setRequestDestinationName(orderRequestDestination);
gateway.setReplyDestinationName(orderResponseDestination);
gateway.setReplyChannel(outboundOrderResponseChannel());
return gateway;
}
}
and this my service class
public class OrderService {
private static final Logger LOGGER =
LoggerFactory.getLogger(OrderService.class);
public Message<?> order(Message<?> order) {
LOGGER.info("received order='{}'", order);
Message<?> status = MessageBuilder.withPayload("Accepted")
.setHeader("jms_correlationId",
order.getHeaders().get("jms_messageId"))
.setReplyChannelName("inboundOrderResponseChannel").build();
LOGGER.info("sending status='{}'", status);
return status;
}
}
While running the application InboundGateway able to receive the message payload from request queue but while reply , giving bellow warning message.
2022-04-07 16:06:24.303 WARN 19408 --- [enerContainer-1] o.s.j.l.DefaultMessageListenerContainer : Setup of JMS message listener invoker failed for destination 'xxx.yyy.queue' - trying to recover. Cause: Cannot determine reply destination: Request message does not contain reply-to destination, and no default reply destination set.

Compressing and decompressing Spring RabbitMQ messages with a DirectMessageListenerContainer

I have modified my RabbitMQ from a previous post (spring-rabbit JSON deserialization for ArrayList contents) to now use a DirectMessageListener with MessagePostProcessors to GZip and GUnzip the message payloads.
However, it doesn't appear to be working as the breakpoints are not activated, but also because my RabbitListeners are no longer receiving messages, whereas they did with a SimpleMessageFactoryListenerContainer.
Also, it appears the SimpleMessageListenerContainer(?) is still being used. On a side-note, I am autowiring the DirectMessageListenerContainer so I can dynamically set the queues I used.
spring-rabbit: 2.0.3.RELEASE.
spring-boot: 2.0.1.RELEASE.
RabbitMQ configuration:
#Configuration
#EnableRabbit
public class MessagingConfiguration implements ShutdownListener {
#Autowired
private RabbitListenerEndpointRegistry registry;
#Autowired
private DirectMessageListenerContainer container;
#Bean
public DirectMessageListenerContainer messageListenerContainer(final ConnectionFactory connectionFactory) {
final DirectMessageListenerContainer listenerContainer = new DirectMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory);
listenerContainer.setMessageConverter(jsonConverter()); // i.e.#RabbitListener to use Jackson2JsonMessageConverter
listenerContainer.setAutoStartup(false);
// container.setErrorHandler(errorHandler);
final MessageListenerAdapter messageListener = new MessageListenerAdapter(new Object() {
#SuppressWarnings("unused")
public String handleMessage(final String message) {
return message.toUpperCase();
}
});
messageListener.setBeforeSendReplyPostProcessors(new GZipPostProcessor());
listenerContainer.setMessageListener(messageListener);
listenerContainer.setAfterReceivePostProcessors(new GUnzipPostProcessor());
return listenerContainer;
}
#EventListener(ApplicationDatabaseReadyEvent.class)
public void onApplicationDatabaseReadyEvent() {
log.info("Starting all RabbitMQ Listeners..."); //$NON-NLS-1$
for (final MessageListenerContainer listenerContainer : registry.getListenerContainers()) {
listenerContainer.start();
}
log.info("Register is running: {}", registry.isRunning()); //$NON-NLS-1$
log.info("Started all RabbitMQ Listeners."); //$NON-NLS-1$
}
#Bean
public List<Declarable> bindings() {
final List<Declarable> declarations = new ArrayList<>();
final FanoutExchange exchange = new FanoutExchange("fx", true, false);
final Queue queue = QueueBuilder.durable("orders").build();
declarations.add(exchange);
declarations.add(queue);
declarations.add(BindingBuilder.bind(queue).to(exchange));
List<String> q = new ArrayList<>();
q.add(queue.getName());
container.addQueueNames(q.toArray(new String[queues.size()]));
return declarations;
}
#Bean
public Jackson2JsonMessageConverter jsonConverter() {
final Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setClassMapper(classMapper());
return converter;
}
private static DefaultJackson2JavaTypeMapper classMapper() {
final DefaultJackson2JavaTypeMapper classMapper = new DefaultJackson2JavaTypeMapper();
classMapper.setTrustedPackages("*"); //$NON-NLS-1$ //TODO add trusted packages
return classMapper;
}
#ConditionalOnProperty(name = "consumer", havingValue = "true")
#Bean
public ConsumerListener listenerConsumer() {
return new ConsumerListener();
}
#ConditionalOnProperty(name = "producer", havingValue = "true")
#Bean
public ProducerListener listenerProducer() {
return new ProducerListener();
}
#Bean
public RabbitAdmin rabbitAdmin(final CachingConnectionFactory connectionFactory) {
return new RabbitAdmin(connectionFactory);
}
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonConverter()); // convert all sent messages to JSON
rabbitTemplate.setReplyTimeout(TimeUnit.SECONDS.toMillis(3));
rabbitTemplate.setReceiveTimeout(TimeUnit.SECONDS.toMillis(3));
return rabbitTemplate;
}
#Override
public void shutdownCompleted(final ShutdownSignalException arg0) {
}
}
It doesn't work that way, you can't autowire containers for #RabbitListeners; they are not beans; they are created by the container factory and registered in the registry. Instead you have to retrieve them from the registry (by id).
However, since you have autoStartup set to false, it shouldn't be "stealing" messages from your #RabbitListener.
Generally, DEBUG logging should help.

Re downloading of message when using spring integration inbound channel adapter

I am reading mails using spring mail inbound channel adapter once message is read i am performing some db operations in service activator of corresponding channel. My requirement is if any db operation fails adapter should read same message again.
Mail configuration :
#Bean
public DirectChannel inputChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow pop3MailFlow() {
String url = "[url]";
return IntegrationFlows
.from(Mail.pop3InboundAdapter(url)
.javaMailProperties(p -> p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory")),e -> e.autoStartup(true)
.poller(Pollers.fixedDelay(2000).transactionSynchronizationFactory(transactionSynchronizationFactory())))
.channel(inputChannel())
.handle(inboundEmailProcessor(),"messageProcess")
.get();
}
#Bean
public InboundEmailProcessor inboundEmailProcessor() {
return new InboundEmailProcessor();
}
#Bean
public TransactionSynchronizationFactory transactionSynchronizationFactory() {
TransactionSynchronizationFactory synchronizationFactory = new DefaultTransactionSynchronizationFactory(expressionEvaluatingTransactionSynchronizationProcessor());
return synchronizationFactory;
}
#Bean
public ExpressionEvaluatingTransactionSynchronizationProcessor expressionEvaluatingTransactionSynchronizationProcessor() {
ExpressionEvaluatingTransactionSynchronizationProcessor processor = new ExpressionEvaluatingTransactionSynchronizationProcessor();
ExpressionParser parser = new SpelExpressionParser();
processor.setAfterRollbackExpression(parser.parseExpression("new com.muraai.ex.config.Exp().process(payload)"));
return processor;
}
public class InboundEmailProcessor {
#Autowired
AttachmentsRepository attachmentsRepository;
#Transactional(rollbackFor = Exception.class)
public void messageProcess() {
// some db operations
// if it fails the same message should be read again
}
}
I thought this would work but its not working. Is there any way to achieve my requirement
public class Exp {
public void process(MimeMessage message) throws MessagingException {
message.setFlag(Flags.Flag.SEEN, false);
}
}
You need IMAP for that; with POP3, the server always marks them read.
You can add a spring-retry interceptor advice to the poller's advice chain and/or send the failed message to an error channel.
The retry advice can be configured for number of retries, back off policy etc.

Enrich Header at one FTP server and get the header at another FTP server

I have successfully been able send file from one FTP Server(source) to another FTP server (target). I first send files from source to the local directory using the inbound adapter and then send files from the local directory to the target using the outbound adapter. So far this is working fine.
What I want to achieve is: to enrich the header of the message at the source with a hash code (which is generated using the file on source that is transferred) and then get that header at the target and match it with the hash code (which is generated using the file on the target)
Here is what I have tried so far:
Application.java
#SpringBootApplication
public class Application {
#Autowired
private Hashing hashing;
public static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = new SpringApplicationBuilder(Application.class)
.web(false)
.run(args);
}
#Bean
#ServiceActivator(inputChannel = "ftpChannel")
public MessageHandler sourceHandler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println("Reply channel isssss:"+message.getHeaders().getReplyChannel());
Object payload = message.getPayload();
System.out.println("Payload: " + payload);
File file = (File) payload;
// enrich header with hash code before sending to target FTP
Message<?> messageOut = MessageBuilder
.withPayload(message.getPayload())
.copyHeadersIfAbsent(message.getHeaders())
.setHeaderIfAbsent("hashCode", hashing.getHashCode(file)).build();
// send to target FTP
System.out.println("Trying to send " + file.getName() + " to target");
MyGateway gateway = context.getBean(MyGateway.class);
gateway.sendToFtp(messageOut);
}
};
}
}
FileTransferServiceConfig.java
#Configuration
#Component
public class FileTransferServiceConfig {
#Autowired
private ConfigurationService configurationService;
#Autowired
private Hashing hashing;
public static final String FILE_POLLING_DURATION = "5000";
#Bean
public SessionFactory<FTPFile> sourceFtpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(configurationService.getSourceHostName());
sf.setPort(Integer.parseInt(configurationService.getSourcePort()));
sf.setUsername(configurationService.getSourceUsername());
sf.setPassword(configurationService.getSourcePassword());
return new CachingSessionFactory<>(sf);
}
#Bean
public SessionFactory<FTPFile> targetFtpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(configurationService.getTargetHostName());
sf.setPort(Integer.parseInt(configurationService.getTargetPort()));
sf.setUsername(configurationService.getTargetUsername());
sf.setPassword(configurationService.getTargetPassword());
return new CachingSessionFactory<>(sf);
}
#MessagingGateway
public interface MyGateway {
#Gateway(requestChannel = "toFtpChannel")
void sendToFtp(Message message);
}
#Bean
public FtpInboundFileSynchronizer ftpInboundFileSynchronizer() {
FtpInboundFileSynchronizer fileSynchronizer = new FtpInboundFileSynchronizer(sourceFtpSessionFactory());
fileSynchronizer.setDeleteRemoteFiles(false);
fileSynchronizer.setRemoteDirectory(configurationService.getSourceDirectory());
fileSynchronizer.setFilter(new FtpSimplePatternFileListFilter(
configurationService.getFileMask()));
return fileSynchronizer;
}
#Bean
public AcceptOnceFileListFilter<File> acceptOnceFileListFilter() {
return new AcceptOnceFileListFilter<>();
}
#Bean
#InboundChannelAdapter(channel = "ftpChannel",
poller = #Poller(fixedDelay = FILE_POLLING_DURATION))
public MessageSource<File> ftpMessageSource() {
FtpInboundFileSynchronizingMessageSource source
= new FtpInboundFileSynchronizingMessageSource(ftpInboundFileSynchronizer());
source.setLocalDirectory(new File(configurationService.getLocalDirectory()));
source.setAutoCreateLocalDirectory(true);
source.setLocalFilter(acceptOnceFileListFilter());
return source;
}
// makes sure transfer continues on connection reset
#Bean
public Advice expressionAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setTrapException(true);
advice.setOnFailureExpression("#acceptOnceFileListFilter.remove(payload)");
return advice;
}
#Bean
#ServiceActivator(inputChannel = "toFtpChannel")
public void listenOutboundMessage() {
// tried to subscribe to "toFtpChannel" but this was not triggered
System.out.println("Message received");
}
#Bean
#ServiceActivator(inputChannel = "ftpChannel", adviceChain = "expressionAdvice")
public MessageHandler targetHandler() {
FtpMessageHandler handler = new FtpMessageHandler(targetFtpSessionFactory());
handler.setRemoteDirectoryExpression(new LiteralExpression(
configurationService.getTargetDirectory()));
return handler;
}
}
Hashing.java
public interface Hashing {
public String getHashCode(File payload);
}
I have managed to enrich the message in sourceHandler(), built the message and sent it to the target but I cannot figure out how I can receive that message on the target so that I can get the header from the message?
Tell me if any more information is required. I would really appreciate your help.
You have two subscribers on ftpChannel - the target handler and your sourceHandler; they will get alternate messages unless ftpChannel is declared as a pubsub channel.
There should be no problems with your subscription to toFtpChannel.
Turn on DEBUG logging to see all the subscription activity when the application context starts.
EDIT
Remove the #Bean from the #ServiceActivator - such beans must be a MessageHandler.
#ServiceActivator(inputChannel = "toFtpChannel")
public void listenOutboundMessage(Message message) {
// tried to subscribe to "toFtpChannel" but this was not triggered
System.out.println("Message received:" + message);
}
works fine for me...
Payload: /tmp/foo/baz.txt
Trying to send baz.txt to target
Message received:GenericMessage [payload=/tmp/foo/baz.txt, headers={hashCode=foo, id=410eb9a2-fe8b-ea8a-015a-d5896387cf00, timestamp=1509115006278}]
Again; you must have only one subscriber on ftpChannel unless you make it a pubsub.

How to create a Spring Reactor Flux from a ActiveMQ queue?

I am experimenting with the Spring Reactor 3 components and Spring Integration to create a reactive stream (Flux) from a JMS queue.
I am attempting to create a reactive stream (Spring Reactor 3 Flux) from a JMS queue (ActiveMQ using Spring Integration) for clients to get the JMS messages asynchronously. I believe that I have everything hooked up correctly but the client does not receive any of the JMS messages until the server is stopped. Then all of the messages get "pushed" to the client a once.
Any help would be appreciated.
Here is the configuration file that I am using to configure the JMS, Integration components and the reactive publisher:
#Configuration
#EnableJms
#EnableIntegration
public class JmsConfiguration {
#Value("${spring.activemq.broker-url:tcp://localhost:61616}")
private String defaultBrokerUrl;
#Value("${queues.patient:patient}")
private String patientQueue;
#Autowired
MessageListenerAdapter messageListenerAdapter;
#Bean
public DefaultJmsListenerContainerFactory myFactory(
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
configurer.configure(factory, jmsConnectionFactory());
return factory;
}
#Bean
public Queue patientQueue() {
return new ActiveMQQueue(patientQueue);
}
#Bean
public ActiveMQConnectionFactory jmsConnectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(defaultBrokerUrl);
connectionFactory.setTrustedPackages(Arrays.asList("com.sapinero"));
return connectionFactory;
}
// Set the jackson message converter
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(jmsConnectionFactory());
template.setDefaultDestinationName(patientQueue);
template.setMessageConverter(jacksonJmsMessageConverter());
return template;
}
#Bean
public MessageListenerAdapter messageListenerAdapter() {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter();
messageListenerAdapter.setMessageConverter(jacksonJmsMessageConverter());
return messageListenerAdapter;
}
#Bean
public AbstractMessageListenerContainer messageListenerContainer() {
DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setMessageConverter(jacksonJmsMessageConverter());
defaultMessageListenerContainer.setConnectionFactory(jmsConnectionFactory());
defaultMessageListenerContainer.setDestinationName(patientQueue);
defaultMessageListenerContainer.setMessageListener(messageListenerAdapter());
defaultMessageListenerContainer.setCacheLevel(100);
defaultMessageListenerContainer.setErrorHandler(new ErrorHandler() {
#Override
public void handleError(Throwable t) {
t.printStackTrace();
}
});
return defaultMessageListenerContainer;
}
#Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
public MessageChannel jmsOutboundInboundReplyChannel() {
return MessageChannels.queue().get();
}
#Bean
public Publisher<Message<String>> pollableReactiveFlow() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(messageListenerContainer()).get())
.channel(MessageChannels.queue())
.log(LoggingHandler.Level.DEBUG)
.log()
.toReactivePublisher();
}
#Bean
public MessageChannel jmsChannel() {
return new DirectChannel();
}
The controller that creates the Flux is:
#RestController
#RequestMapping("patients")
public class PatientChangePushController {
private LocalDateTime lastTimePatientDataRetrieved = LocalDateTime.now();
private int durationInSeconds = 30;
private Patient patient;
AtomicReference<SignalType> checkFinally = new AtomicReference<>();
#Autowired
PatientService patientService;
#Autowired
#Qualifier("pollableReactiveFlow")
private
Publisher<Message<String>> pollableReactiveFlow;
#Autowired
private JmsTemplate jmsTemplate;
#Autowired
private Queue patientQueue;
/**
* Subscribe to a Flux of a patient that has been updated.
*
* #param id
* #return
*/
#GetMapping(value = "/{id}/alerts", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Message<String>> getPatientAlerts(#PathVariable Long id) {
Flux<Message<String>> messageFlux = Flux.from(pollableReactiveFlow);
return messageFlux;
}
#GetMapping(value = "/generate")
public void generateJmsMessage() {
for (long i = 0L; i < 100; i++) {
Patient patient = new Patient();
patient.setId(i);
send(patient);
System.out.println("Message was sent to the Queue");
}
}
void send(Patient patient) {
this.jmsTemplate.convertAndSend(this.patientQueue, patient);
}
}
If anyone can tell me why the messages do not get sent to the client until after the server is killed, I would appreciate it.
Works well for me:
#SpringBootApplication
#RestController
public class SpringIntegrationSseDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringIntegrationSseDemoApplication.class, args);
}
#Autowired
private ConnectionFactory connectionFactory;
#Autowired
private JmsTemplate jmsTemplate;
#Bean
public Publisher<Message<String>> jmsReactiveSource() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(this.connectionFactory)
.destination("testQueue"))
.channel(MessageChannels.queue())
.log(LoggingHandler.Level.DEBUG)
.log()
.toReactivePublisher();
}
#GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getPatientAlerts() {
return Flux.from(jmsReactiveSource())
.map(Message::getPayload);
}
#GetMapping(value = "/generate")
public void generateJmsMessage() {
for (int i = 0; i < 100; i++) {
this.jmsTemplate.convertAndSend("testQueue", "testMessage #" + (i + 1));
}
}
}
In one terminal I have curl http://localhost:8080/events which waits for SSEs from that Flux.
In other terminal I perform curl http://localhost:8080/generate and see in the first one:
data:testMessage #1
data:testMessage #2
data:testMessage #3
data:testMessage #4
I use Spring Boot 2.0.0.BUILD-SNAPSHOT.
Also see here: https://spring.io/blog/2017/03/08/spring-tips-server-sent-events-sse

Categories