Reputation: 8955
In Spring MVC, I had a @UniqueEmail
custom hibernate validator (to check for uniqueness of email when signup), which looked as below:
public class UniqueEmailValidator
implements ConstraintValidator<UniqueEmail, String> {
private UserRepository userRepository;
public UniqueEmailValidator(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return !userRepository.findByEmail(email).isPresent();
}
}
Now I'm migrating to WebFlux with reactive MongoDB, with my code as below:
public class UniqueEmailValidator
implements ConstraintValidator<UniqueEmail, String> {
private MongoUserRepository userRepository;
public UniqueEmailValidator(MongoUserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public boolean isValid(String email, ConstraintValidatorContext context) {
return userRepository.findByEmail(email).block() == null;
}
}
First of all, using block
as above doesn't look good. Secondly, it's not working, and here is the error:
Caused by: java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
How to go about this? I can of course use a MongoTemplate blocking method, but is there a way to handle this reactively? I could do it manually in the service method, but I wished this error to be shown to the user along with other errors (e.g. "short" password).
Upvotes: 6
Views: 1943
Reputation: 131
I agree by now Spring Framework should have had a reactive validator, but until then I do this inside my controllers or services layers, this is for a simple file manager where I needed to check the permissions on the parent folder, and also if the parent exists:
private Mono<Folder> validate(Folder folder, UserReference user) {
// init
var errors = new SimpleErrors(folder);
// validate annotations
this.validator.validate(folder, errors);
if (errors.hasErrors()) {
if (errors.getFieldError() != null) {
return Mono.error(new BadRequestException(
errors.getFieldError().getDefaultMessage()));
}
if (errors.getGlobalError() != null) {
return Mono.error(new BadRequestException(
errors.getGlobalError().getDefaultMessage()));
}
return Mono.error(new BadRequestException("Invalid folder"));
}
// validate parent
if (folder.getParentId() != null) {
return repository.findById(folder.getParentId())
.switchIfEmpty(
Mono.error(new BadRequestException("Parent folder not found.")))
.flatMap(parent -> {
var access = AccessControlList.AccessType.WRITE;
if (!parent.getAcl().hasAccess(user.getId(), access)) {
return Mono.error(new BadRequestException(
"User does not have write access to parent folder."));
}
return Mono.just(folder);
});
}
// done
return Mono.just(folder);
}
Upvotes: 0
Reputation: 3
I had the same problem and finally I decided to check simple validations with ConstraintValidator and to check reactive validations in the application logic which is reactive. I don't know if there is other better solution, but it could be a good approach.
Upvotes: 0
Reputation: 59086
As of Reactor 3.2.0, using blocking APIs inside a parallel or single Scheduler
is forbidden and throws the exception you're seeing. So you got that right when you said it doesn't look good - not only it's really bad for your application (it might block the processing of new requests and crash the whole thing down), but it was so bad that the Reactor team decided to consider that as an error.
Now the problem is you'd like to do some I/O related work within a isValid
call. THe complete signature of that method is:
boolean isValid(T value, ConstraintValidatorContext context)
The signature shows that it's blocking (it doesn't return a reactive type, nor provides the result as a callback). So you're not allowed to do I/O related or latency involved work in there. Here you'd like to check an entry against the database, which exactly falls into that category.
I don't think you can do that as part of this validation contract and I'm not aware of any alternative to that.
Upvotes: 3