Configure output channel for service activator via .handle() - java

Edited
I am new to Spring integration, and I am trying to pass data from a service activator(via handle method) to a channel.
#Bean
public IntegrationFlow processFileFlow() {
return IntegrationFlows
.from("fileInputChannel")
.transform(fileToStringTransformer())
.handle("fileProcessor", "process")
.channel("xmlChannel")
.get();
}
#Bean
public DirectChannel fileInputChannel(){
return new DirectChannel();
}
#Bean
public DirectChannel xmlChannel(){
return new DirectChannel();
}
#Bean
public FileProcessor fileProcessor() {
return new FileProcessor();
}
#Bean
public IntegrationFlow enrichDataFlow() {
return IntegrationFlows
.from("xmlChannel")
.handle("enricher", "enrichProduct")
.channel("bvChannel")
.get();
}
#Bean
public Enricher enricher() {
return new Enricher();
}
This is the FileProcessor class
public class FileProcessor {
#ServiceActivator(outputChannel = "xmlChannel")
public String process(Message<String> msg) {
String content = msg.getPayload();
return content;
}
}
This is the Enricher class:
public class Enricher {
public void enrichProduct(Message<String> msg) {
System.out.println("Enriching product " + msg);
}
}
The message "Enriching product .." isn't getting printed, and I'm not really sure of what has gone wrong.

Doesn't that .channel("xmlChannel") work for you?
That's definitely the proper way to compose IntegrationFlow - from one endpoint over a message channel to another endpoint.
And I would say that outputChannel = "xmlChannel" in the #ServiceActivator isn't going to work just because there is no inputChannel. More over the .handle() will just ignore all those attributes and just will deal with the fileProcessor.process() method directly.

Related

Spring Boot + RabbitMQ: how to convert received object using convertSendAndReceive() method?

