Spring Integration Email Redelivery on Exception - email

I have a web service that via a GET Http method, the user requests for a person object. This person is sent to a JMS Queue and then with the help of Spring Integration, I send it to a fake email address (https://papercut.codeplex.com/). I have written the code with Spring Integration Java DSL. I would like to ask:
Is there a more flexible way to send the email message?
If an exception is thrown, how can the mail be redelivered with the help of Spring Integration? (e.g. for 5 times and if it is not sent then the exception gets handled and the program stops)
Here is my code:
Web Service
public Person findById(Integer id) {
Person person = jpaPersonRepository.findOne(id);
jmsTemplate.convertAndSend("testQueue", person);
return jpaPersonRepository.findOne(id);
}
Java Confiuration
#Configuration
#EnableIntegration
#ComponentScan
public class JavaConfig {
private static final String DEFAULT_BROKER_URL = "tcp://localhost:61616";
private static final String DEFAULT_QUEUE = "testQueue";
#Bean
public ActiveMQConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(DEFAULT_BROKER_URL);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(this.connectionFactory());
template.setDefaultDestinationName(DEFAULT_QUEUE);
return template;
}
#Bean
public DefaultMessageListenerContainer defaultMessageListenerContainer() {
DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setDestinationName(DEFAULT_QUEUE);
defaultMessageListenerContainer.setConnectionFactory(this.connectionFactory());
return defaultMessageListenerContainer;
}
#Bean(name="inputChannel")
public DirectChannel directChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow orders() {
return IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(defaultMessageListenerContainer()))
.transform(new ObjectToStringTransformer())
.enrichHeaders(p -> p.header(MailHeaders.TO, "Papercut0#test.com"))
.handle(Mail.outboundAdapter("127.0.0.1")
.credentials("test","test").port(25)
.javaMailProperties(p -> p.put("mail.debug", "true")),
e -> e.id("sendMailEndpoint"))
.get();
}
}

Is there a more flexible way to send the email message?
Sorry, the question isn't clear. You have enough short code to do that. Mail.outboundAdapter() and all its fluent API. What should be more flexible?
If an exception is thrown, how can the mail be redelivered with the help of Spring Integration?
For this purpose Spring Integration suggests RequestHandlerRetryAdvice. And Mail.outboundAdapter() can be configured with that as:
#Bean
public Advice retryAdvice() {
RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice();
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(retryPolicy);
advice.setRetryTemplate(retryTemplate);
advice.setRecoveryCallback(new ErrorMessageSendingRecoverer(emailErrorChannel()));
return advice;
}
...
.handle(Mail.outboundAdapter("127.0.0.1")
.credentials("test","test").port(25)
.javaMailProperties(p -> p.put("mail.debug", "true")),
e -> e.id("sendMailEndpoint")
.advice(retryAdvice())) // HERE IS THE TRICK!
See its JavaDocs and Reference Manual on the matter.

Related

Consuming SOAP Web Services with ActiveMQ in Spring Boot

