Spring validator class does not validate the Dto data - java

I am implementing a microservice which has only to validate Transaction
from the request body, and if it is valid, send it to the next microservice. I have implemented the Validator, but it does not seem to work
Transaction DTO:
public class Transaction {
private TransactionType transactionType;
private String iban;
private String CNP;
private String name;
private String description;
private Float sum;
...
}
The Validator:
#Component()
public class TransactionValidation implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return Transaction.class.equals(aClass);
}
#Override
public void validate(Object object, Errors errors) {
Transaction transaction = (Transaction) object;
if(!isValidCnp(transaction.getCNP())){
errors.rejectValue("CNP", "CNP validation error");
}
if(!isValidIban(transaction.getIban())){
errors.rejectValue("IBAN", "IBAN validation error");
}
if(isInputStringEmpty(transaction.getName())){
errors.rejectValue("name", "Name validation error");
}
}
...
}
The Controller does nothing but simply returns the transaction it receives:
#PostMapping()
public Transaction validateTransaction(#RequestBody #Valid Transaction transaction){
return transaction;
}
But it does not seem to work, it seems that the app does not use the Validator implemented above. Why? Do I have to register my validator anywhere?

I believe the #Valid annotation works on JSR-303 validation spec with annotations in your DTO. You have 2 options
public class Transaction {
#NonNull
#NotEmpty
#NumericIbanConstriant
private String iban;
}
#Constraint(validatedBy = NumericIbanConstriantValidator.class)
public #interface NumericIbanConstriant {
}
public NumericIbanConstriantValidator implements ConstraintValidator<NumericIbanConstraint, String> {
// validation logic for iban string being numeric perhaps?
}
OR in your controller directly autowire the TransactionValidation and call "validate" passing in Transaction, and Errors object
#PostMapping()
public Transaction validateTransaction(#RequestBody Transaction transaction, Errors errors){
transactionValidation.validate(transaction, errors);
return transaction;
}

Related

Spring RabbitMQ: implement additional validations without custom annotations

In a Spring RabbitMQ project I am looking for a way to programmatically validate an object that has JSR303 annotations (like #NotNull, #Size, etc) while at the same time requires some custom validation logic. I would normally use a ConstraintValidator in combination with a custom Annotation, but the use of custom Annotations is not an option in this case.
I have the following (simplified) class, which is generated by Swagger and therefore cannot be edited:
#ApiModel(description="User")
public class User {
private String name;
#NotNull
#Size(min = 1, max = 6)
public String getName() {
return this.name;
}
...
}
The additional validation logic is encapsulated in a validator:
#Component
public class UserValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
User user = (User) o;
...
if(!valid) {
errors.reject("some rejection");
}
}
}
The service in which the validation occurs:
#Service
#RequiredArgsConstructor
public class SomeService {
private final javax.validation.Validator validator; // might as well be org.springframework.validation.Validator if that works better
public void someMethod(User user) {
if (!validator.validate(user).isEmpty()) {
// handle invalid user
}
...
}
}
However, the UserValidator is not being invoked. Is there some way to make Spring aware of the UserValidator? I have read some topics on using an InitBinder, however as this is not a web MVC project but a rabbitMQ project I'm not sure whether this can be used.
It is not clear from your description how this is relevant to Spring AMQP, but if you want to use a validator on the listener method level, you should configure it respectively:
#Configuration
#EnableRabbit
public class Config implements RabbitListenerConfigurer {
...
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setValidator(new MyValidator());
}
}
See docs for more info: https://docs.spring.io/spring-amqp/docs/current/reference/html/#rabbit-validation

#Valid annotation not being verified through Junit Quarkus

