Spring boot bean validation - java

In my Spring Boot project, I have a POJO class for reading yaml config file.
#Configuration
#ConfigurationProperties("filenet")
#Data
public class ApplicationConfig {
#NotNull(message = "Input directory cannot be null")
#NotBlank(message = "Input directory cannot be blank")
private String **inputDir**;
#NotNull(message = "Working directory cannot be null")
#NotBlank(message = "Working directory cannot be blank")
private String **workingDir**;
#Pattern(regexp = "[0-9]+",message = "Invalid value for SMTP port")
private String port;
}
Sometimes it happens that either inputDir or workingDir or other fields of the config file are blank. I'm using javax.validation.constraints to check for blank or null. When so, and when application is started, I see an exception message and program is terminated.
ex: Port has validation that it has to be a number.
What I would like to do is to gracefully handle this exception and send an email to concerned team to take care of it.
I have created a class where I'm validating the contents of config file
#Component
public class ConfigParamValidation {
public List<String> validateApplicationConfigFile(ApplicationConfig applicationConfig) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ApplicationConfig.ContentEngine>> contentEngineVoilations = validator.validate(applicationConfig.contentEngine);
exceptionMessgae = contentEngineVoilations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
if(!exceptionMessgae.isEmpty()) {
through new ConfigFileException(<by passing all required params>);
}
}
}
I have tried to create a class which extends RuntimeException
public class ConfigFileException extends RuntimeException {
private String message;
private List<String> details;
private String hint;
private String nextActions;
private String support;
private HttpStatus httpStatus;
private ZonedDateTime zonedDateTime;
public ConfigFileException(String message) {
super(message);
}
public ConfigFileException(String message, Throwable cause) {
super(message, cause);
}
public ConfigFileException(String message, Throwable cause, List<String> details, String hint, String nextActions, String support, HttpStatus httpStatus, ZonedDateTime zonedDateTime) {
super(message, cause);
this.message = message;
this.details=details;
this.hint=hint;
this.nextActions=nextActions;
this.support=support;
this.httpStatus=httpStatus;
this.zonedDateTime = zonedDateTime;
}
}
Another class with #ExceptionHandler
#Data
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupportTeamException {
#ExceptionHandler(value = {ConfigFileException.class})
public ResponseEntity<Object> handleConfigFileException(ConfigFileException e) {
ConfigFileException configFileException = new ConfigFileException(e.getMessage(), e, e.getDetails(), e.getHint(), e.getNextActions(), e.getSupport(), e.getHttpStatus(), e.getZonedDateTime());
return new ResponseEntity<Object>(configFileException,e.getHttpStatus());
}
}
The problem is that for some reason control is not passed to my SupportTeamException class where I could send email.
Or is there a better way to handle this?

It was my understanding that #ControllerAdvice only works for components that are annotated with #Controller or #RestController.
Since the validation happens at start up of your spring boot app (and not as a result of a http request), it will never go into it. What you could do is create a Component with a #PostConstructor method. (See below) I would strongly recommend to inject your Validator rather than building it yourself (to utilise Spring Boot's full potential).
What I don't fully understand is why you would want to handle this exception gracefully. If your application starts without required properties, it will just fail further down the line when the application actually uses the property. If depending on circumstances (like the environment), certain properties don't need to be there, I would recommend using Validation groups
Finally, small aside #NotBlank will also check it's not null. You don't need both annotations, unless you want to be really specific with your messages.
package com.yourpackage;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.ValidationException;
import javax.validation.Validator;
import java.util.Set;
import static java.util.stream.Collectors.joining;
#Component
public class PropertiesValidator {
private final Validator validator;
public final YourProperties properties;
public PropertiesValidator(final Validator validator, final YourProperties properties) {
this.validator = validator;
this.properties = properties;
}
#PostConstruct
public void propertiesShouldBeValid() {
final Set<ConstraintViolation<YourProperties>> constraintViolations = validator.validate(properties);
if (!constraintViolations.isEmpty()) {
final String message = constraintViolations.stream()
.map(ConstraintViolation::getMessage)
.collect(joining(","));
throw new ValidationException(message); //Or send your email
}
}
}

