Reactive custom constraint validator in spring webflux - reactive-programming

I'm trying to write a custom ConstraintValidator which does a mongodb lookup using spring-webflux and spring-data reactive mongo.
In traditional spring webmvc, one would create a) an annotation and b) implement a custom ConstraintValidator, then annotate the #RequestBody with #Valid, and spring webmvc would
call the validator and reject the request with status code 400 if the validation result is false.
However when using the same approach in spring webflux, i seem to be forced to implement the custom constraint validator in a blocking way, which does not fit the reactive model of spring webflux. Example (simplified for brevity):
public class UserPasswordValidator
implements ConstraintValidator<ValidUserPassword, UpdateUserPasswordDto> {
#Resource
private ReactiveUserRepository reactiveUserRepo;
#Override
public boolean isValid(UpdateUserPasswordDto userPasswordDto,
ConstraintValidatorContext context) {
// This is a blocking call. How can this be done in webflux?
UserEntity user = reactiveUserRepo.findByUuid(userPasswordDto.getUuid()).block();
if (!oldPasswordIsCorrect(userPasswordDto.getCurrentPassword(), user.getPasswordHash())) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("Incorrect current password")
.addConstraintViolation();
return false;
}
return true;
}
}
Is there a way to do custom validation which needs a database lookup in spring webflux in a non-blocking way?

Related

View Single Record in Spring Boot and Mongo

I'm trying to develop a simple crud application using Spring and Mongodb.
When I'm trying to develop view single data function, I get no error.
But it return value as null when I try in Postman.
Could you please help me to find what is the wrong with my code?
Controller
#GetMapping("/patient/{id}")
public Optional<Patients> findTicketById(#PathVariable("id") #NotNull String id){
System.out.println(id);
return patientRepository.findById(id);
}
Repository
#Repository
public interface PatientRepository extends MongoRepository<Patients, Long> {
Optional<Patients> findById(String id);
}
You can use ifPresentOrElse , check for the usages :
Functional style of Java 8's Optional.ifPresent and if-not-Present?

Call not propagating to the service method from Spring Reactive Controller

I am a beginner to the spring webflux. We are currently migrating our application to Spring Webflux. No I have stuck with a problem. The following is my scenario.
The main service class is calling the following service classes for data
StudentService - return Mono<Student>
StaffService - return Mono<Staff>
Here I have a wrapper class StudentWithMentor to store the result from these service classes.
public class StudentWithMentor {
private Student student;
private Staff mentor;
}
Now in controller I am calling the above 2 services and map it into 'StudentWithMentor' in the following way
Mono<StudentWithMentor> studentWithMentorMono = Mono.just(new StudentWithMentor());
return studentWithMentorMono.map(s->{
studentService.getStudentById(id)
.doOnSuccess(s::setStudent)
.doOnSuccess(st->staffService.getStaffByGrade(st.getGrade()));
return s;
});
But when I call this endpoint I am getting the following result in postman
{
"student": null,
"mentor": null
}
Note: I am getting result from the underlying services when I debugg. But the call is returning before it process.
How can I achieve this in a complete non-blocking way.
Appreciates any help.
The easiest way will be to to use a zipWith operator to merge the results into StudentWithMentor object.
See the code below:
Mono<StudentWithMentor> studentWithMentorMono = studentService.getStudentById(id)
.zipWhen(student -> staffService.getStaffByGrade(student.getGrade()), StudentWithMentor::new);

How do I define a custom HttpMessageConverter for the reactive Spring WebClient (Spring-WebFlux)

