Java bean validation not working with web init binder - java

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);

Related

Consider defining a bean of type 'UserConverter' in your configuration

I don't know what happend with my spring boot application but now I can't to start it because of an error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userConverter in webapp.controllers.UserResourceController required a bean of type 'webapp.converter.UserConverter' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'webapp.converter.UserConverter' in your configuration.
Process finished with exit code 1
Controller code:
#RestController
#RequestMapping("/api/user")
public class UserResourceController {
#Autowired
private UserServiceImpl userService;
#Autowired
private UserConverter userConverter;
#PostMapping
public ResponseEntity<UserDto> addUser(#RequestBody UserDto userDto) {
userService.persist(userConverter.toUser(userDto));
return ResponseEntity.ok().body(userDto);
}
#GetMapping
public ResponseEntity<List<UserDto>> findAllUsers() {
return ResponseEntity.ok(userConverter.toUserDtos(userService.getAll()));
}
#PutMapping("/api/user/{id}")
public ResponseEntity<UserDto> updateUser(#PathVariable Long id, #RequestBody UserDto userDto) {
User user = userConverter.toUser(userDto);
user.setId(id);
userService.persist(user);
return ResponseEntity.ok().body(userDto);
}
#GetMapping("/api/user/{id}")
public ResponseEntity<UserDto> findUser (#PathVariable Long id) {
Optional<User> user = Optional.ofNullable(userService.getByKey(id));
return ResponseEntity.ok(userConverter.toUserDto(user.get()));
}
}
The mapper class:
#Mapper(componentModel = "spring")
#Service
public abstract class UserConverter {
public abstract User toUser(UserDto userDto);
public abstract UserDto toUserDto(User user);
public abstract List<UserDto> toUserDtos(List<User> users);
}
The first I tried to run it without #Service annotation, and then with it annotation but I always see the same error.
You couldn't inject an abstract class which doesn't have any real implementation. It isn't possible in Java even without Spring. So have do you expect it could be injected?
I don't understand why do you need to inject that class. The best solution will create a utility class with the appropriate converter, like:
public final class UserConverter {
private UserConverter() {}
public static UserDTO toUserDTO(User employee) {
Department empDp = employee.getDepartment();
return UserDTO.builder()
.id(employee.getId())
.name(employee.getName())
.active(employee.getActive())
.departmentId(empDp.getId())
.departmentName(empDp.getName())
.build();
}
public static User toUser(UserDTO dto) {
Department dp = Department.builder()
.id(dto.getDepartmentId())
.name(dto.getDepartmentName())
.build();
return User.builder()
.id(dto.getId())
.name(dto.getName())
.active(dto.getActive())
.department(dp)
.build();
}
}
And call it as a static method from your code:
#GetMapping("/{id}")
public ResponseEntity<UserDto> findUser (#PathVariable Long id) {
return userService.getByKey(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
Also, your code has some mistakes for your API:
mapping to resource should be plural like #RequestMapping("/api/users") -> and only when you need operation by id add #GetMapping("/{id}")
create method should return 201 - Create status code instead of 200
if /api should be common for all your API you could define it at the configuration file for all your resources.
Snippet from applicatoin.yml:
server:
servlet:
context-path: /api
Useful references for solutions with MapStruct:
Quick Guide to MapStruct
Automating Code with MapStruct

Customize validator for spring validation framework

I have a Application Level Service below:
#Service
public class DatasetApplicationService {
private DatasetRepository datasetRepository;
#Autowired
public DatasetApplicationService(DatasetRepository datasetRepository) {
this.datasetRepository = datasetRepository;
}
#Transactional
public Dataset createDataset(#Valid CreateDatasetCommand newDatasetParams) {
...
}
}
Look at the method DatasetApplicationService.createDataset, a parameter CreateDatasetCommand is sent to it with an annotation #Valid. The annotation can be triggered to validate the CreateDatasetCommand when the method is called (see spring boot bean validation).
My question is Can I give a customized validator for specific parameter. For example I have a CreateDatasetCommandValidator which implements org.springframework.validation.Validator I wanna trigger this validator before the method is called.
I do some research with InitBinder but it seems not triggered during the process, the FAILED EXAMPLE is:
#Service
public class DatasetApplicationService {
private DatasetRepository datasetRepository;
private CreateDatasetCommandValidator validator;
#InitBinder // <----- this is not triggered
protected void initBinder(DataBinder binder) {
binder.setValidator(validator);
}
#Autowired
public DatasetApplicationService(DatasetRepository datasetRepository,
CreateDatasetCommandValidator validator) {
this.datasetRepository = datasetRepository;
this.validator = validator;
}
#Transactional
public Dataset createDataset(#Valid CreateDatasetCommand newDatasetParams) {
...
}
}
Every request that is received by Controller, the #InitBinder method will be called, in order to avoid calling for every request we define value inside it.
#InitBinder(value="user")
public void anyName(WebDataBinder binder){
binder.xxx()
}
Autowiring of the validator.
Adding of the validator to the InitBinder.
Apply #Valid annotation to the model in the concrete controller.

Spring validator class does not validate the Dto data

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;
}

Spring ignores custom ObjectMapper during data binding

I am trying to enable Jackson's "fail on unknown properties" feature for all endpoints in my service. I've added Jackson2ObjectMapperBuilder:
#Configuration
public class JacksonConfig{
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder(){
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.failOnUnknownProperties(true);
return builder;
}
}
When I use injected ObjectMapper, validation works as supposed, it throws HttpMessageNotReadableException while passing invalid json:
public class Person{
#NotNull
private String name;
#NotNull
private String surname;
/*getters and setters*/
}
#Autowired
private ObjectMapper objectMapper;
Person person = objectMapper.readValue("{ "\unknown\":\"field\", \"name\":\"John\", \"surname\":\"Smith\" }", Person.class);
However when i pass same json straight to controller validation does not occur and body of method is invoked:
#RequestMapping(value = "/something", method = RequestMethod.POST)
public void something(#Valid #RequestBody Person person){...}
Firstly I thought that MessageConverter does not use custom ObjectMapper so I checked it:
#Autowired
private MappingJackson2HttpMessageConverter converter;
converter.getObjectMapper().getDeserializationConfig().hasDeserializationFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.getMask()));
//returns true
It's even more weird because when i use repositories in other part of service validation works in POST method but does not work in PUT method.
#RepositoryRestResource(collectionResourceRel = "...", path = "...")
public interface CarRepository extends CrudRepository<Car, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN') || hasPermission(#car, T(...).CREATE_OR_MODIFY")
Car save(#Param("car") Car car);
/*other methods*/
}
Is there a simple way to force controllers and repositories to check if passed json does not contain unknown properties?
When working with spring data rest there are multiple ObjectMappers registered in the application context e.g. one for application/hal+json and one for application/json. It looks like spring data rest is not using the primary object mapper that you configured.
If you want to configure the spring data rest object mapper you can do this using a RepositoryRestConfigurerAdapter - see http://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.configuration
public class DefaultRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
#Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
if (this.objectMapperBuilder != null) {
this.objectMapperBuilder.configure(objectMapper);
}
}
}

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