I am new to Web Service development, currently building a SOAP Web Service with Spring Boot 2.7.0, Java 17.
As well as a client application that communicates with this soap service via JMS.
But I do not know the procedure of the process.
The way I see it -> The client application (Producer) sends a message to a queue that lives on the server side (Consumer), the queue pops the message when ready to consume and redirects it to the endpoint handler method and then sends a response in the response queue back to the client side.
However, I don't know how to redirect the JMS message to the endpoint. Nor do I know how to send it back. I have read all of the documentations related to "SOAP over JMS", CXF-SOAP-JMS", "ActiveMQ with Spring", etc... None of them helped me fix this problem.
Using SOAP with http is pretty easy by exploiting the "WebServiceTemplate", provided by Spring-WS API. But when I tried using it over JMS I encountered several problems, including the following:
What to do with the JMS Message once in the destination object?
How do I send it specifically to my endpoint handler method?
What and how do I send back to the response destination?
Sample code of what I've tried latest
CLIENT APP
Client Configuration
#Configuration
public class SoapClientConfiguration {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.mile.soap.client.app.quiz");
return marshaller;
}
#Bean
WebServiceTemplate template() {
return new WebServiceTemplate(jaxb2Marshaller());
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
#Bean
public JmsTemplate jmsTemplate(){
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(mqConnectionFactory());
return template;
}
Client Service
#Service
public class SoapClient extends WebServiceGatewaySupport{
#Autowired WebServiceTemplate template;
#Autowired JmsTemplate jmsTemplate;
public CategoriesResponse getCategories() {
CategoriesResponse response = new CategoriesResponse();
try {
SAAJResult soapRequest = new SAAJResult();
template.getMarshaller().marshal(new GetCategoriesRequest(), soapRequest);
Message m = jmsTemplate.sendAndReceive("example.queue", new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(soapRequest.toString());
}
});
response = m.getBody(CategoriesResponse.class);
}
catch (Exception e) {
e.printStackTrace();
}
return response;
}
SERVER SIDE APP
ActiveMQ Configuration
#Configuration #EnableJms
public class ActiveMqConfig {
#Value("${spring.activemq.broker-url}")
private String activeMqUrl;
#Value("${spring.activemq.user}")
private String userName;
#Value("${spring.activemq.password}")
private String password;
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(mqConnectionFactory());
return factory;
}
#Bean
public SingleConnectionFactory mqConnectionFactory(){
SingleConnectionFactory factory = new SingleConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
mqConnectionFactory.setBrokerURL(activeMqUrl);
mqConnectionFactory.setUserName(userName);
mqConnectionFactory.setPassword(password);
factory.setTargetConnectionFactory(mqConnectionFactory);
return factory;
}
}
Main Configuration (WSDL/SERVLET)
#Configuration
#EnableWs
public class SoapConfiguration extends WsConfigurerAdapter{
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus(){
SpringBus bus = new SpringBus();
return bus;
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(
ApplicationContext applicationContext, SpringBus springBus){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/*");
}
//wsdl
#Bean(name = "quiz") #SneakyThrows
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema schema) {
DefaultWsdl11Definition defaultWsdl11Definition = new DefaultWsdl11Definition();
defaultWsdl11Definition.setPortTypeName("QuizMainEndPoint");
defaultWsdl11Definition.setLocationUri("/");
defaultWsdl11Definition.setTargetNamespace("http://www.mile.com/collection/management/soap/Quiz");
defaultWsdl11Definition.setTransportUri("http://www.openuri.org/2002/04/soap/jms/");
defaultWsdl11Definition.setSchema(schema);
return defaultWsdl11Definition;
}
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
EndpointInterceptor endpointInterceptor = new PayloadRootSmartSoapEndpointInterceptor(
new QuizMainEndpointInterceptor(), "http://www.mile.com/collection/management/soap/Quiz", "GetCategoriesRequest");
interceptors.add(endpointInterceptor);
}
#Bean
public XsdSchema schema() {
return new SimpleXsdSchema(new ClassPathResource("/schemas/QuizSchema/quiz.xsd"));
}
}
Listener
#Component
public class Listener {
#JmsListener(destination = "example.queue")
public void listenRequests(Message message) {
System.out.println(message.toString());
/*I RECEIVE THE MESSAGE BUT I HAVE NO IDEA WHAT TO DO WITH IT.
* HOW DO I CONSUME IT?
*/
}
}
Method in a class annotated with #Endpoint
#ResponsePayload
#PayloadRoot(namespace = NAMESPACE, localPart = "GetCategoriesRequest")
public CategoriesResponse getCategories( #RequestPayload GetCategoriesRequest request) {
CategoriesResponse response = new CategoriesResponse(service.getCategories());
/*
* How to CONVERT my JMS Message living in the Destination Object - "example.queue" To a SOAP Message
* and be RECOGNISED by this exact method??
Do i send a JMS response here or somewhere else?
Is it sent by default?
*/
return response;
}
Thank you for reading thoroughly. I'd appreciate any kind of help.

Spring Cloud Stream unit test Producer

I'm using Spring Cloud Stream 3.1.3.
I'm migrate from a pre 3.1 version, so I wrote my producer using a java.util.Function (I know I can use Supplier but this is what I need)
application.yaml file is configured with function definition, input and output bindings, and this is what I have:
#EnableAutoConfiguration
#Service
public class Producer {
public void produce(int messageId, Object data) {
Message<Object> message = MessageBuilder
.withPayload(data)
.setHeader(PARTITION_KEY, messageId)
.build();
streamBridge.send("produceMessage-in-0", message);
}
#Bean
public Function<Message<Object>, Message<Object>> produceMessage() {
return (input) -> {
int messageId = input.getHeaders().get(PARTITION_KEY, Integer.class);
Object message = input.getPayload();
return MessageBuilder
.withPayload(message)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.setHeader(PARTITION_KEY, messageId)
.setHeader("type", "MyMessage")
.build();
};
}
}
Now, I would like to test this implmentation, so I wrote this test class
#SpringBootTest
class ProducerTest {
#Autowired
private Producer producer;
#Autowired
private ObjectMapper objectMapper;
#Test
void produceOk() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestChannelBinderConfiguration.getCompleteConfiguration(Producer.class)).run()) {
producer.produce(1, new MyMessage(1, "Hello"));
OutputDestination output = context.getBean(OutputDestination.class);
Message<byte[]> received = output.receive();
Assertions.assertNotNull(received);
}
}
}
Test fails because output.receive() returns null.
Is this the right way to test my code?
Thanks
It is difficult to see what your issue may be since we don't see the entire setup of your project, but here are few pointers that may help. . .
Please look at any of the tests we use in the framework as well as checkout the Testing section of the reference manual.
There is also s dedicated StreamBridgeTests.java which is what I believe you are looking for.