For the Spring org.springframework.web.client.RestTemplate, it was relatively easy to define an own HttpMessageConverter:
/**
* Set the message body converters to use.
* <p>These converters are used to convert from and to HTTP requests and responses.
*/
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
validateConverters(messageConverters);
// Take getMessageConverters() List as-is when passed in here
if (this.messageConverters != messageConverters) {
this.messageConverters.clear();
this.messageConverters.addAll(messageConverters);
}
}
When converting my client to an reactive WebClient, I did not find a suitable way to define my own message converter as before with the RestTemplate.
Background: Our spring boot project is based on Scala and we use our own converter (based on com.fasterxml.jackson.module.scala.JacksonModule) to process Scala Case classes.
You can register custom codecs(Encoder, Decoder, HttpMessageReader, HttpMessageWriter) via WebClient.builder() for your WebClient in the reactive world.
WebClient client = WebClient.builder()
//see: https://github.com/jetty-project/jetty-reactive-httpclient
//.clientConnector(new JettyClientHttpConnector())
.clientConnector(new ReactorClientHttpConnector())
.codecs(
clientCodecConfigurer ->{
// .defaultCodecs() set defaultCodecs for you
// clientCodecConfigurer.defaultCodecs();
// You can customize an encoder based on the defualt config.
// clientCodecConfigurer.defaultCodecs().jackson2Encoder(...)
// Or
// use customCodecs to register Codecs from scratch.
clientCodecConfigurer.customCodecs().register(new Jackson2JsonDecoder());
clientCodecConfigurer.customCodecs().register(new Jackson2JsonEncoder());
}
)
.baseUrl("http://localhost:8080")
.build();
If you're using Spring Boot with WebFlux, you can add a CodecCustomizer bean, which you can use to register your own custom codecs:
#Bean
CodecCustomizer myCustomCodecCustomizer(...) {
return configurer -> configurer.customCodecs().register(...);
}
By the way, when using Spring Boot with WebMVC, you can just add your HttpMessageConverter implementation as a bean and it will get picked up.

How to inject spring aop advice for MongoDb call?

I am new to Spring Aop, but I have case to implement AOP advice for a mongo db call(monog db update). I am trying in different way but getting 'Point cut not well formed' error or 'warning no match for this type name: arg string [Xlint:invalidAbsoluteTypeName]'(even if I give absolute name of the argument). Anyone can help on this as how to inject advice for mongo db update call?
#Aspect
#Component
public class DBStatsLoggerAspect {
private static final Logger log = LoggerFactory
.getLogger(DBStatsLoggerAspect.class);
private static final Document reqStatsCmdBson = new Document(
"getLastRequestStatistics", 1);
private DbCallback<Document> requestStatsDbCallback = new DbCallback<Document>() {
#Override
public Document doInDB(MongoDatabase db) throws MongoException,
DataAccessException {
return db.runCommand(reqStatsCmdBson);
}
};
#After("execution( public * com.mongodb.client.MongoCollection.*(..)) && args(org.bson.conversions.Bson.filter,..)")
public void requestStatsLoggerAdvice(JoinPoint joinPoint) {
MongoTemplate mongoTemplate = (MongoTemplate) joinPoint.getTarget();
log.info(mongoTemplate.execute(requestStatsDbCallback).toJson());
}
}
Actual db call method where I need to inject advice:(filter, updatePart all are org.bson.conversions.Bson data type) and here 'collection' is com.mongodb.client.MongoCollection.collection
Document result = collection.findOneAndUpdate(filter, updatePart, new FindOneAndUpdateOptions().upsert(false));
I am not a Spring or MongoDB user, just an AOP expert. But from what I see I am wondering:
You are intercepting execution(public * com.mongodb.client.MongoCollection.*(..)), so joinPoint.getTarget() is a MongoCollection type. Why do you think you can cast it to MongoTemplate? That would only work if your MongoCollection happened to be a MongoTemplate subclass. To me this looks like a bug.
Class MongoCollection is not a Spring component but a third-party class. Spring AOP can only intercept Spring component calls by means of creating dynamic proxies for those components and adding aspect interceptors to said proxies. so no matter how correct or incorrect your pointcut, it should never trigger.
What you can do instead is switch from Spring AOP to full-blown AspectJ. The standard way to do this is to activate AspectJ load-time weaving (LTW).

Spring Data Rest with Spring Security - find all by current user

Is it possible to use Spring Data Rest and Spring Security to return current user related entities, using the findAll() method without specifying this user in the GET query parameter?
My only solution is to pass user as a parameter, but maybe it's another option to get him from SpringSecurityContext
public interface InvoiceRepository extends CrudRepository<Invoice, Long> {
#RestResource
#PreAuthorize("hasRole('ROLE_ADMIN') or user?.username == authentication.name")
List<Invoice> findAllByUser(#Param("user") User user);
You can use SpEL EvaluationContext extension that makes security properties and expressions available in SpEL expressions in the #Query annotations. This allows you to get only those business objects that relate to the current user:
interface SecureBusinessObjectRepository extends Repository<BusinessObject, Long> {
#Query("select o from BusinessObject o where o.owner.emailAddress like ?#{hasRole('ROLE_ADMIN') ? '%' : principal.emailAddress}")
List<BusinessObject> findBusinessObjectsForCurrentUser();
}
More details are here.