How can I deserialize a message using convertSendAndReceive() method? It gives me NullPointerException due to not being able to find the required class for deserialization in another package. Packages are marked in the code.
Listener receives and sends messages normally
package org.dneversky.user;
#EnableRabbit
#Component
public class TestListener {
private static final Logger logger = LoggerFactory.getLogger(TestListener.class);
#Autowired
private RabbitTemplate rabbitTemplate;
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE)
public void doGet(UserReplyMessage message) {
logger.info("Received message: {}", message);
UserReplyMessage response = new UserReplyMessage();
logger.info("Sending message: {}", response);
rabbitTemplate.convertSendAndReceive(RabbitMQConfig.RPC_EXCHANGE,
RabbitMQConfig.REPLY_QUEUE, response);
}
}
Configuration of the listener
package org.dneversky.user.config;
#Configuration
public class RabbitMQConfig {
public static final String RECEIVE_QUEUE = "rpc_queue";
public static final String REPLY_QUEUE = "reply_queue";
public static final String RPC_EXCHANGE = "rpc_exchange";
#Bean
public TopicExchange rpcExchange() {
return new TopicExchange(RPC_EXCHANGE);
}
#Bean
public Queue receiveQueue() {
return new Queue(RECEIVE_QUEUE);
}
#Bean
public Queue replyQueue() {
return new Queue(REPLY_QUEUE);
}
#Bean
public Binding receiveBinding() {
return BindingBuilder.bind(receiveQueue()).to(rpcExchange()).with(RECEIVE_QUEUE);
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
Sender sends a message normally, but it can't to deserialize returning message
package org.dneversky.gateway.servie.impl;
#Service
public class UserServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
private RabbitTemplate rabbitTemplate;
public UserPrincipal getUserByUsername(String username) {
UserResponse message = new UserResponse(username);
logger.info("Sending created message: {}", message);
UserResponse result = (UserResponse) rabbitTemplate.convertSendAndReceive(RabbitMQConfig.RPC_EXCHANGE, RabbitMQConfig.RPC_QUEUE, message);
logger.info("Getting response: {}", result);
return null;
}
}
Configuration of the Sender
package org.dneversky.gateway.config;
#Configuration
public class RabbitMQConfig {
public static final String RPC_QUEUE = "rpc_queue";
public static final String REPLY_QUEUE = "reply_queue";
public static final String RPC_EXCHANGE = "rpc_exchange";
#Bean
public Queue rpcQueue() {
return new Queue(RPC_QUEUE);
}
#Bean
public Queue replyQueue() {
return new Queue(REPLY_QUEUE);
}
#Bean
public TopicExchange rpcExchange() {
return new TopicExchange(RPC_EXCHANGE);
}
#Bean
public Binding binding() {
return BindingBuilder.bind(replyQueue()).to(rpcExchange()).with(REPLY_QUEUE);
}
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setExchange(RPC_EXCHANGE);
rabbitTemplate.setReplyAddress(REPLY_QUEUE);
rabbitTemplate.setReplyTimeout(6000);
rabbitTemplate.setMessageConverter(messageConverter());
return rabbitTemplate;
}
#Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public SimpleMessageListenerContainer replyContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(REPLY_QUEUE);
container.setMessageListener(rabbitTemplate(connectionFactory));
return container;
}
}
Error log
2022-05-22 17:12:31.344 ERROR 16920 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [org.dneversky.user.model.UserReplyMessage]] with root cause
java.lang.ClassNotFoundException: org.dneversky.user.model.UserReplyMessage
by default, the producer set the _TypeID_ header as the class name used for the serialization of the object
then consumer uses _TypeID_ header to know the class that should use to convert the JSON to java instance
you use two different classes to serialize and deserialize the object and you have to configure the converter
inside your replyContainer I couldn't see your messageConverter bean. In default it uses java objects to send and receive messages without converting them into human readable json.
#Bean
public SimpleRabbitListenerContainerFactory customListenerContainerFactory(ConnectionFactory connectionFactory,
MessageConverter jsonMessageConverter) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(jsonMessageConverter);
return factory;
}
for your consumer;
#RabbitListener(queues = RabbitConstants.YOUR_QUEUE_NAME, containerFactory = "customListenerContainerFactory")
public void onMessage(#Valid YourEvent YourEvent){
//your code
}
Inside the Listener class, you need to add this line to bind your message converter
#Bean
public SimpleRabbitListenerContainerFactory jsaFactory(ConnectionFactory connectionFactory,
SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(jsonMessageConverter());
return factory;
}
Also, in the TestListener class, you should replace this line
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE)
with this one
#RabbitListener(queues = RabbitMQConfig.RECEIVE_QUEUE,containerFactory="jsaFactory")

Azure Service Bus queues dynamic Spring

I have a challenge to set up a service in SpringBoot, where it will be listening to several queues. I searched a lot and couldn't find what I was looking for. I have queues, which can grow dynamically.
Exemple: queue-1, queue-2, queue-3...
What could I use in this service to get this service up by listening to these queues dynamically?
Using spring JMS you can do this like it is done here
Your config file is like this:
#Configuration
#EnableJms
public class AppConfig implements JmsListenerConfigurer {
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
List<QueueInformation> queueInformationList = consumersStatic.getQueueInformationList();
int i = 0;
for (QueueInformation queueInformation :
queueInformationList) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint-" + i++);
endpoint.setDestination(queueInformation.getMqQueueName());
endpoint.setMessageListener(message -> {
logger.debug("***********************************************receivedMessage:" + message);
});
registrar.registerEndpoint(endpoint);
logger.debug("registered the endpoint for queue" + queueInformation.getMqQueueName());
}
}
Another way is using RabbitListenerConfigurer. You can get more idea from here
code from this link:
for rabbitconfig:
#Configuration
public class RabbitMqConfiguration implements RabbitListenerConfigurer {
#Autowired
private ConnectionFactory connectionFactory;
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
#Bean
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
return rabbitTemplate;
}
#Bean
public RabbitAdmin rabbitAdmin() {
return new RabbitAdmin(connectionFactory);
}
#Bean
public RabbitListenerEndpointRegistry rabbitListenerEndpointRegistry() {
return new RabbitListenerEndpointRegistry();
}
#Bean
public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(consumerJackson2MessageConverter());
return factory;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
#Override
public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setPrefetchCount(1);
factory.setConsecutiveActiveTrigger(1);
factory.setConsecutiveIdleTrigger(1);
factory.setConnectionFactory(connectionFactory);
registrar.setContainerFactory(factory);
registrar.setEndpointRegistry(rabbitListenerEndpointRegistry());
registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}
}
service you can find here

