JSR-303 validation in Spring controller and getting #JsonProperty name - java

I do validation with JSR-303 in my Spring app, it works as needed.
This is an example:
#Column(nullable = false, name = "name")
#JsonProperty("customer_name")
#NotEmpty
#Size(min = 3, max = 32)
private String name;
And REST API clients use customer_name as name of input field that send to API bud validation field error org.springframework.validation.FieldError returns name as name of the field.
Is there some way hot to get JSON-ish name that is specified in #JsonProperty? Or do I have to implement own mapper to map class fields name into its JSON alternative?
Edit1: Renaming class fields into names that correspond to JSON names is not alternative (for many reasons).

This can now be done by using PropertyNodeNameProvider.

There is no way to achieve this currently. We have an issue for this in the reference implementation: HV-823.
This would address the issue on the side of Hibernate Validator (i.e. return the name you expect from Path.Node#getName()), it'd require some more checking whether Spring actually picks up the name from there.
Maybe you'd be interested in helping out with implemeting this one?

For MethodArgumentNotValidException and BindException I have written a method that tries to access the private ConstraintViolation from Spring ViolationFieldError via reflection.
/**
* Try to get the #JsonProperty annotation value from the field. If not present then the
* fieldError.getField() is returned.
* #param fieldError {#link FieldError}
* #return fieldName
*/
private String getJsonFieldName(final FieldError fieldError) {
try {
final Field violation = fieldError.getClass().getDeclaredField("violation");
violation.setAccessible(true);
var constraintViolation = (ConstraintViolation) violation.get(fieldError);
final Field declaredField = constraintViolation.getRootBeanClass()
.getDeclaredField(fieldError.getField());
final JsonProperty annotation = declaredField.getAnnotation(JsonProperty.class);
//Check if JsonProperty annotation is present and if value is set
if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
return annotation.value();
} else {
return fieldError.getField();
}
} catch (Exception e) {
return fieldError.getField();
}
}
This code can be used in methods handling BindExceptions #ExceptionHandler(BindException.class) within a Class with #ControllerAdvice:
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(BindException.class)
public ResponseEntity<YourErrorResultModel> handleBindException(final BindException exception) {
for (FieldError fieldError : exception.getBindingResult().getFieldErrors()) {
final String fieldName = getJsonFieldName(fieldError);
...
}

Related

Annotation based validation on DTO level happens before field level validation like #NotNull #Size etc

I have a DTO class where I made some fields mandatory.
And, based on another type field where value can be assume A & B
if type A, need to check only 1 item can be passed, if B it can be more than 1 also.
I need to check if a list has at least 1 value in DTO class level. And, in custom annotation validation I need to check size of list based on type field of DTO.
So, in DTO
#NotNull
#Size(min = 1)
private List<#NotBlank String> items;
And, inside annotation
if (cancelType == CancellationTypeEnum.A && cancelDto.getItems().size() > 1) {
isValid = false;
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"Only one item can be sent for 'A' cancel type.").addConstraintViolation();
}
But, since field level happens later, if I pass null for items field, it goes to this annotation validator & throw NPE as field is null & I am trying to get size
Temporary solution is I do null check, then check size, but in field level anyway we are giving #NotNull.
Is there any way to do class level custom annotation to validate after field level validation happens.
In that case, it will throw field level validation as field is null & will not go to custom class level annotation
You could use JS-303 validation groups (see here), in conjunction with #Validated, from the documentation:
Variant of JSR-303's Valid, supporting the specification of validation
groups.
For example:
#CheckEnumSize(groups = {Secondary.class})
public class CancelDto {
public CancelDto(List<String> items) {
this.items = items;
}
#NotNull(groups = {Primary.class})
#Size(min = 1)
public List<#NotNull String> items;
public CancellationTypeEnum cancelType;
public List<String> getItems() {
return items;
}
}
where Primary is a simple interface:
public interface Primary {
}
and the same for Secondary:
public interface Secondary {
}
finally you could use validated as follows:
#Service
#Validated({Primary.class, Secondary.class})
public class CancelService {
public void applyCancel(#Valid CancelDto cancel) {
//TODO
}
}
when using the above code with a CancelDto with items null, you should get:
Caused by: javax.validation.ConstraintViolationException: applyCancel.cancel.items: must not be null

How to generate an example POJO from Swagger ApiModelProperty annotations?

