I have the next hierarchy:
public class A extends B {
#Valid
public A(String languageCode, C token) {
super(languageCode, token);
}
}
The B class has a property call language code that should be validated with #NotEmpty only if a service has the #Validated({RequiringLanguageCode.class})
public class B extends D {
private String languageCode;
#Valid
public B(String languageCode, C token) {
super(token);
this.languageCode = languageCode;
}
#NotEmpty(groups = {RequiringLanguageCode.class})
public String getLanguageCode() {
return languageCode;
}
}
Now, D is the base class that has a C property that should be validated as NotNull and also the values that are inside the C class.
public class D {
private C token;
#Valid
public D(C token) {
this.token = token;
}
#NotNull
public C getToken() {
return token;
}
}
C class contains two String that are validated as #NotEmpty:
public class C {
private String value1;
private String value2;
public C(String value1,
String value2) {
this.value1 = value1;
this.value2 = value2;
}
#NotEmpty
public String getValue1() {
return value1;
}
#NotEmpty
public String getValue2() {
return value2;
}
}
When trying to test this using mockito the values of C class aren't validated if the token values (value1 and value2) are empty.
Can somebody help me?
Does somebody have any idea about what is happening?
The test are as followed:
#RunWith(SpringJUnit4ClassRunner.class)
#ActiveProfiles("test")
public class ATest {
#Autowired
private AAssembler aAssembler;
#Mock
A request;
#Mock
C token;
#Test
public void test() {
when(token.getValue1()).thenReturn("");
when(token.getValue2()).thenReturn("key");
when(request.getToken()).thenReturn(token);
assertThatIllegalArgumentException()
.isThrownBy(() -> aAssembler.build(request));
}
}
The AAssembler is annotated as #Validated({RequiringLanguageCode.class})
The reason why I launch an IllegalArgumentException instead of ConstraintViolationException is something out of scope of this problem. I catch the constraint violation an throw the IllegalArgumentException instead.
The Assembler build method has also a constraint annotated as :
public Response build(#Valid #NotNull A request) {
....
}
I would be very thankful if somebody can help me. Thanks anyway.
I had the same problem javax.validation.ConstraintDeclarationException: HV000131: A method return value must not be marked for cascaded validation more than once in a class hierarchy, but the following two methods are marked as such: while testing REST services annotated with javax.validation.valid.
Based on this AutoValue issue I thought maybe the same happens here, too. Mockito copies by default all annotations. So I was able to resolve this problem by preventing this.
A stub = Mockito.mock(A.class, withSettings().withoutAnnotations());
The disadvantage compared to the AutoValue solution no annotation will be copied. So if you need them you're stuck.
Regards Christian
Related
My Custom DTO class is as follows :
public class TestDto1 {
private String key;
private String val;
#AssertTrue
private boolean isValid() {
return key !=null || val !=null;
}public class TestDto1 {
private String key;
private String val;
#AssertTrue
private boolean isValid() {
return key !=null || val !=null;
}
My Parent DTO class :
public class TestDto {
private String id;
#Valid
private TestDto1 tes;
public TestDto1 getTes() {
return tes;
}
public void setTes(TestDto1 tes) {
this.tes = tes;
}
public String getId() {
return id;
While running the app and hitting api with following JSON getting following error:
{
"id":"1234",
"tes":{
}
}
JSR-303 validated property 'tes.valid' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)] with root cause
org.springframework.beans.NotReadablePropertyException: Invalid property 'tes.valid' of bean class [com.example.thirdparty.controller.TestDto]: Bean property 'tes.valid' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Please let me know what needs to be done here
This is not a field that gets validated but a method which is kind of read as a virtual field from that method.
I think the method has to be declared as public to become accessible for validation
#AssertTrue
public boolean isValid() {
return key !=null || val !=null;
}
In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}
From the frontend I receive GET-request which contains encoded json string as one of its parameters:
http://localhost:8080/engine/template/get-templates?context=%7B%22entityType%22%3A%22DOCUMENT%22%2C%22entityId%22%3A%22c7a2a0c6-fd34-4f33-9cb8-14c2090565ea%22%7D&page=1&start=0&limit=25
Json-parameter 'context' without encoding (UUID is random):
{"entityType":"DOCUMENT","entityId":"c7a2a0c6-fd34-4f33-9cb8-14c2090565ea"}
On backend my controller's method which handle that request looks like this:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
'Context' domain class:
public class Context {
private String entityType;
private UUID entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public UUID getEntityId() {
return entityId;
}
public void setEntityId(UUID entityId) {
this.entityId = entityId;
}
}
I believed Spring's Jackson module would automatically convert that kind of json to java object of Context class, but when I run this code it gives me exception:
org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'com.company.domain.Context'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.company.domain.Context': no matching editors or conversion strategy found
On StackOverflow I've seen similar questions, but those were about POST-requests handling (with #RequestBody annotation), which doesn't fit with GET-request.
Could you help me to solve this problem?
Thanks in advance.
I think you need to specify that your GET mapping is looking to consume JSON:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET, consumes = "application/json")
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
If this doesn't work then you can call the Jackson ObjectMapper yourself:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") String context) {
ObjectMapper mapper = new ObjectMapper();
Context myContext = mapper.readValue(context, Context.class);
//...
}
As far as I know Spring does not have the mechanism to convert from a String to a UUID in older releases. In such case you should declare you entityId as a String and then use a converter in order to convert it to UUID.
So your Context class should be like below:
public class Context {
private String entityType;
private String entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public String getEntityId() {
return entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public UUID getEntityIdAsUUID() {
return convertToUUID(this.entityId);
}
// Helper Conversion String to UUID method
private UUID convertToUUID(String entityId){
return UUID.fromString(entityId);
}
}
I faced the same issue in both Jersey and Spring MVC when trying to convert the JSON String {"x":"1001822.831","y":"200716.8913"} to a object of a class called Point
Point class is as below
public class Point
{
private Double x;
private Double y;
//getters and setters
}
As per Jersey documentation, I added the below method to Point class and it worked for both Jersey and Spring MVC.
//used by jax rs & spring mvc for converting queryParam String to Point
public static Point valueOf(String json) throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, Point.class);
}
Please refer to section 3.2 here https://jersey.github.io/documentation/latest/jaxrs-resources.html#d0e2271
In my controller I have a method such as bellow:
public QueryResult<TrsAccount> listExclude(String codeAccount, String searchFilter, String order, int pageNumber,
int pageSize){}
But before executing this method I have to chech if:
Assert.TRUE(codeAccount.matches("^[0-9]{1,20}$"));
Because this is very frequent in my application and it is not only this case, I want a general approach to check the argument format. The way I'm using now is the use of AOP, in which:
#Aspect
public class HijackBeforeMethod {
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut("execution(* *(..))")
public void methodPointcut() {
}
#Before(value = "controllerBean() && methodPointcut()", argNames = "joinPoint")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
for (int count = 0; count < paramNames.length; count++) {
String tempParam = paramNames[count];
Object tempValue = args[count];
if (tempParam.toLowerCase().equalsIgnoreCase("codeAccount") && Assert.isNotNull(tempValue)
&& Assert.isNotEmpty((String) tempValue)) {
Assert.TRUE(((String) tempValue).matches("^[0-9]{1,20}$"));
}
}
}
}
As you can see, this is very rudimentary and error prone code snippet. Is there any better solutions??
Using AOP in Controllers is not really recommended. A better approach would be to use JSR 303 / JSR 349 Bean Validation, but that would probably require wrapping the string in a value object, which is then annotated accordingly.
If you insist on solving this with AOP, you'll probably need a ControllerAdvice
Just like #Sean Patrick Floyd said, using Bean Validation is more advisable.
Firstly, define a class which extends from org.springframework.validation.Validator like:
#Component
public class CodeAccountValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
if (Assert.isNotNull(target) && Assert.isNotEmpty((String) target)) {
Assert.TRUE(((String) target).matches("^[0-9]{1,20}$"));
}
}
}
Then add #Validated annotation to your controller like:
public QueryResult<TrsAccount> listExclude(
#Validated(CodeAccountValidator.class)String codeAccount,
String searchFilter,
String order, int pageNumber,
int pageSize) {
... ...
}
Trying to solve this with AOP is something you shouldn't do. Instead use an object to bind your properties and validate that object.
public class QueryCriteria {
private String codeAccount;
private String searchFilter;
private int pageNumber;
private int pageSize;
private String order;
// Getters / Setters.
}
Then modify your controller method
public QueryResult<TrsAccount> listExclude(#Valid QueryCriteria criteria, BIndingResult result) { ... }
Then either use a Spring Validator which validates what you need .
public QueryCriteriaValidator implements Validator {
private final Pattern ACCOUNT_EXPR = Pattern.compile("^[0-9]{1,20}$");
public boolean supports(Class<?> clazz) {
return QueryCriteria.isAssignable(clazz);
}
public void validate(Object target, Errors errors) {
final QueryCriteria criteria = (QueryCriteria) target;
if (!ACCOUNT_EXPR.matcher(criteria.getCodeAccount()).matches()) {
errors.rejectValue("codeAccount", "invalid.format");
}
}
}
In an #InitBinder in your controller method register this validator
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new QueryCriteriaValidator());
}
When using JSR-303 you don't need this and you could simply annotate the codeAccount field with the #Pattern annotation.
#Pattern(regexp="^[0-9]{1,20}$")
private String codeAccount;
The validation works nicely together with Spring MVC and error reporting using I18N. So instead of trying to hack around it with exceptions work with the framework.
I suggest a read of the validation section and binding section of the Spring Reference guide.
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);
}
}