Spring Cloud Stream and Kafka Integration Error Handling

I am trying to create a Spring Boot application with Spring Cloud Stream and Kafka integration. I created a sample Topic in Kafka with 1 partition and have published to the topic from the Spring Boot application created based on the directions given here
http://docs.spring.io/spring-cloud-stream/docs/1.0.2.RELEASE/reference/htmlsingle/index.html
and
https://blog.codecentric.de/en/2016/04/event-driven-microservices-spring-cloud-stream/
Spring Boot App -
#SpringBootApplication
public class MyApplication {
private static final Log logger = LogFactory.getLog(MyApplication.class);
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Kafka Producer Class
#Service
#EnableBinding(Source.class)
public class MyProducer {
private static final Log logger = LogFactory.getLog(MyProducer.class);
#Bean
#InboundChannelAdapter(value = Source.OUTPUT, poller = #Poller(fixedDelay = "10000", maxMessagesPerPoll = "1"))
public MessageSource<TimeInfo> timerMessageSource() {
TimeInfo t = new TimeInfo(new Timestamp(new Date().getTime())+"","Label");
MessageBuilder<TimeInfo> m = MessageBuilder.withPayload(t);
return () -> m.build();
}
public static class TimeInfo{
private String time;
private String label;
public TimeInfo(String time, String label) {
super();
this.time = time;
this.label = label;
}
public String getTime() {
return time;
}
public String getLabel() {
return label;
}
}
}
All is working well except for when I want to handle exceptions.
If the Kafka Topic went down, I can see the ConnectionRefused exception being thrown in the log files for the app, but the retry logic built in seems to be going at retrying continuously without stopping!
There is no exception thrown at all for me to handle and do further exception processing. I have read through the Producer options and the Binder options for Kafka in the Spring Cloud Stream documentation above and I cannot see any customization options possible to get this exception thrown above all the way for me to capture.
I am new to Spring Boot / Spring Cloud Stream / Spring Integration (which seems to be the underlying implementation to the cloud stream project).
Is there anything else you guys know to get this exception cascaded to my Spring Cloud Stream app?

Creating a new Queue using Spring AMQP on web application startup

I am trying to create a new queue in RabbitMQ using Spring AMQP on server startup of my web application. I am not getting the exact configuration code how to achieve it.
Below is my code snippet. Please correct the following.
#Configuration
public class RabbitMQConfiguration {
#Bean
public ConnectionFactory rabbitConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("10.165.18.29");
connectionFactory.setUsername("User");
connectionFactory.setPassword("user");
return connectionFactory;
}
#Bean
public SimpleMessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConnectionFactory());
container.addQueueNames("create.queue");
container.setMessageListener(exampleListener());
return container;
}
#Bean
public MessageListener exampleListener() {
return new MessageListener() {
public void onMessage(Message message) {
System.out.println("received: " + message);
}
};
}
}
See the documentation.
Simply add <rabbit:queue ... /> beans and a <rabbit:admin ... /> and the admin will automatically declare the queues when the connection is first established.

how to set queue/message durability to false in Spring AMQP using annotations?

I wrote sample spring amqp producer which is running on RabbitMQ server which sends messages and consuming those messages uisng MessageListener using Spring AMQP. Here, I want to set queue and message durability to false. Could you please any one help me on how to set "durable" flag to false using annotations.
Here is sample code
#Configuration
public class ProducerConfiguration {
protected final String queueName = "hello.queue";
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setRoutingKey(this.queueName);
template.setQueue(this.queueName);
return template;
}
#Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
}
public class Producer {
public static void main(String[] args) throws Exception {
new Producer().send();
}
public void send() {
ApplicationContext context = new AnnotationConfigApplicationContext(
ProducerConfiguration.class);
RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
for (int i = 1; i <= 10; i++) {
rabbitTemplate.convertAndSend(i);
}
}
}
Thanks in Advance.
#Configuration
public class Config {
#Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory();
}
#Bean
public Queue foo() {
return new Queue("foo", false);
}
#Bean
public RabbitAdmin rabbitAdmin() {
return new RabbitAdmin(connectionFactory());
}
}
The rabbit admin will declare the queue the first time the connection is opened. Note that you can't change a queue from durable to not; delete it first.