I have a simple service sending emails. It can be invoked using REST and JMS APIs. I want the requests to be validated before processing.
When I invoke it using REST I can see that org.springframework.validation.DataBinder invokes void validate(Object target, Errors errors, Object... validationHints) and then validator from Hibernate is invoked. This works as expected.
The problem is I can't achieve the same effect with JMS Listener. The listener is implemented as follows:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import our.domain.mailing.Mailing;
import our.domain.mailing.jms.api.SendEmailFromTemplateRequest;
import our.domain.mailing.jms.api.SendSimpleEmailRequest;
import javax.validation.Valid;
#ConditionalOnProperty("jms.configuration.destination")
#Component
#AllArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class SendMailMessageListener {
Mailing mailing;
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmailUsingTemplate(#Valid SendEmailFromTemplateRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmailTemplate(
request.getEmailDetails().getRecipients(),
request.getEmailDetails().getAccountType(),
request.getTemplateDetails().getTemplateCode(),
request.getTemplateDetails().getLanguage(),
request.getTemplateDetails().getParameters());
}
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmail(#Valid SendSimpleEmailRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmail(
request.getRecipients(),
request.getSubject(),
request.getMessage());
}
}
The methods receive payloads but they are not validated. It's Spring Boot application and I have #EnableJms added. Can you guide what part of Spring source code is responsible for discovering #Validate and handling it? If you have any hints on how to make it running I would appreciate it a lot.
The solution is simple and was clearly described in official documentation: 29.6.3 Annotated endpoint method signature. There are few things you have to do:
Provide configuration implementing JmsListenerConfigurer (add #Configuration class implementing this interface)
Add annotation #EnableJms on the top of this configuration
Create bean DefaultMessageHandlerMethodFactory. It can be done in this configuration
Implement method void configureJmsListeners(JmsListenerEndpointRegistrar registrar) of interface JmsListenerConfigurer implemented by your configuration and set MessageHandlerMethodFactory using the bean you've just created
Add #Validated instead of #Valid to payload parameters
You can use #Valid in your listeners. Your answer was very close to it. In the step when you create DefaultMessageHandlerMethodFactory call .setValidator(validator) where validator is from org.springframework.validation. You can configure validator like this:
#Bean
public LocalValidatorFactoryBean configureValidator ()
{
return new LocalValidatorFactoryBean();
}
And then inject validator instance into your jms config
Related
I followed the rest client guide in Quarkus web site. It works fine. But when registering a global provider using the ServiceLoader pattern, as described in the specification, the CDI beans injection did not work, they are all null. I downloaded the example and simply added the following classes:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.Response;
#ApplicationScoped
public class MyExceptionMapper implements ResponseExceptionMapper<Exception> {
#Override
public Exception toThrowable (Response response) {
return new Exception();
}
}
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
#ApplicationScoped
public class MyListener implements RestClientBuilderListener {
#Inject MyExceptionMapper myExceptionMapper;
#Override
public void onNewBuilder (RestClientBuilder builder) {
builder.register(myExceptionMapper);
}
}
I also added the file META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener with the content org.acme.rest.client.MyListener. The MyListener onNewBuilder method is invoked, but the injected provider MyExceptionMapper is null. How to register a global provider in Quarkus client?
Implementation of RestClientBuilderListener are not CDI beans - they are just objects that are created via the normal Java ServiceLoader mechanism when RestClientBuilder is being used.
So if you want to obtain CDI beans when onNewBuilder is called, you can do something like:
CDI.current().select(MyExceptionMapper.class).get()
Furthermore, you need to annotate MyExceptionMapper with #Provider, not #ApplicationScoped.
I am currently planning an application that requires a function to run whenever a session is created and expires. I'm planning on using something like redis but I am open to other ideas. What i am looking for is a n annotation such as #whenexpires and #whencreated. I know that most of the annotations for sessions are at the class, and notthemethod Thanks in regards.
As of Servlet specification 2.3, Java Servlet containers like Apache Tomcat provide the HttpSessionListener interface in order to execute custom logic in the event of created or destroyed sessions. Basic usage:
package com.example;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
public class MySessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent event) {
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
}
}
Add MySessionListener to your web.xml or - in case of Spring - declare a Spring bean for it that is detected by Spring. However, Spring is not required as HttpSessionListener is part of the Java Servlet spec.
If you go for Spring Session with Redis, you can continue using your HttpSessionListener by adding it to the Spring configuration as described in the official docs.
#EnableRedisHttpSession
public class Config {
#Bean
public MySessionListener mySessionListener() {
return new MySessionListener();
}
// more Redis configuration comes here...
}
Moreover, Spring Session comes with support for the "Spring-native" way of event subscription and publishing: ApplicationEvent. Depending on the session persistence approach, there are currently up to three events that can be catched by your application: SessionExpiredEvent, SessionCreatedEvent, SessionDestroyedEvent.
Implement an EventListener in order to subscribe to Spring Session events, for example:
package com.example;
import org.springframework.context.event.EventListener;
import org.springframework.session.events.SessionCreatedEvent;
import org.springframework.session.events.SessionDestroyedEvent;
import org.springframework.session.events.SessionExpiredEvent;
import org.springframework.stereotype.Component;
#Component
public class MySessionEventListener {
#EventListener
public void sessionDestroyed(SessionDestroyedEvent event) {
}
#EventListener
public void sessionCreated(SessionCreatedEvent event) {
}
#EventListener
public void sessionExired(SessionExpiredEvent event) {
}
}
Following code works like expected.
When a MailSender is available in the context an EmailService is created.
When no MailSender is available the NoopEmailSender is created.
From my understanding Spring Boot prefers the method with the most parameters when they have the same names. Unfortunately I can not find this behavior described somewhere in the documentation or JavaDoc.
My issue is that I'm not sure if the code works just because of "luck"/"random" or this behavior is intended.
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender;
#Configuration
#Slf4j
public class MailConfiguration {
#Bean
public EmailSender emailSender(MailSender mailSender) {
return new EmailService(mailSender);
}
#Bean
public EmailSender emailSender() {
log.info("Email sending is not configured.");
return new NoopEmailSender();
}
}
Thanks for the help!
I want to create an annotation that I will use on controller methods to validate access to the resource. I have written interceptor to intercept the request and also written code to create an annotation for their independent scenarios. Now I want intercept the request as well as take values provided in anotation to further processing.
Ideally
#RequestMapping("/release")
#ValidateAction("resource","release") //custom annotation that will accept two strings
public ResponseEntity releaseSoftware(Request request){
}
From the above I have to take those two values from #ValidateAction and send a request to another authorization server to authorize the action if the user have access to it (request contains oauth access token that will be used to authorize) and return true if the user have access otherwise throw AcceeDenied exception.
Can anybody point me in the right direction of doing it in Spring boot environment
Best way to achieve this is using Spring AOP Aspects.
Let us assume you have an Annotation like this
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAction {
String resource();
String release();
}
Then write an Aspect like this
#Aspect
#Component
public class AspectClass {
#Around(" #annotation(com.yourpackage.ValidateAction)")
public Object validateAspect(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
ValidateAction validateAction = method.getAnnotation(ValidateAction.class);
String release = validateAction.release();
String resource = validateAction.resource();
// Call your Authorization server and check if all is good
if( hasAccess)
pjp.proceed();
.......
}
}
Control will come to validateAspect method when any method which is annotated with #ValidateAction is called. Here you capture the annotation values as shown and do the necessary check.
Make sure you have the right dependency needed and these imports
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
You can do it with Spring AOP:
First, add spring-aop dependency:
compile 'org.springframework.boot:spring-boot-starter-aop' // Mine using gradle
In your #Configuration class, add #EnableAspectJAutoProxy:
#Configuration
#EnableAspectJAutoProxy
public class Application {
....
}
Create an annotation handler:
#Aspect
#Component // This #Component is required in spring aop
public class ValidateActionHandler {
#Around("execution(#your.path.ValidateAction * *(..)) && #annotation(validateAction)")
public Object doValidate(ProceedingJoinPoint pjp, ValidateAction retryConfig) throws Throwable {
// Your logic here
// then
return pjp.proceed();
}
}
I am planning to use the Spring Kafka client to consume and produce messages from a kafka setup in a Spring Boot application. I see support for custom headers in Kafka 0.11 as detailed here. While it is available for native Kafka producers and consumers, I don't see support for adding/reading custom headers in Spring Kafka.
I am trying to implement a DLQ for messages based on a retry count that I was hoping to store in the message header without having to parse the payload.
I was looking for an answer when I stumbled upon this question. However I'm using the ProducerRecord<?, ?> class instead of Message<?>, so the header mapper does not seem to be relevant.
Here is my approach to add a custom header:
var record = new ProducerRecord<String, String>(topicName, "Hello World");
record.headers().add("foo", "bar".getBytes());
kafkaTemplate.send(record);
Now to read the headers (before consuming), I've added a custom interceptor.
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerInterceptor;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
#Slf4j
public class MyConsumerInterceptor implements ConsumerInterceptor<Object, Object> {
#Override
public ConsumerRecords<Object, Object> onConsume(ConsumerRecords<Object, Object> records) {
Set<TopicPartition> partitions = records.partitions();
partitions.forEach(partition -> interceptRecordsFromPartition(records.records(partition)));
return records;
}
private void interceptRecordsFromPartition(List<ConsumerRecord<Object, Object>> records) {
records.forEach(record -> {
var myHeaders = new ArrayList<Header>();
record.headers().headers("MyHeader").forEach(myHeaders::add);
log.info("My Headers: {}", myHeaders);
// Do with header as you see fit
});
}
#Override public void onCommit(Map<TopicPartition, OffsetAndMetadata> offsets) {}
#Override public void close() {}
#Override public void configure(Map<String, ?> configs) {}
}
The final bit is to register this interceptor with the Kafka Consumer Container with the following (Spring Boot) configuration:
import java.util.Map;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
#Configuration
public class MessagingConfiguration {
#Bean
public ConsumerFactory<?, ?> kafkaConsumerFactory(KafkaProperties properties) {
Map<String, Object> consumerProperties = properties.buildConsumerProperties();
consumerProperties.put(ConsumerConfig.INTERCEPTOR_CLASSES_CONFIG, MyConsumerInterceptor.class.getName());
return new DefaultKafkaConsumerFactory<>(consumerProperties);
}
}
Well, Spring Kafka provides headers support since version 2.0: https://docs.spring.io/spring-kafka/docs/2.1.2.RELEASE/reference/html/_reference.html#headers
You can have that KafkaHeaderMapper instance and use it to populated headers to the Message before sending it via KafkaTemplate.send(Message<?> message). Or you can use the plain KafkaTemplate.send(ProducerRecord<K, V> record).
When you receive records using KafkaMessageListenerContainer, the KafkaHeaderMapper can be supplied there via a MessagingMessageConverter injected to the RecordMessagingMessageListenerAdapter.
So, any custom headers can be transferred either way.