How Test a Mqtt Client with Junit without run it but with a mock?

I have doing a mqtt client with org.springframework.integration.mqtt.core.MqttPaho follow this guide: Spring Mqtt Support and work correctly, I read from a topic and write in another topic.
Now I want test it with Junit5+mockito.. I don't understand well guide in internet.. in particular how mock producer or consumer.. I use configuration for consumer and producer class for example:
#Configuration
public class Consumer {
#Autowired MqttPahoClientFactory mqttClientFactory;
#Bean
public MessageChannel topicChannel(){
return new DirectChannel();
}
#Bean
public MessageProducer mqttInbound() {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
Parameters.MQTT_CLIENT_ID, mqttClientFactory, Parameters.TOPICS[0]);
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(1);
adapter.setOutputChannel(topicChannel());
return adapter;
}
}
and
#Configuration
public class Producer {
#Autowired MqttPahoClientFactory mqttClientFactory;
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface ProducerGateway {
void sendToMqtt(String data, #Header(MqttHeaders.TOPIC) String topic);
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
Parameters.MQTT_CLIENT_ID,
mqttClientFactory);
messageHandler.setAsync(true);
messageHandler.setLoggingEnabled(true);
return messageHandler;
}
}
#Configuration
public class MqttConfiguration {
#Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(Parameters.BROKER_URIs);
options.setUserName(Parameters.MQTT_USERNAME);
options.setPassword(Parameters.MQTT_PASSWORD.toCharArray());
factory.setConnectionOptions(options);
return factory;
}
}

How to setup a socket client with spring-integration?

I'm using spring 4 annotation based configuration, and would like to set up a simple telnet/socket client.
This is what I have so far:
#MessageEndpoint
public class MySocket {
#Bean
public TcpConnectionFactoryFactoryBean clientFactory() {
TcpConnectionFactoryFactoryBean fact = new TcpConnectionFactoryFactoryBean();
fact.setType("client");
fact.setHost(host);
fact.setPort(port);
fact.setUsingNio(true);
fact.setSingleUse(true);
fact.setSoTimeout(timeout);
return fact;
}
#Bean
public MessageChannel clientChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "clientChannel")
public TcpOutboundGateway outGateway(TcpNioClientConnectionFactory factory,
#Qualifier("clientChannel") MessageChannel clientChannel) throws Exception {
TcpOutboundGateway gate = new TcpOutboundGateway();
gate.setConnectionFactory(factory);
gate.setReplyChannel(clientChannel);
return gate;
}
}
#Component
public class MyMessageService {
#Autowired
#Qualifier("clientChannel")
private MessageChannel clientChannel;
public void run() {
Message<String> msg = MessageBuilder.withPayload("test").build();
Message<?> rsp = new MessagingTemplate(clientChannel).sendAndReceive(msg);
}
}
Result: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
What am I missing here to send the message via the socket and receive the reply?
You don't need the #MessageEndpoint annotation, but you need a consumer on the channel...
#ServiceActivator(inputChannel = "clientChannel")
#Bean
public TcpOutboundGateway outGateway(AbstractClientConnectionFactory scf) {
...
}
The gateway needs a reference to the connection factory. Since you are using a factory bean, it's easiest to add it as a parameter to the bean's factory method.