Related

How to validate java plain methods using javax validation

I am validating java beans using spring-boot-starter-validation.
Validation on the controller is working fine,
I want to know whether can we validate the normal methods of a class using #Valid annotation? I have tried it but not working.
My working solution on the controller
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#Valid #RequestBody InternalMsg internalMsg) {
return ResponseEntity.ok("Valid Message");
}
I want to move the validation to a class method so that when I hit the RestAPI, the validation errors are captured in a new method.
Let's say the method is validateMsg of class MsgValidator and I am calling this method inside controller code
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#RequestBody InternalMsg internalMsg) { // No #Valid here
MsgValidator msgValidator = new MsgValidator();
Boolean isValid = msgValidator.validateMsg(internalMsg);
// some other processings
return ResponseEntity.ok("Valid Message");
}
public class MsgValidator{
public boolean validateMsg(#Valid InteropMsg interopMsg){
return true;
}
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Ack> handleValidationExceptions(MethodArgumentNotValidException ex) {
StringBuilder errorMessages = new StringBuilder("");
ex.getBindingResult().getAllErrors().forEach((error) -> {
errorMessages.append(error.getDefaultMessage()).append(";");
});
log.error("Validation errors : "+errorMessages.toString());
return ResponseEntity.ok().body(ack);
}
}
public class InternalMsg implements Serializable {
#NotNull(message = "Msg Num is a required field")
private String msgNumber;
#NotNull(message = "Activity Name is a required field")
private String activityName;
}
This is not working
Please let me know how to achieve this
Below is an example of how you could use the ValidatorFactory to get a Validator to do the validation rather than using the #Valid annotation.
InternalMsg internalMsg = new InternalMsg();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<InternalMsg>> validate = validator.validate(internalMsg);
See here for more details -> https://www.baeldung.com/javax-validation
The below is just a snippet and not necessarily the recommended way of using the ValidationFactory

Spring Boot RestTemplate Composite Response Empty List

