Validating Mongo Documents in Spring Boot - spring

I'm writing a rest service using spring boot with Jersey and MongoDB starter packages. So I have validation working on top level documents by creating the beans:
#Configuration
public class MongoValidationBeans {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
I have a document:
#Document
public class SomeDocument {
#NotEmpty(message="error message that shows on console")
private Set<NonDocumentObject> referencesToOtherDocuments;
}
With set of embedded objects:
public class NonDocumentObject {
#NotNull(message="can't see this error message")
private ObjectId referenceId;
#NotBlank
private String referenceInfo;
}
The validation beans respect the #NotEmpty annotation on my set of objects, but they do not respect #NotNull or #NotBlank annotations on fields on my NonDocumentObject. How can I get validation to work on the fields of my embedded Set of objects.
EDIT: #Valid fixes the above problem.
Also, when a constraint violation happens on my top level document, I can see the specific message on my console but tomcat returns an http error page with response status 400. How can I instead send a json object with more information about the error? I have a class
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {}
which catches 404, 405, etc exceptions and returns a json object with the appropriate information, but does not catch mongo constraint validations. I think I need to throw exceptions from the mongo validation beans but can't find resources that direct me how to.
I also want to be able to embed other objects into NonDocumentObject with its own validation. Would it be possible?

So the #Valid annotation triggers cascade validation, but I still can't figure out how to catch validation errors with an exception mapper, or some other way to catch validation errors.

Related

Implementing JSR-303 validation with crnk JSON API

I am fairly familiar with the JSR-303's #Valid annotation and have used it a couple of times in my #Controller classes. For example:
#PostMapping("/users")
ResponseEntity<String> addUser(#Valid #RequestBody User user) {
// persisting the user
return ResponseEntity.ok("User is valid");
}
Where User object is a typical class with annotations like #NotBlank or #NotNull on the fields.
However, I am trying to build a REST API based on JSON API using the crnk library and am trying to do the same validation, example:
#Override
public Subscription save(#Valid Subscription subscription) {
// code goes here
}
Unfortunately the validation doesn't work and I have tried both #Valid and #Validation.
Can anyone kindly show what is wrong with this code?
Thanks

Spring Boot WebFlux Converter

I am trying to migrate my project from the Spring MVC to the Spring WebFlux.
The repository I am currently using is ReactiveCrudRepository.
In order to achieve the post-redirect-get pattern, which I have used within Spring MVC, I need to rewrite the current converter to work with ReactiveCrudRepository.
I was trying to do that with this aproach:
#Component
public class ObjByIdConverter implements Converter<String, Obj> {
#Autowired
private IObjRepository objRepository;
#Override
public Obj convert(String id) {
return objRepository.findById(id).block();
}
}
When I implement converter in this way, I am getting the following error:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-xxx.
When I was using CrudRepository instead of ReactiveCrudRepository everything was worked fine.
Is there a way to implement converter to work with ReactiveCrudRepository?
~~~ Edit 1 ~~~
The controller class:
#PostMapping
public Mono<String> processOrder(#ModelAttribute("newCar") Car car) {
webDataBinder.validate();
BindingResult bindingResult = webDataBinder.getBindingResult();
if (bindingResult.hasErrors()) {
return Mono.just("orderForm");
}
return this.carRepository.save(car).thenReturn("redirect:/");
}
The model class:
#Document(collection = "cars")
#ToString
#EqualsAndHashCode
public class Car {
#Id
private String id;
private List<Obj> objs = new ArrayList<>();
// constructor, getters, setters, ...
}
I am using the Thymeleaf view technology.
I have to provide the implementation for ObjByIdConverter because I am getting the following error message: [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'objs'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.app.model.Obj' for property 'objs[0]': no matching editors or conversion strategy found]
You should not use block in any case in reactive development. If you have ReactiveRepository and Spring Webflux, use them together with Mono/Flux from repository to controller to leverage the reactive way of doing.
But I think the main reason why you try to convert the result to a standard type is for the post-redirect-get pattern, could you detail this in the spring controller context ?

How to validate request parameters on feign client

Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true

Domain Object Returned from RestController causes AJAX error; Custom Model Object works

I have an issue in my SpringMVC RestController that writes JSON results:
1) When I return a domain object as below, ActivitiesT, I get an AJAX 500 Internal Server Error on the client-side.
#RequestMapping("/participant/activityForEvent")
public ActivitiesT getActivityForGuiEventId() throws Exception {
ActivitiesT activitiesT = participantService.getActivity();
return activitiesT;
}
ActivitiesT Domain object, Hiberate-generated:
#Entity
#Table(name = "activities_t", schema = "public")
public class ActivitiesT implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int id;
//... etc...
}
2) But when I return a custom POJO, the AJAX method works. It's almost the same.
#RequestMapping("/participant/activityForEvent")
public ActivitiesT getActivityForGuiEventId() throws Exception {
ActivitiesT activitiesT = participantService.getActivity();
// Create a custom Activity POJO and return it
return new Activity(activitiesT.id, activitiesT.title);
}
Activity custom POJO:
public class Activity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public int id;
public String title;
//etc.
ERROR MESSAGE
HTTP Status 500 - Could not write JSON: could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy -
no Session (through reference chain: com.myapp")
Some things to note:
Both objects implement Serializable, that's fine
The issue is not DAO, I can see the DAO always returns correct data for both, we can ignore the DAO piece
The Controller is annotated with #RestController, so it always outputs JSON; I don't need a ResponseBody or the older tags
Any thoughts on where the issue may lie? The problem boils down to this:
I can't return JSON from RestController methods on Hibernate-generated #Entity POJOs,
But I can return JSON from RestController methods on my own custom POJOs
When Jackson prepares response, it recursively tries to convert model classes to JSON.For example if you have a Department and Employee relationship with one to many relationship then Jackson executes getEmployees() method in Department.java to prepare the response.But getEmployees() method requires hibernate to execute another query to fetch employee records from DB due to Lazy loading. But the session has been already closed. Hence you get this error.
To avoid this, always use DTO instead of actual model classes.And the same thing you have mentioned that - Domain Object Returned from RestController causes AJAX error; Custom Model Object works.

Spring 5 Webflux functional endpoints - How to perform input validation?

According to the current doc (5.0.0.RELEASE) Spring Webflux supports validation when working with annotated controllers:
By default if Bean Validation is present on the classpath — e.g.
Hibernate Validator, the LocalValidatorFactoryBean is registered as a
global Validator for use with #Valid and Validated on #Controller
method arguments.
However nothing is said about how to automate it with functional endpoints. In fact, the only example of input processing in the documentation doesn't validate anything:
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ServerResponse.ok().build(repository.savePerson(person));
}
Are we supposed to do this manually or there is some automatic way to do it?
In Spring version 5.0, there is no automatic way to do validation in functional endpoints, and as such validation must be done manually.
Though there are currently no concrete plans to do so, we might add some sort of validation in the future. But even then it will be an explicit method call, and not an automatic mechanism. Overall, the functional endpoint model is designed to be a lot more explicit than the annotation-based model.
As arjen-poutsma said, it seems there is no way of running automated validations on Spring 5 functional endpoints.
Spring documentation is not very clear about this, and it doesn't suggest any approach.
On this Baeldung article, you'll find an idea on how you can run validations using this approach (disclaimer: I'm the writer of the article :) )
In a nutshell, you can follow these steps:
Implement Spring Validators to evaluate your resources
Create an abstract class with the basic procedure that any handler will follow when processing a request, leaving up to the children classes what to do when the data is valid
Make your request handler classes extend this abstract class, implementing this abstract method, stating the body it will be expecting, and what validator needs to be used to validate it
EDIT:
I've been following this related Spring issue, and it seems we now count with official documentation regarding this subject: https://github.com/spring-projects/spring-framework/blob/master/src/docs/asciidoc/web/webflux-functional.adoc#validation
The suggested approach is to use validators as explained in the article.
At the current version(2.0.4.RELEASE) there isn't a way to do automatic validation with handles, however you always could make a manual validation like this:
#Slf4j
#Component
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
#RequiredArgsConstructor
public class MyHandlerValidator implements HandlerValidator<MyResource> {
Validator validator;
#Override
public void callValidator(final MyResource fdr) {
final DataBinder binder = new DataBinder(fdr);
binder.setValidator(validator);
binder.validate();
if (binder.getBindingResult().hasErrors()) {
final String reason = binder.getBindingResult().getFieldError().toString();
log.error(reason);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, reason);
}
}
}
The thing with this, its that the you should throw a WebExchangeBindException like automatic validation does, however i could't create a MethodParameter witch is a dependency to create this exception.
UPDATE:
Spring show us a way to do it, which is similar to my solution, but, not enough in my opinion on documentation
Just to demo some working code. If you need simple validation based on the object annotations like:
#Value
#Builder
#Jacksonized
public class SigninRequest {
#NotBlank(message = "The username is mandatory")
#Email(message = "The username should be valid Email")
String username;
#NotBlank(message = "The password is mandatory")
String password;
}
At the handler you need just one simple additional operator doOnNext:
#Component
#RequiredArgsConstructor
public class AuthHandler {
private final AuthService authService;
private final ObjectValidator validator;
public Mono<ServerResponse> signin(ServerRequest request) {
return ok().body(
request.bodyToMono(SigninRequest.class)
.doOnNext(validator::validate) //<-- just one single line
.flatMap(login -> authService.authenticate(login.getUsername(), login.getPassword())),
AuthResult.class);
}
}
The ObjectValidator is doing actual validation and throws the runtime exception with the 4xx error in case of validation errors:
#Component
#RequiredArgsConstructor
public class ObjectValidator {
private final Validator validator;
public <T> T validate(T object) {
var errors = validator.validate(object);
if (errors.isEmpty()) {
return object;
} else {
String errorDetails = errors.stream().map(er -> er.getMessage()).collect(Collectors.joining(", "));
throw new ObjectValidationException(errorDetails);
}
}
}
And the exception:
#ResponseStatus(code = HttpStatus.UNPROCESSABLE_ENTITY)
public class ObjectValidationException extends RuntimeException {
public ObjectValidationException(String errorDetails) {
super("Please supply the valid data: " + errorDetails);
}
}
If you properly setup global error handling you can keep you handler code clean and reuse the object validator across all your handlers.

Resources