We are creating a REST API which is documented using Swagger's #ApiModelProperty annotations. I am writing end-to-end tests for the API, and I need to generate the JSON body for some of the requests. Assume I need to post the following JSON to an endpoint:
{ "name": "dan", "age": "33" }
So far I created a separate class containing all the necessary properties and which can be serialized to JSON using Jackson:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPostRequest {
private String name;
private String age;
// getters and fluid setters omitted...
public static MyPostRequest getExample() {
return new MyPostRequest().setName("dan").setAge("33");
}
}
However, we noticed that we already have a very similar class in the codebase which defines the model that the API accepts. In this model class, the example values for each property are already defined in #ApiModelProperty:
#ApiModel(value = "MyAPIModel")
public class MyAPIModel extends AbstractModel {
#ApiModelProperty(required = true, example = "dan")
private String name;
#ApiModelProperty(required = true, example = "33")
private String age;
}
Is there a simple way to generate an instance of MyAPIModel filled with the example values for each property? Note: I need to be able to modify single properties in my end-to-end test before converting to JSON in order to test different edge cases. Therefore it is not sufficient to generate the example JSON directly.
Essentially, can I write a static method getExample() on MyAPIModel (or even better on the base class AbstractModel) which returns an example instance of MyAPIModel as specified in the Swagger annotations?
This does not seem to be possible as of the time of this answer. The closest possibilities I found are:
io.swagger.converter.ModelConverters: The method read() creates Model objects, but the example member in those models is null. The examples are present in the properties member in String form (taken directly from the APIModelParameter annotations).
io.swagger.codegen.examples.ExampleGenerator: The method resolveModelToExample() takes the output from ModelConverters.read(), and generates a Map representing the object with its properties (while also parsing non-string properties such as nested models). This method is used for serializing to JSON. Unfortunately, resolveModelToExample() is private. If it were publicly accessible, code to generate a model default for an annotated Swagger API model class might look like this:
protected <T extends AbstractModel> T getModelExample(Class<T> clazz) {
// Get the swagger model instance including properties list with examples
Map<String,Model> models = ModelConverters.getInstance().read(clazz);
// Parse non-string example values into proper objects, and compile a map of properties representing an example object
ExampleGenerator eg = new ExampleGenerator(models);
Object resolved = eg.resolveModelToExample(clazz.getSimpleName(), null, new HashSet<String>());
if (!(resolved instanceof Map<?,?>)) {
// Model is not an instance of io.swagger.models.ModelImpl, and therefore no example can be resolved
return null;
}
T result = clazz.newInstance();
BeanUtils.populate(result, (Map<?,?>) resolved);
return result;
}
Since in our case all we need are String, boolean and int properties, there is at least the possibility to parse the annotations ourselves in a crazy hackish manner:
protected <T extends MyModelBaseClass> T getModelExample(Class<T> clazz) {
try {
T result = clazz.newInstance();
for(Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ApiModelProperty.class)) {
String exampleValue = field.getAnnotation(ApiModelProperty.class).example();
if (exampleValue != null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
setField(result, field, exampleValue);
field.setAccessible(accessible);
}
}
}
return result;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Could not create model example", e);
}
}
private <T extends MyModelBaseClass> void setField(T model, Field field, String value) throws IllegalArgumentException, IllegalAccessException {
Class<?> type = field.getType();
LOGGER.info(type.toString());
if (String.class.equals(type)) {
field.set(model, value);
} else if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {
field.set(model, Boolean.parseBoolean(value));
} else if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
field.set(model, Integer.parseInt(value));
}
}
I might open an Issue / PR on Github later to propose adding functionality to Swagger. I am very surprised that nobody else has seemed to request this feature, given that our use case of sending exemplary model instances to the API as a test should be common.

Jackson fail deserialization if field is not present