So I'm developping some microservices in JAVA using Spring Boot and I'm facing some problems involving the objects I'm using.
So I have a data service which is the DB interface and a scheduling service which will be called by the frontend.
Both work with their own Response and Request objects eventhough at this point they are basically the same.
please ignore that there are no getters and setters in the code below.
Data-Service
#RestController
#RequestMapping("")
public class DataServiceResource {
#GetMapping(...)
public ResponseEntity<JobDetailsResponse> getJobDetailsSingleDate(#PathVariable("singledate") final String date) {
...
return response;
}
}
JobDetailsResponse
#JsonIgnoreProperties(ignoreUnknown = true)
public class JobDetailsResponse {
private Object requestSent;
private List<Job> jobsFound;
private boolean hasError;
private String errorMessage;
private LocalDateTime dataTimestamp;
}
JobDetailsSingleDateRequest
#JsonIgnoreProperties(ignoreUnknown = true)
public class JobDetailsSingleDateRequest {
private String dateFrom;
}
Scheduling Service
#RestController
#RequestMapping("")
public class SchedulingServiceResource {
...
#Autowired
private RestTemplate restTemplate;
#GetMapping(...)
public ResponseEntity<ReportDetailsResponse> getReportDetailsSingleDate(#PathVariable("singledate") final String singledate) {
ResponseEntity<ReportDetailsResponse> quoteResponse = this.restTemplate.exchange(DATA_SERVICE_JOB_DETAILS_SINGLE_DATE_URL + singledate, HttpMethod.GET,
null, new ParameterizedTypeReference<ReportDetailsResponse>() {});
...
return response;
}
ReportDetailsSingleDateRequest
#JsonIgnoreProperties(ignoreUnknown = true)
public class ReportDetailsSingleDateRequest {
private String dateFrom;
}
ReportDetailsResponse
#JsonIgnoreProperties(ignoreUnknown = true)
public class ReportDetailsResponse {
private Object requestSent;
private List<Job> jobsFound;
private boolean hasError;
private String errorMessage;
private LocalDateTime dataTimestamp;
}
So when I go through the quoteResponse.getBody().getJobsFound() method to check the data I got from the Data Service My List of jobs is empty.
I read that If the objects are equal in definition, spring would use reflection to pass the values, but in my case its not woking.
Is there a way to consume the microservice without having to add the data service dependency to the scheduling service?
Sorry for the long post but, until now I haven't found a proper example for my case. All the examples I found work with List as return of the microservice.
Thanks in advance.

Spring Boot Validate JSON Mapped via ObjectMapper GET #RequestParam

What's the simplest approach to validating a complex JSON object being passed into a GET REST contoller in spring boot that I am mapping with com.fasterxml.jackson.databind.ObjectMapper?
Here is the controller:
#RestController
#RequestMapping("/products")
public class ProductsController {
#GetMapping
public ProductResponse getProducts(
#RequestParam(value = "params") String requestItem
) throws IOException {
final ProductRequest productRequest =
new ObjectMapper()
.readValue(requestItem, ProductRequest.class);
return productRetriever.getProductEarliestAvailabilities(productRequest);
}}
DTO request object I want to validate:
public class ProductRequest {
private String productId;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}}
I was thinking of using annotations on the request DTO however when I do so, they are not triggering any type of exceptions, i.e. #NotNull. I've tried various combinations of using #Validated at the controller as well as #Valid in the #RequestParam and nothing is causing the validations to trigger.
In my point of view, Hibernate Bean Validator is probably one of the most convenient methods to validate the annotated fields of a bean anytime and anywhere. It's like setup and forget
Setup the Hibernate Bean Validator
Configure how the validation should be done
Trigger the validator on a bean anywhere
I followed the instructions in the documentation given here
Setup dependencies
I use Gradle so, I am going to add the required dependencies as shown below
// Hibernate Bean validator
compile('org.hibernate:hibernate-validator:5.2.4.Final')
Create a generic bean valdiator
I setup a bean validator interface as described in the documentation and then use this to validate everything that is annotated
public interface CustomBeanValidator {
/**
* Validate all annotated fields of a DTO object and collect all the validation and then throw them all at once.
*
* #param object
*/
public <T> void validateFields(T object);
}
Implement the above interface as follow
#Component
public class CustomBeanValidatorImpl implements CustomBeanValidator {
ValidatorFactory valdiatorFactory = null;
public CustomBeanValidatorImpl() {
valdiatorFactory = Validation.buildDefaultValidatorFactory();
}
#Override
public <T> void validateFields(T object) throws ValidationsFatalException {
Validator validator = valdiatorFactory.getValidator();
Set<ConstraintViolation<T>> failedValidations = validator.validate(object);
if (!failedValidations.isEmpty()) {
List<String> allErrors = failedValidations.stream().map(failure -> failure.getMessage())
.collect(Collectors.toList());
throw new ValidationsFatalException("Validation failure; Invalid request.", allErrors);
}
}
}
The Exception class
The ValidationsFatalException I used above is a custom exception class that extends RuntimeException. As you can see I am passing a message and a list of violations in case the DTO has more than one validation error.
public class ValidationsFatalException extends RuntimeException {
private String message;
private Throwable cause;
private List<String> details;
public ValidationsFatalException(String message, Throwable cause) {
super(message, cause);
}
public ValidationsFatalException(String message, Throwable cause, List<String> details) {
super(message, cause);
this.details = details;
}
public List<String> getDetails() {
return details;
}
}
Simulation of your scenario
In order to test whether this is working or not, I literally used your code to test and here is what I did
Create an endpoint as shown above
Autowire the CustomBeanValidator and trigger it's validateFields method passing the productRequest into it as shown below
Create a ProductRequest class as shown above
I annotated the productId with #NotNull and #Length(min=5, max=10)
I used Postman to make a GET request with a params having a value that is url-encoded json body
Assuming that the CustomBeanValidator is autowired in the controller, trigger the validation as follow after constructing the productRequest object.
beanValidator.validateFields(productRequest);
The above will throw exception if any violations based on annotations used.
How is the exception handled by exception controller?
As mentioned in the title, I use ExceptionController in order to handle the exceptions in my application.
Here is how the skeleton of my exception handler where the ValidationsFatalException maps to and then I update the message and set my desired status code based on exception type and return a custom object (i.e. the json you see below)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({SomeOtherException.class, ValidationsFatalException.class})
public #ResponseBody Object handleBadRequestExpection(HttpServletRequest req, Exception ex) {
if(ex instanceof CustomBadRequestException)
return new CustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage());
else
return new DetailedCustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage(),((ValidationsFatalException) ex).getDetails());
}
Test 1
Raw params = {"productId":"abc123"}
Url encoded parmas = %7B%22productId%22%3A%22abc123%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
Result: All good.
Test 2
Raw params = {"productId":"ab"}
Url encoded parmas = %7B%22productId%22%3A%22ab%22%7D
Final URL: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22ab%22%7D
Result:
{
"statusCode": 400,
"status": "BAD_REQUEST",
"message": "Validation failure; Invalid request.",
"details": [
"length must be between 5 and 10"
]
}
You can expand the Validator implementation to provide a mapping of field vs message error message.
Do you mean something like this ?
#RequestMapping("/products")
public ResponseEntity getProducts(
#RequestParam(value = "params") String requestItem) throws IOException {
ProductRequest request = new ObjectMapper().
readValue(requestItem, ProductRequest.class);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ProductRequest>> violations
= validator.validate(request);
if (!violations.isEmpty()) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
public class ProductRequest {
#NotNull
#Size(min = 3)
private String id;
public String getId() {
return id;
}
public String setId( String id) {
return this.id = id;
}
}

Returning error list in jersey rest service

In our project we are using rest service(Jersey). In one of the requirement I would like to return a list of missing mandatory parameters back to the client.
Right now I am using exception mapper which can return a single error message,
public class MandatoryParameterMissingException extends RuntimeException {
private static final long serialVersionUID = 1L;
public MandatoryParameterMissingException(String message) {
super(message);
}
public class MandatoryParamMissingExceptionMapper implements ExceptionMapper<MandatoryParameterMissingException> {
#Override
public Response toResponse(MandatoryParameterMissingException ex) {
ErrorMessage errorMessage = new ErrorMessage(ex.getMessage(), 404, "Document source:todo");
return Response.status(Status.NOT_FOUND)
.entity(errorMessage)
.build();
}
private String errorMessage;
private int errorCode;
private String documentation;
public ErrorMessage() {
}
public ErrorMessage(String errorMessage, int errorCode, String documentation) {
super();
this.errorMessage = errorMessage;
this.errorCode = errorCode;
this.documentation = documentation;
}
..getter setters
If I find any missing mandatory params, right now I am doing something like,
if (emailID == null) {
throw new MandatoryParameterMissingException("Email id is missing");
}
Could some one please suggest What is the best way to enhance this to take a list of error messages and pass it back to the client?
For this issue i created a class called ValidationException. A ValidationException contains a list of ValidationError.
There are different kind of ValidationError, for example OutOfRangeError, MandatoryParameterError and whatever you need. A ValidationError always contains specific attributes (all what the client needs to create a meaningful messagetext for the user) => A MandatoryParameterError just the name of the field, an OutOfRangeError the name of the field and the allowed range.
On serverside there are validators (in a common package, so the client could do the validation itself)... the validators are creating ValidationErrors during the validation of your data. If there are validation errors, a ValidationException is thrown. That's it. I can give you some codesnippets...

JSR-303 validation groups define a default group

I have a bean that has a lot of fields annotated with JSR-303 validation annotations. There is a new requirement now that one of the fields is mandatory, but only in certain conditions.
I looked around and have found what I needed, validation groups.
This is what I have now:
public interface ValidatedOnCreationOnly {
}
#NotNull(groups = ValidatedOnCreationOnly.class)
private String employerId;
#Length(max = 255)
#NotNull
private String firstName;
#Length(max = 255)
#NotNull
private String lastName;
However, when I run this validation in a unit test:
#Test
public void testEmployerIdCanOnlyBeSetWhenCreating() {
EmployeeDTO dto = new EmployeeDTO();
ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
Set<ConstraintViolation<EmployeeDTO>> violations = vf.getValidator().validate(dto, EmployeeDTO.ValidatedOnCreationOnly.class);
assertEquals(violations.size(), 3);
}
It turns out that all of the non-group annotated validations are ignored and I get only 1 violation.
I can understand this behaviour but I would like to know if there is a way I can make the group include all non-annotated parameters as well. If not I'd have to do something like this:
public interface AlwaysValidated {
}
public interface ValidatedOnCreationOnly extends AlwaysValidated {
}
#NotNull(groups = ValidatedOnCreationOnly.class)
private String employerId;
#Length(max = 255, groups = AlwaysValidated.class)
#NotNull(groups = AlwaysValidated.class)
private String firstName;
#Length(max = 255, groups = AlwaysValidated.class)
#NotNull(groups = AlwaysValidated.class)
private String lastName;
The real class I'm working with has a lot more fields (about 20), so this method turns what was a clear way of indicating the validations into a big mess.
Can anyone tell me if there is a better way? Maybe something like:
vf.getValidator().validate(dto, EmployeeDTO.ValidatedOnCreationOnly.class, NonGroupSpecific.class);
I'm using this in a spring project so if spring has another way I'll be glad to know.
There is a Default group in javax.validation.groups.Default, which represents the default Bean Validation group. Unless a list of groups is explicitly defined:
constraints belong to the Default group
validation applies to the Default group
You could extends this group:
public interface ValidatedOnCreationOnly extends Default {}
just wanted to add more:
if you're using spring framework you can use org.springframework.validation.Validator
#Autowired
private Validator validator;
and to perform validation manually:
validator.validate(myObject, ValidationErrorsToException.getInstance());
and in controller:
#RequestMapping(method = RequestMethod.POST)
public Callable<ResultObject> post(#RequestBody #Validated(MyObject.CustomGroup.class) MyObject request) {
// logic
}
although in this way extending from javax.validation.groups.Default won't work so you have to include Default.class in groups:
class MyObject {
#NotNull(groups = {Default.class, CustomGroup.class})
private String id;
public interface CustomGroup extends Default {}
}
For me add Default.class everywhere is not good approach.
So I extended LocalValidatorFactoryBean which validate with some group and delegate for validation without any group.
I used spring boot 2.2.6.RELEASE
and I used spring-boot-starter-validation dependency.
My bean for validattion
public class SomeBean {
#NotNull(groups = {UpdateContext.class})
Long id;
#NotNull
String name;
#NotNull
String surName;
String optional;
#NotNull(groups = {CreateContext.class})
String pesel;
#Valid SomeBean someBean;
}
code of own class which extends LocalValidatorFactoryBean
public class CustomValidatorFactoryBean extends LocalValidatorFactoryBean {
#Override
public void validate(Object target, Errors errors, Object... validationHints) {
if (validationHints.length > 0) {
super.validate(target, errors, validationHints);
}
super.validate(target, errors);
}
}
Put it to spring context via #Bean or just with #Component (as you wish)
#Bean
#Primary
public LocalValidatorFactoryBean customLocalValidatorFactoryBean() {
return new CustomValidatorFactoryBean();
}
usage of it in some RestController
// So in this method will do walidation on validators with CreateContext group and without group
#PostMapping("/create")
void create(#RequestBody #Validated(CreateContext.class) SomeBean someBean) {
}
#PostMapping("/update")
void update(#RequestBody #Validated(UpdateContext.class) SomeBean someBean) {
}
Due to some reason testValidation is not working when is invoked DummyService.testValidation() by RestController or other spring bean.
Only on RestController side is working :/
#Validated
#Service
#AllArgsConstructor
class DummyService {
public void testValidation(#NotNull String string, #Validated(UpdateContext.class) SomeBean someBean) {
System.out.println(string);
System.out.println(someBean);
}
}

Categories

Resources