How to catch an error writing to the Kafka topic?
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Processor;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Component;
import javax.xml.bind.JAXBException;
#Component
#EnableBinding(Processor.class)
public class Parser {
#StreamListener(Processor.INPUT)
#SendTo(Processor.OUTPUT)
public String process(Message<?> input) {
// do smth
// how to catch an error if sending message to 'output' topic fails
}
}
Switch producer to synchronous mode.
spring.cloud.stream.kafka.bindings.output.producer.sync=true
What next? Any examples? Extend some binding impl, add some AOP magic?
I think what you need is to register a KafkaProducerListener which handles the error scenario in your case and make it available as a bean in your application.
For more info on KafkaProducerListener is here
Also, note that usually the failed messages would be available on errorChannel with the channel name error. All the error messages are published once you have the configure the destination name for the error channel: spring.cloud.stream.bindings.error.destination.
Related
I am having trouble getting messages from a locally run ActiveMQ. I can produce them onto the queue and my PC also is registered as producer. However, another Spring App on the machine should be configured as a listener. So far it is not working. ActiveMQ is listening on the default ports.
My JMS config for the sender:
package at.dkepr.queueservice;
import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.jms.Queue;
#Configuration
public class JmsConfig {
#Bean
public Queue queue(){
return new ActiveMQQueue("indexing-queue");
}
}
And this is the consumer:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import at.dkepr.entity.UserSearchEntity;
#Component
#EnableJms
public class JmsConsumer {
private final Logger logger = LoggerFactory.getLogger(JmsConsumer.class);
#JmsListener(destination = "indexing-queue", containerFactory = "jmsListenerContainerFactory")
public void receive(UserSearchEntity user){
logger.info(user.getEmail());
}
}
In the application.propertiers I have added the necessary properties:
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin
Also the UserSearchEntity implements Serializable.
To the best of my knowledge for this setup I should not even need a config for the consumer. Never the less, I added one.
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
#Configuration
#EnableJms
public class ConsumerConfig {
#Value("${spring.activemq.broker-url}")
private String brokerUrl;
#Bean
public ActiveMQConnectionFactory activeMQConnectionFactory() {
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
activeMQConnectionFactory.setBrokerURL(brokerUrl);
return activeMQConnectionFactory;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(activeMQConnectionFactory());
factory.setConcurrency("1-3");
return factory;
}
}
I am not getting any error logs. Unfortunately, it is simply doing nothing.
This is a screenshot from the ActiveMQ web console with the enqueued messages:
My consuming application was running when I took this screenshot, but the broker clearly does not recognize it since the "Number of Consumers" is 0.
Edit:
I just tried adding the Listener to the same Spring Application where the Producer is. Surprinsingly, the Listener connected fine. It seems like the problem lies in the different Spring Applications. However, i used the same application.properties for both Spring Apps. The Config File is the same too.
To everyone having the same problem:
For me it was a simple problem with folder structure. For some reason the Application.java for the consumer service was in a subfolder. After i moved the Application.java one folder up, the connection to the ActiveMQ worked.
I have a SpringBoot application. I have a dedicated server, where I shall read data by the HTTP GET requests. I configured the http-outbound-config.xml file for Spring Integration module.
When I run the following code, everything is fine:
http-outbound-config.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-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http https://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int:channel id="requestChannel"/>
<int:channel id="replyChannel">
<int:queue capacity='10'/>
</int:channel>
<int-http:outbound-gateway id="outboundGateway"
request-channel="requestChannel"
url="http://server/API.jsp?id=1"
http-method="GET"
expected-response-type="java.lang.String"
charset="UTF-8"
reply-channel="replyChannel"/>
</beans>
Main Application Class:
package test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import ru.eco.products.waste.egr.Integration;
#SpringBootApplication
#ImportResource("/META-INF/spring/integration/http-outbound-config.xml")
public class Application {
public static void main(String[] args) {
Integration integration = new Integration();
integration.start();
SpringApplication.run(WasteWebClientApplication.class,
args
);
}
}
Integration class:
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
public void start() {
ClassPathXmlApplicationContext
context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml");
context.start();
MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class);
PollableChannel replyChannel = context.getBean("replyChannel", PollableChannel.class);
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
BUT, when I try to Autowire MessageChannel and PollableChannel, I receive a null pointer exception.
Integration class(not working example):
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
#Autowired
#Qualifier("requestChannel")
MessageChannel requestChannel;
#Autowired
#Qualifier("replyChannel")
PollableChannel replyChannel;
public void start() {
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
Question 1: Why Autowiring is not working?
Question 2: What is the best way to get data from dedicated server and save it into DB? Such config is ok? I will create a model class for the response and after will save it into DB via JPA.
Question 3: I need reading from server works in Async mode. How can I implement it in Spring Boot? So the main idea here is that I will receive a POST method from the UI, and will launch the integration with web-service. After integration will be finished, I need to notify user.
Question 4: Maybe Camel will be the best solution here?
Stack: Java 11, Spring Boot, thymeleaf + bootstrap.
Thank you for the answer in advance.
Since you do new Integration();, you definitely not going to have dependency injection since inversion of control container is not involved. Although it is fully not clear why do you need that new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml") if you already do Spring Boot and that proper #ImportResource.
The best way as you already do with the #ImportResource for that Spring Integration XML config. Then you need to get access to the ApplicationContext of the SpringApplication.run() and getBean(Integration.class) to call your start() method. However you fully need to forget about new Integratio(). Spring is going to manage a bean for that Integration and then dependency injection is going to work.
Async mode can be achieved with an ExecutorChannel in Spring Integration. So, when you send a message to this channel, the logic is going to be processed on a different thread. However it is not clear why you state such a requirement since you still going to block via that replyChannel.receive()... Although this should be addressed in a separate SO thread.
Camel VS Spring Integration question is out of StackOverflow policies.
Thymeleaf and Bootstrap are misleading in the context of this question.
Please, consider to learn how to ask properly here on SO: https://stackoverflow.com/help/how-to-ask
Also read some docs about dependency injection and inversion of control: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
I created a simple standalone consumer, trying to consume 4 messages sitting on the ActiveMQ. But when I started the application, it created another Queue with the same name as shown in the image below:
My Project Structure looks like this:
And code inside classes looks like the following:
class FebMessageConsumer
package com.consumer.messages.febMessageConsumer;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
#Component
public class FebMessageConsumer {
#JmsListener(destination = "CDD Feb 21 Queue")
///#JmsListener
public void processFebMessage(String message) {
System.out.println("Message Retrieved is:" +message);
}
}
class FebMessageConsumerApplication
package com.consumer.messages.febMessageConsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jms.annotation.EnableJms;
#SpringBootApplication
#EnableJms
public class FebMessageConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FebMessageConsumerApplication.class, args);
}
}
1) What's wrong in the above code?
2) Once I get above thing working,I plan on deploying it as a WAR to Apache Tomcat 8.5. Is it like when I deploy the application or start the application as Java Application, it's going to consume all the messages one by one? OR when I start the application, only one message will be consumed at a time and then I'll have to stop the application and then start again to consume next message?
Here's a Google Drive Link to the zipped project in case needed for reference.
The existing queue name includes quotes.
Use #JmsListener(destination = "\"CDD Feb 21 Queue\"").
It will continually receive messages one by one.
I need a component that:
Receives a message, enacts a transformation on both the payload and header of the message (so far it acts like a transformer).
Then, based on the values passed in on the header route to an appropriate channel (acting like a header-router).
I'd like to configure this purely using annotations in java rather than XML, but I'll absolutely take what I can get in that regard. If someone knows the XML solution please pass it along.
If it helps, my use case is that I want the transformation that the transformer enacts on a message payload to be dependent on a custom loaded header value. I also want the channel that is used to sent the message from the transformer to be dependent on the header value.
I am aware of the solution that involves multiple transformers, one per transformation type as defined in the header value, and then a router. I'm trying to avoid this solution and only use at most a single router and single transformer.
Thank you for your help.
In Spring Integration channels act as any other beans. You could use a service activator to invoke a method on any bean. That bean could have the required channels injected. You could use the #Qualifier annotation to select, which channel should be injected, or just autowire a Map<String, MessageChannel> wich would get indexed by the bean name of the channel. It could pass transformed messages to those channels.
A Spring Boot app:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.config.EnableIntegration;
#SpringBootApplication
#EnableIntegration
#ImportResource("classpath:int.xml")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The gateway interface:
package demo;
public interface MyGateway {
public void send(Object o);
}
The service to be invoked:
package demo;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;
#Service
public class MyService {
private final Map<String, MessageChannel> channels;
#Autowired
public MyService(Map<String, MessageChannel> channels) {
super();
this.channels = channels;
}
public void transformAndRoute(Message<?> in) {
// do your business logic here
Message<?> out = MessageBuilder.fromMessage(in).build();
// if(something)...
channels.get("fooChannel").send(out);
}
}
Integration config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd">
<int:gateway id="myGateway" service-interface="demo.MyGateway"
default-request-channel="inChannel" />
<int:service-activator input-channel="inChannel"
ref="myService" method="transformAndRoute" />
<int:channel id="inChannel" />
<int:logging-channel-adapter id="fooChannel" level="INFO" log-full-message="true"/>
</beans>
And finally a simple integration test:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=DemoApplication.class)
public class MyGatewayIT {
#Autowired
MyGateway myGateway;
#Test
public void test() {
myGateway.send(new Object());
// do your assertions here
}
}
Output:
14:17:19.733 [main] DEBUG o.s.i.handler.LoggingHandler - org.springframework.integration.handler.LoggingHandler#0 received message: GenericMessage [payload=java.lang.Object#6f2cfcc2, headers={id=6791344c-07b4-d420-0d17-e2344f4bf15b, timestamp=1437826639733}]
BUT
The main benefit of developing message based systems is that you create your application out of small, loosely coupled components that are easy to test, reuse and change. Creating a component that plays two roles in a process brakes that rule. Additionally your code is realy tied to Spring Integration if you create it as above. What you could do instead is create a couple of components that each have a single responsibility, and then configure them to act in a chain.
What you could do is create a transformer that modifies the payload and headers of the message. That transformer would encapsulate your business logic and would set a header (say, myRoutingHeader) that can be later used in routing. It would probably be even better to have a transformer for business logic, and a header enricher for adding the header. But let's assume that you are doing it in a single class. Define that class as a bean (say myTransformer). Then in your config :
<int:channel id="inChannel/>
<!-- if performance is important consider using a SpEL expression to
invoke your method instead as they can be configured to be compiled -->
<int:transformer ref="myTransformer" input-channel="inChannel"
method="transform" output-channel="routingChannel"/>
<int:channel id="routingChannel/>
<int:header-value-router input-channel="routingChannel" header-name="myRoutingHeader">
<int:mapping value="foo" channel="fooChannel" />
<int:mapping value="bar" channel="barChannel" />
</int:header-value-router>
sounds like chaining will do it for you
<chain input-chanel="source" output-channel="routing-channel">
<transform/>
<!-- any enrichment process or intermediate process can go here -->
</chain>
I'm currently using Oracle AQ and would like to replace it by a persisted ActiveMQ.
My current setup using Oracle AQ is:
DB server: Oracle DB with a queue Q1
App server 1: Has a producer and multiple listeners on Q1
App server 2: Has a producer and multiple listeners on Q1
The following flow is currently followed:
App server 1:
Incoming message via a webservice
Start DB transaction
Save message in DB with id
Post id and other information of the message on queue Q1
commit transaction
App server 2:
Same setup, horizontal scaled
Requirements
When implementing ActiveMQ I want the data to the DB and the post on the queue in the same transaction. So that if one does a rollback, the other will do it as well.
Because I need to be able to produce messages on the queue with both app-servers at the same time, I need to run the ActiveMQ broker on the DB-server, and not on the app-servers. Otherwise they will act as a 'master slave'.
I an article I read, they explain how you can share transaction resources.
But this is done assuming you put the ActiveMQ broker on the same server as where the transaction is started.
Is there any way, except using JTA to accomplish this?
I'm using Java with:
Spring 2.5.6
Hibernate 3.3
TransactionManager: org.springframework.orm.hibernate3.HibernateTransactionManager
DataSource: oracle.jdbc.pool.OracleDataSource
I didn't test this. But isn't this what you're looking for?
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.JmsException;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
private Queue someQueue;
private JmsTemplate jmsTemplate;
private SomeRepository repository;
public SomeServiceImpl() {}
public SomeServiceImpl(ConnectionFactory activeMQConnectionFactory, SomeRepository repository) {
this.jmsTemplate = new JmsTemplate(activeMQConnectionFactory);
this.repository = repository;
}
#Transactional(rollbackFor = {JmsException.class, RepositoryException.class})
public void sendMessage(final SomeObject object) {
repository.save(object);
jmsTemplate.send(
new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(object.getSpecialId());
}
});
}
}
This way if there is a failure while saving the object or an issue with sending the message to the queue (JMSException), the local transaction will be rolled back and the object won't be persisted. I don't think you will be needing a distributed transaction.