I have a pojo like
class Pojo {
private String string;
public String getString() { return string; }
#JsonSetter(nulls = Nulls.FAIL)
public void setString(String string) {
this.string = string;
}
}
I want to make jackson fail when deserializing if the string field is null or absent. (i.e. {"string":null} or {})
As you can see, I've succeeded in the first goal with the JsonSetter annotation. What I am hoping for now is something like that but for a missing property. I found a few other questions asking similar things but they were quite old and referenced features that might be implemented in the future. With the recent release of jackson 2.9, I was hoping maybe this is now possible.
#JsonProperty has a required element that can be used
to ensure existence of property value in JSON
Unfortunately, Jackson currently (2.9) only supports it for use with #JsonCreator annotated constructors or factory methods. Since #JsonSetter only works with setters, you'll have to do the null validation yourself.
For example, you'd define a constructor like
#JsonCreator
public Pojo(#JsonProperty(value = "string", required = true) String string) {
if (string == null) {
throw new IllegalArgumentException("string cannot be null");
}
this.string = string;
}
If the property is present, but set to null, Jackson would throw an InvalidDefinitionException that wraps the IllegalArgumentException thrown in the constructor.
If the property is absent, Jackson would throw a MismatchedInputException stating that a property is missing.
Both of these exceptions are subtypes of JsonMappingException, so you can easily deal with them the same way.
With this solution, you could also get rid of the setter altogether and make the field final if that suited your design better.
You may perform bean validation here by annotating the field of interest with #NotNull.
You may remove the annotation from your setter.
class Pojo {
#NotNull
private String string;
public String getString() { return string; }
public void setString(String string) {
this.string = string;
}
}
Similarly if you want to fail the validation for other constraints like size, pattern etc, you may use similar equivalent annotations available here.

How to add JSR 303 validation to arguments of a method

I would like to use Hibernate Validator to validate arguments of my method.
I have tried to look-up examples and went through the following example
JSR 303. Validate method parameter and throw exception
But the answer does not exactly solve my problem and also the links are expired in the answer.
Here is a sample code of what I have tried.
//removing imports to reduce the length of code.
/**
* Created by Nick on 15/06/2016.
*/
#Component
public class ValidationService {
#Value("${namesAllowed}")
private String validNames;
#Autowired
private Validator validator;
public boolean validateName(
#NotEmpty
#Pattern(regexp = ".*'.*'.*")
#Email
#Length(max = 10)
String name
) {
Set<ConstraintViolation<String>> violations = validator.validate(name);
for (ConstraintViolation<String> violation : violations) {
String propertyPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
System.out.println("invalid value for: '" + propertyPath + "': " + message);
}
List<String> namesAllowed= Arrays.asList(validNames.split(","));
if (namesAllowed.contains(name.substring(name.indexOf(".") + 1))) {
return true;
}
return false;
}
}
}
Method validation has been standardized as part of Bean Validation 1.1. You can learn more about it in the Hibernate Validator reference guide.
With Spring you need to configure a MethodValidationPostProcessor bean and annotate the constrained class with #Validated. Also this answer may be helpful to you.

Using #NonNull for checking class members too

Let's say I have a class that has 3 members:
Class A {
private String string1; /** Cannot be null */
private String string2; /** Cannot be null */
private String string3; /** Can be null */
}
I have 2 method that accepts an object of this class as a parameter. One of the methods needs to check that the non nullable fields are present while in the other one, it doesn't matter:
public int func1(A object); /** Check non nullable fields */
public int func2(A object); /** No check required */
Is there any clean way to do it? Using #NonNull annotations or something?
I have tried various ways but none of them work. All the NonNull only help make sure that the setter doesn't get null as the parameter. Or that the object itself isn't null.
I can't seem to find something that does this kind of recursive null check on the object.
It'd be great if any of you could help. :)
You need a bean Validator, a class used to check that the bean is OK. In Spring there are a number of implementations. For example, see SmartValidatorand LocalValidatorFactoryBean
The #valid annotation is a nice way to call automagically the validator. As you are using Spring you can avoid the manual creation of the validator. It only works if the method is called by Spring (or any equivalent container). You may get the validation results in a BindingResult object. For example:
#RequestMapping(value = "/MyPath", method = RequestMethod.POST)
public String postCostForm(Model model, #Valid MyForm myForm, BindingResult result){
if(result.hasErrors()){
for(ObjectError error : result.getAllErrors()){
// Do something
}
return "/error";
}else{
return "/success";
}
}
Validation is very powerfull and sometimes complex. You may create groups of validation and check just one group of them. You can create your custom constraint tags, you can call validation methods and you may customize and internationalize the messages returned if the validation fails.
class A {
#NotNull
private String string1; /** Cannot be null */
#NotNull
private String string2; /** Cannot be null */
private String string3; /** Can be null */
}
And in the method signature have #Valid
public int function(#Valid A object)
Use #Required annotation in Spring's
private String string1;
#Required
public void setString1(String string1) {
this.string1= string1;
}

Categories

Resources