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);
}
}
Related
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
}
}
}
As we all know, there is a big problem with a partial update of the entity. Since the automatic conversion from json strings to the entity, all fields that have not been transferred will be marked null. And as a result, the fields that we did not want to reset will be reset.
I will show the classical scheme:
#RestController
#RequestMapping(EmployeeController.PATH)
public class EmployeeController {
public final static String PATH = "/employees";
#Autowired
private Service service;
#PatchMapping("/{id}")
public Employee update(#RequestBody Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
}
#Service
public class Service {
#Autowired
private EmployeeRepository repository;
#Override
public Employee update(Long id, Employee entity) {
Optional<T> optionalEntityFromDB = repository.findById(id);
return optionalEntityFromDB
.map(e -> saveAndReturnSavedEntity(entity, e))
.orElseThrow(RuntimeException::new);
}
private T saveAndReturnSavedEntity(Employee entity, Employee entityFromDB) {
entity.setId(entityFromDB.getId());
return repository.save(entity);
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
and as I have already said that in the current implementation we will not be able to perform a partial update in any way. That is, it is impossible to send an update of only one field in a json line; all fields will be updated, and in null (excepted passed).
The solution to this problem is that you need to perform the conversion from string json to the entity in manual. That is, do not use all the magic from Spring Boot (which is very sad).
I will also give an example of how this can be implemented using merge at the json level:
#RestController
#RequestMapping(EmployeeRawJsonController.PATH)
public class EmployeeRawJsonController {
public final static String PATH = "/raw-json-employees";
#Autowired
private EmployeeRawJsonService service;
#PatchMapping("/{id}")
public Employee update(#RequestBody String json, #PathVariable Long id) {
return service.update(id, json);
}
}
#Service
public class EmployeeRawJsonService {
#Autowired
private EmployeeRepository employeeRepository;
public Employee update(Long id, String json) {
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
return optionalEmployee
.map(e -> getUpdatedFromJson(e, json))
.orElseThrow(RuntimeException::new);
}
private Employee getUpdatedFromJson(Employee employee, String json) {
Long id = employee.getId();
updateFromJson(employee, json);
employee.setId(id);
return employeeRepository.save(employee);
}
private void updateFromJson(Employee employee, String json) {
try {
new ObjectMapper().readerForUpdating(employee).readValue(json);
} catch (IOException e) {
throw new RuntimeException("Cannot update from json", e);
}
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
With this solution, we eliminate the problem associated with the partial update.
But here another problem arises, that we are losing the automatic addition of validation of beans.
That is, in the first case, validation is enough to add one annotation #Valid:
#PatchMapping("/{id}")
public Employee update(#RequestBody #Valid Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
But we can't do the same when we perform manual deserialization.
My question is, is there any way to enable automatic validation for the second case?
Or maybe there are other solutions that allow you to use Spring Boot magic for Bean Validation.
What you need is not the normal validation , which can achieved through manual validator call.Let’s now go the manual route and set things up programmatically:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(object);
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage());
}
To validate a bean, we must first have a Validator object, which is constructed using a ValidatorFactory.
Normal validations on Spring Controllers specified with #Valid annotations are triggered automatically during the DataBinding phase when a request is served.All validators registered with the DataBinder will be executed at that stage. We can't do that for your case, so you can manually trigger the validation like above.
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.
I have this Controller in Java:
#Controller
public class AuthenticationController extends AbstractController {
#RequestMapping(value = Constantes.MAPPING_AUTH_BASE_ASP, method = { RequestMethod.POST })
public String authenticate(#Valid ComunicationWithAspRequest comunicationWithAspRequest, BindingResult result,
RedirectAttributes redirectAttributes, HttpSession sesion) throws Exception {
...
...
...
}
}
When I scan my code in Fortify, the object comunicationWithAspRequest causes the Mass Assignment: Insecure Binder Configuration Vulnerability. Is possible to control which HTTP request parameters will be used in the binding process and which ones will be ignored?
You may refer to the problem Prevent mass assignment in Spring MVC with Roo.
In your case, you can use #InitBinder provided by Spring MVC. #InitBinder would specify the white list for json and bean mapping.
In my experience, I used #RequestBody for auto-binding. I need to add #JsonIgnore to specify the property that would not include for the mapping.
SimpleController.java
#RequestMapping(value="/simple")
public String simple(#Valid #RequestBody User user){
simpleService.doSomething();
}
User.java
public class User{
private String name;
#JsonIgnore
private String dummy;
public void getName(){return name;}
public void setName(name){this.name = name;}
public void getDummy(){return dummy;}
public void setDummy(dummy){this.dummy= dummy;}
}
By adding #JsonIgnoreProperties(ignoreUnknown = true) annotation on the class my issue got resolved in case we don't know what to ignore.
#JsonIgnoreProperties(ignoreUnknown = true)
public class className{
}
class employee{
...
private long phone;
...
}
I want to validate phone number using spring jsr303 validator, In my Controller I am using #valid. I am successfully validating entered value is number or string by using generic typeMismatch placing in error message property file.
But I want to validate entered number format is correct or not.(#pattern for string only)
How to achieve this one,please suggest me.
Normally phone numbers are String and you can validate by using #Pattern, but if you want to validate any fields you can do like this.
Custom annotation Javax validator
#javax.validation.Constraint(validatedBy = { PhoneNumberConstraintValidator.class })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface ValidPhoneNumber {
}
public class PhoneNumberConstraintValidator implements ConstraintValidator<ValidPhoneNumber, Long> {
#Override
public void initialize(final ValidPhoneNumber constraintAnnotation) {
// nop
}
#Override
public boolean isValid(final Long value, final ConstraintValidatorContext context) {
//your custom validation logic
}
}
class employee{
...
private long phone;
#ValidPhoneNumber
public Long getPhone() { return phone; }
...
}
OR simpler if you have hibernate validator, you can just add this method in your entity class.
#org.hibernate.validator.AssertTrue
public boolean validatePhoneNumber() { }