I'm working with Quarkus and facing weird scenario where #Valid annotation not working as expected in JUnit but working fine in REST API.
Any help would be appreciated.
Thanks in advance.
Test
#Test
void
validateContent_GivenInvalidFieldName_ThrowConstraintViolationException() {
// Arrange
String invalidFieldName =
"{\"i\": \"Test\"}";
// Act, Assert
assertThrows(
ConstraintViolationException.class,
() -> service.validate(invalidFieldName),
"ConstraintViolationException exception should be thrown");
}
Service class method
public void validate(String jsonString){
try {
ValidField validField = convert(jsonString);
validateContent(validField);
} catch (JsonProcessingException e) {
createValidationError(e.getMessage(), "content");
}
}
void validateContent(
#Valid ValidField content) {
// #Valid doing the work here
}
Model class
public class ValidField{
private String id;
#NotBlank
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
The #Valid annotation does not do anything by itself, it's just a "metadata" instructing an validator implementation to validate the entity. In your rest endpoint is working because it's a bean managed by quarkus, and the framework will automatically weave the validator for you. This will not happen in a plain junit test. The easiest way to make it work in your case, is to turn the validator into a bean and create a test using the QuarkusTest, like this:
#ApplicationScoped
public class YourValidatorService() {
public void validate(String jsonString) {
try {
ValidField validField = convert(jsonString);
validateContent(validField);
} catch (JsonProcessingException e) {
createValidationError(e.getMessage(), "content");
}
}
void validateContent(
#Valid ValidField content) {
// #Valid doing the work here
}
}
and injecting into your test using the #QuarkusTest annotation
#QUarkusTest
public class YourValidatorServiceTest() {
#Inject
private YourValidatorService service;
#Test
void validateContent_GivenInvalidFieldName_ThrowConstraintViolationException() {
// Arrange
String invalidFieldName =
"{\"i\": \"Test\"}";
// Act, Assert
assertThrows(
ConstraintViolationException.class,
() - > service.validate(invalidFieldName),
"ConstraintViolationException exception should be thrown");
}
}
By doing that, you will enable the Quarkus CDI on tests, which will weave the validator for you.
You can read a little bit more about how the validations work, here.

Validation in service layer (SpringBoot)

I have a DTO which is validated at Controller layer with a mix of BeanValidation (javax.validation) and a custom Validator (org.springframework.validation.Validator). This way I can check if the input provided is valid and then convert the DTO in an entity and forward it to the Service layer.
#Data
public class UserDTO {
#NotBlank
#Size(max = 25)
private String name;
#NotNull
private Date birthday;
#NotNull
private Date startDate;
private Date endDate;
private Long count;
}
public class UserDTOValidator implements Validator {
private static final String START_DATE= "startDate";
private static final String END_DATE= "endDate";
private static final String COUNT= "count";
#Override
public boolean supports(Class<?> clazz) {
return UserDTO.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserDTO vm = (UserDTO) target;
if (vm.getEndDate() != null) {
if (vm.getStartDate().after(vm.getEndDate())) {
errors.rejectValue(START_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
if (vm.getEndDate().equals(vm.getStartDate()) || vm.getEndDate().before(vm.getStartDate())) {
errors.rejectValue(END_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
}
if (vm.getCount() < 1) {
errors.rejectValue(COUNT, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
.....
}
}
public class UserController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserDTOValidator());
}
#PostMapping()
public ResponseEntity<UserDTO> create(#RequestBody #Valid UserDTO userDTO) {
.....
}
.....
}
Then there is the business logic validation. For example: the #Entity User's startDate must be after some event occurred and the count has to be greater than some X if the last created User's birthDay is in Summer, in other case, the entity should be discarded by the User service.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private SomeEventService someEventService ;
#Override
public User create(User entity) {
String error = this.validateUser(entity);
if (StringUtils.isNotBlank(error)) {
throw new ValidationException(error);
}
return this.userRepository.save(entity);
}
....
private String validateUser(User entity) {
SomeEvent someEvent = this.someEventService.get(entity.getName());
if (entity.getStartDate().before(someEvent.getDate())) {
return "startDate";
}
User lastUser = this.userRepository.findLast();
....
}
}
However I feel like this is not the best approach to handle business logic validation. What should I do? ConstraintValidator/HibernateValidator/JPA Event listeners? Can they work at #Entity class level or I have to create X of them for each different field check? How do you guys do it in a real production application?
In my suggestion,
Use classic field level validation by #Valid
sample
void myservicemethod(#Valid UserDTO user)
For custom business level validation in entity level, create validate method in DTO itself
sample
class UserDTO {
//fields and getter setter
void validate() throws ValidationException {
//your entity level business logic
}
}
This strategy will help to keep entity specific validation logic will be kept within the entity
If still you have validation logic that requires some other service call, then create custom validation annotation with custom ConstraintValidator (eg. question on stackoverflow). In this case, my preference will be to invoke UserDTO.validate() from this custom validator in spiote of calling from service
This will help to keep your validation logic separated from service layer and also portable and modular

Java bean validation not working with web init binder

I have a controller looks like this:
#RestController
#RequestMapping(value="/api/events")
public class EventController{
#Inject
private EventValidator eventValidator;
#InitBinder
#Qualifier("eventValidatior")
private void initBinder(WebDataBinder binder){
binder.setValidator(eventValidator);
}
#PostMapping()
public ResponseEntity<EventModel> save(#Valid #RequestBody EventRequest request, BindingResult result){
if(result.hasErrors()){
//some validation
}
//some other logic
}
}
Then i have a EventRequest pojo:
public class EventRequest{
private String eventName;
#Valid
#NotNull
private List<Event> events;
//setters and getters
}
In my controller, I have 2 types of validation, the InitBinder, and also java bean validation (JSR-303) that use #NotNull in EventRequest class.
The problem is, if I have BindingResult result in the controller, the #NotNull annotation won't work. And even the cascaded validation in Event class is not working too.
Why is that, how can I have both 2 types of validation?
Tried to add this but still not working
#Configuration
public class ValidatorConfig {
#Bean
public LocalValidatorFactoryBean defaultValidator() {
return new LocalValidatorFactoryBean();
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
binder.setValidator(eventValidator); will replace other registered validators.
Change to:
binder.addValidators(eventValidator);

Spring MVC validator annotation + custom validation

I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.
My User class:
public class User
{
#NotEmpty
private String name;
#NotEmpty
private String login; // should be unique
}
My validator:
#Component
public class UserValidator implements Validator
{
#Autowired
private UserDAO userDAO;
#Override
public boolean supports(Class<?> clazz)
{
return User.class.equals(clazz) || UsersForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
/*
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
*/
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
My controller:
#Controller
public class UserController
{
#Autowired
private UserValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
#RequestMapping(value = "/save")
public ModelAndView save(#Valid #ModelAttribute("user") final User user,
BindingResult result) throws Exception
{
if (result.hasErrors())
{
// handle error
} else
{
//save user
}
}
}
So, Is it possible to use custom validator and annotation together? And if yes how?
I know this is a kind of old question but, for googlers...
you should use addValidators instead of setValidator. Like following:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}
PS: addValidators accepts multiple parameters (ellipsis)
if you checkout the source of org.springframework.validation.DataBinder you will see:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
....
public void setValidator(Validator validator) {
assertValidators(validator);
this.validators.clear();
this.validators.add(validator);
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
....
}
as you see setValidator clears existing (default) validator so #Valid annotation won't work as expected.
If I correctly understand your problem, as soon as you use you custom validator, default validation for #NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.
You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.
Using java configuration it could look like :
#Configuration
public class ValidatorConfig {
#Autowired
private MessageSource messageSource;
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Then you modify UserValidator to use it :
#Component
public class UserValidator implements Validator
{
#Autowired
#Qualifier("basicValidator")
private Validator basicValidator;
#Autowired
private UserDAO userDAO;
// ...
#Override
public void validate(Object target, Errors errors)
{
basicValidator.validate(target, errors);
// eventually stop if any errors
// if (errors.hasErrors()) { return; }
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
Well for me you have to delete the
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
Leave the
#Valid #ModelAttribute("user") final User user,
BindingResult result
And after in the function make
validator.validate(user,result)
This way you will use the validation basic with the #Valid and after you will put make the more complex validation.
Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.
Maybe is wrong, i use always the #Valid without any validator.

Categories

Resources