Spring-batch #BeforeStep does not work with #StepScope

I'm using Spring Batch version 2.2.4.RELEASE
I tried to write a simple example with stateful ItemReader, ItemProcessor and ItemWriter beans.
public class StatefulItemReader implements ItemReader<String> {
private List<String> list;
#BeforeStep
public void initializeState(StepExecution stepExecution) {
this.list = new ArrayList<>();
}
#AfterStep
public ExitStatus exploitState(StepExecution stepExecution) {
System.out.println("******************************");
System.out.println(" READING RESULTS : " + list.size());
return stepExecution.getExitStatus();
}
#Override
public String read() throws Exception {
this.list.add("some stateful reading information");
if (list.size() < 10) {
return "value " + list.size();
}
return null;
}
}
In my integration test, I'm declaring my beans in an inner static java config class like the one below:
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class SingletonScopedTest {
#Configuration
#EnableBatchProcessing
static class TestConfig {
#Autowired
private JobBuilderFactory jobBuilder;
#Autowired
private StepBuilderFactory stepBuilder;
#Bean
JobLauncherTestUtils jobLauncherTestUtils() {
return new JobLauncherTestUtils();
}
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder embeddedDatabaseBuilder = new EmbeddedDatabaseBuilder();
return embeddedDatabaseBuilder.addScript("classpath:org/springframework/batch/core/schema-drop-hsqldb.sql")
.addScript("classpath:org/springframework/batch/core/schema-hsqldb.sql")
.setType(EmbeddedDatabaseType.HSQL)
.build();
}
#Bean
public Job jobUnderTest() {
return jobBuilder.get("job-under-test")
.start(stepUnderTest())
.build();
}
#Bean
public Step stepUnderTest() {
return stepBuilder.get("step-under-test")
.<String, String>chunk(1)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
#Bean
public ItemReader<String> reader() {
return new StatefulItemReader();
}
#Bean
public ItemProcessor<String, String> processor() {
return new StatefulItemProcessor();
}
#Bean
public ItemWriter<String> writer() {
return new StatefulItemWriter();
}
}
#Autowired
JobLauncherTestUtils jobLauncherTestUtils;
#Test
public void testStepExecution() {
JobExecution jobExecution = jobLauncherTestUtils.launchStep("step-under-test");
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
}
}
This test passes.
But as soon as I define my StatefulItemReader as a step scoped bean (which is better for a stateful reader), the "before step" code is no longer executed.
...
#Bean
#StepScope
public ItemReader<String> reader() {
return new StatefulItemReader();
}
...
And I notice the same issue with processor and my writer beans.
What's wrong with my code? Is it related to this resolved issue: https://jira.springsource.org/browse/BATCH-1230
My whole Maven project with several JUnit tests can be found on GitHub: https://github.com/galak75/spring-batch-step-scope
Thank you in advance for your answers.
When you configure a bean as follows:
#Bean
#StepScope
public MyInterface myBean() {
return new MyInterfaceImpl();
}
You are telling Spring to use the proxy mode ScopedProxyMode.TARGET_CLASS. However, by returning the MyInterface, instead of the MyInterfaceImpl, the proxy only has visibility into the methods on the MyInterface. This prevents Spring Batch from being able to find the methods on MyInterfaceImpl that have been annotated with the listener annotations like #BeforeStep. The correct way to configure this is to return MyInterfaceImpl on your configuration method like below:
#Bean
#StepScope
public MyInterfaceImpl myBean() {
return new MyInterfaceImpl();
}
We have added a warning log message on startup that points out, as we look for the annotated listener methods, if the object is proxied and the target is an interface, we won't be able to find methods on the implementing class with annotations on them.
as suggested by pojo-guy
Solution is to implement StepExecutionListener and Override beforeStep method to set stepExecution
#Override
public void beforeStep(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}

Categories