Cross field validation based on value - spring

If we are build a custom JSR 303 validator, is there any way, we can pass field's value to the validator instead of the field's name?
Here is what I am doing..
I need to build a custom class level validation which validates this case..
There are two fields A & B where B is a date field. If A's value is 1, verify that B is not null and its value is future date.
Now I was able to build a validation with these requirements following this post. In the FutureDateValidator's isValid() method, I have checked whether A's value is 1 or not and then I checked the date validity.
#CustomFutureDate(first = "dateOption", second = "date", message = "This must be a future date.")
Now I have new set of fields C and D, where D is again the date field. This time I need to verify that D is a future date if C's value is 2. In this case I cannot use the validator I already implemented because it has the first field's value hard-coded. So how do I solve this issue to reuse the same validator for these two cases.

To not hardcode values 1/2 made it customizable:
#CustomFutureDate(first = "dateOption", firstValue = "1", second = "date", message = "This must be a future date.")
To make it work you need to modify #CustomFutureDate annotation:
public #interface CustomFutureDate {
String first();
String firstValue();
...
}
and implementation:
public class CustomFutureDateValidator implements ConstraintValidator<CustomFutureDate, Object> {
private String firstFieldName;
private String firstFieldValue;
...
#Override
public void initialize(final CustomFutureDate constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
firstFieldValue = constraintAnnotation.firstValue();
...
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context) {
// use firstFieldValue member
...
}
}

Related

How to create a custom annotated validation that has a param in spring boot? [duplicate]

This question already has an answer here:
How to use an annotation element inside a custom constraint validator
(1 answer)
Closed 4 months ago.
Is it possible to pass a value when using a custom annotated validation? The logic is different depending on the param value. In the example below, the chill room may require the key-value pairs to include "snack" : "" with max length 10, min length 1 similar to the #Size(min = 1, max = 10). I'm implementing the ConstraintValidator and set up the interface.
i.e.
#ConcertValidation(dressingRoom = "chill")
private List<Map<String, String>> json;
Inside the initialize method of the ConstraintValidator implementation, you can capture the value from annotation. For example:
class ConcertValidator implements ConstraintValidator<ConcertValidation, List<Map<String, String>>> {
String dressingRoom;
#Override
public void initialize(ConcertValidation constraintAnnotation) {
dressingRoom = constraintAnnotation.dressingRoom();
}
#Override
public boolean isValid(List<Map<String, String>> value, ConstraintValidatorContext context) {
boolean isValid = false;
//TODO implement your logic depending on dressingRoom value
return isValid;
}
}
In the interface for the validator, include:
//default code
pubilc #interface ConcertValidation{
// default code
String dressingRoom() default "";
}
In the validator class, you must initialize the params you want to pass to use them in your logic for the isValid() method;
// global variables
private String dressingRoom;
#Override
public void initialize(ConcertValidation concertValidationConstraint){
this.dressingRoom = concertValidationConstraint.dressingRoom();
}
Whenever you use the custom annotation validation, just pass the params.
#ConcertValidation(dressingRoom = "chill")
private List<Map<String, String>> json;

Different validations on same field based on active validation group

I am trying to validate a request object using Hibernate Validator.
As a simple example assume that the class of the object I am trying to validate has a B bObj field where B is another class that has a String name field .
For that reason, I have implemented my own custom Constraint Annotations linked to custom MyValidator implements ConstraintValidator classes.
DTO class
#AclassValidate(groups = {Operations.Insert.class, Operations.Update.class javax.validation.groups.Default.class})
public class A {
#BclassValidate(groups = {Operations.Insert.class, Operations.Update.class})
private B bObj;
// setters, getters
}
My endpoint method signature (where validator gets invoked, and the active group is set):
#PostMapping("/test")
public A createA(
#Validated(value = Operations.Insert.class)
// #Validated(value = Operations.Update.class)
#RequestBody A a
)
My validator class
public class BclassValidator implements ConstraintValidator<BclassValidate, B> {
public void initialize(BclassValidate constraintAnnotation) {
}
public boolean isValid(B b, ConstraintValidatorContext constraintContext) {
boolean valid = true;
// Get active group here
activeGroup = ..?
if (activeGroup == Operations.Insert.class) {
// check if b.getName() equals to "John"
}
else if (activeGroup == Operations.Update.class) {
// check if b.getName() equals to "Doe"
}
return valid;
}
}
What I want to achieve is to apply different validations for the same field based on the active group. The active group is the group, set at #Validated annotation. The question is how can I retrieve the active group in order to apply different validations based on its value?
You cannot get hold of the currently validated group(s) from within a constraint validator.
Instead you should split up your constraint into several ones, in your case one for inserts and one for updates. Then you can assign these individual constraints to one validation group each.
You should get the active validation groups like this:
public class BclassValidator implements ConstraintValidator<BclassValidate, B> {
public void initialize(BclassValidate constraintAnnotation) {
}
public boolean isValid(B b, ConstraintValidatorContext constraintContext) {
boolean valid = true;
// Get active group here
Set<Class<?>> activeGroups = null;
if (context instanceof ConstraintValidatorContextImpl) {
activeGroups = ((ConstraintValidatorContextImpl) context).getConstraintDescriptor().getGroups();
} else {
activeGroups = Collections.emptySet();
}
if (activeGroups.contains(Operations.Insert.class)) {
// check if b.getName() equals to "John"
}
else if (activeGroups.contains(Operations.Update.class)) {
// check if b.getName() equals to "Doe"
}
return valid;
}
}
Hibernate validator validating the currently active groups with the configured groups for custom constraints, so we no need to write logic to validate it. But there is no way to find the currently active groups.
Reason: 'valueContext' is holding the currently active group, but not passing to 'isValid' method.
https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintTree.java

Is it possible to drive the #Size "max" value from a properties file?

I'm using Spring 3.1.1.RELEASE. I have a model with the following attribute
import javax.validation.constraints.Size;
#Size(max=15)
private String name;
I validate the model in my controller my running
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView save(final Model model,
#Valid final MyForm myForm,
I would like to have the "15" value come from a properties file instead of hard-coded, but am unclear if that's possible or how its done. Any ideas?
This is not possible. The constant expression that you provide as a value for the max attribute is added at compile time. There is no way to change the value of an annotation at runtime. Setting it from a properties file you read is therefore not possible
What you can do instead is to create and register your own Validator for the your class that has that field. For example,
public class MyValidator implements Validator {
public void validate(Object target, Errors errors) {
MyObject obj = (MyObject) target;
int length = getProperties().get("max.size");
if (obj.name.length() > length) {
errors.rejectValue("name", "String length is bigger than " + length);
}
}
public boolean supports(Class<?> clazz) {
return clazz == MyOBject.class;
}
}
Take a look at Spring's validation framework.

play framework validation binding invalid input value with POJO

I have a model TestModel.java which contains a field declared like below
#Required
#IntegerAnnotation
#Column (length = 4, nullable = false)
public Integer sort;
For the default validation annotation of Play doesn't support Integer check, so I create an annotation validation for checking the input value is an Integer or not.
The IntegerAnnotation.java :
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Constraint(checkWith = IntegerCheck.class)
public #interface IntegerAnnotation {
String message() default IntegerCheck.message;}
}
and this annotation refers to an implementations of IntegerCheck.java
public class IntegerCheck extends AbstractAnnotationCheck<IntegerAnnotation>{
final static String message = "validation.integer";
#Override
public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) throws OValException {
// TODO Auto-generated method stub
if(value == null) return false;
return isInteger(value.toString());
}
private static boolean isInteger(String s) {
return isInteger(s,10);
}
private static boolean isInteger(String s, int radix) {
if(s.isEmpty()) return false;
for(int i = 0; i < s.length(); i++) {
if(i == 0 && s.charAt(i) == '-') {
if(s.length() == 1) return false;
else continue;
}
if(Character.digit(s.charAt(i), radix) < 0) return false;
}
return true;
}
}
And there are two actions in the controller for create or update the TestModel, method create and method update.
public static void create(#Valid TestModel testModel){
if(validation.hasErrors()) {
params.flash();
validation.keep();
blank();
}
}
public static void update(#Valid TestModel testModel) {
if(validation.hasErrors()) {
params.flash();
validation.keep();
}
else{
testModel.save();
show(sys5000.id);
}
}
I found when the field sort is not entered with integer value, the value will be null in the create method, that's why I put a if null condition in the IntegerCheck.class.
Thus, if the value of filed sort is wrong typed, it detects null and return false.
Though it's not what I expect it will use the wrong typed value to verify, it worked...sort of.
The problem is in the update method.
For when updating an instance of TestModel, it won't show the wrong typed value, but the original retrieved value from database instead.
It's really a problem, for it will always return true if the retrieved data from database is already an integer. Then the validation won't do the work.
And the questions are:
Any advice for this validation strategy? I think maybe I'm apart from the right way to verify the Integer value validation.
How can I retrieve the wrong typed value from the action, or it's just not possible, for it's already not a valid data type of that field.
The behavior that you see in the update method is the way Play works!
Here is the relevant section from the documentation :
... When Play finds the id field, it loads the matching instance from
the database before editing it. The other parameters provided by the
HTTP request are then applied.
So in your case, when the sort property is null during an update, the value in the database is used.
Now if I were to try to achieve what you are trying to do, I would propably do it this way :
In my model
#Required
#CheckWith(IntegerCheck.class)
public int sort; // using primitive here to avoid null values.
static class IntegerCheck extends Check {
public boolean isSatisfied(Object model, Object sort) {
int fieldValue = (int) sort; // here you have the value as int
// Here you would check whatever you want and return true or false.
return ...
}
}

How do you handle deserializing empty string into an Enum?

I am trying to submit a form from Ext JS 4 to a Spring 3 Controller using JSON. I am using Jackson 1.9.8 for the serialization/deserialization using Spring's built-in Jackson JSON support.
I have a status field that is initially null in the Domain object for a new record. When the form is submitted it generates the following json (scaled down to a few fields)
{"id":0,"name":"someName","status":""}
After submitted the following is seen in the server log
"nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.blah.domain.StatusEnum from String value '': value not one of the declared Enum instance names"
So it appears that Jackson is expecting a valid Enum value or no value at all including an empty string. How do I fix this whether it is in Ext JS, Jackson or Spring?
I tried to create my own ObjectMapper such as
public class MyObjectMapper extends Object Mapper {
public MyObjectMapper() {
configure(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
}
}
and send this as a property to MappingJacksonMappingView but this didn't work. I also tried sending it in to MappingJacksonHttpMessageConverter but that didn't work. Side question: Which one should I be sending in my own ObjectMapper?
Suggestions?
The other thing you could do is create a specialized deserializer (extends org.codehaus.jackson.map.JsonDeserializer) for your particular enum, that has default values for things that don't match. What I've done is to create an abstract deserializer for enums that takes the class it deserializes, and it speeds this process along when I run into the issue.
public abstract class EnumDeserializer<T extends Enum<T>> extends JsonDeserializer<T> {
private Class<T> enumClass;
public EnumDeserializer(final Class<T> iEnumClass) {
super();
enumClass = iEnumClass;
}
#Override
public T deserialize(final JsonParser jp,
final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final String value = jp.getText();
for (final T enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
return enumValue;
}
}
return null;
}
}
That's the generic class, basically just takes an enum class, iterates over the values of the enum and checks the next token to match any name. If they do it returns it otherwise return null;
Then If you have an enum MyEnum you'd make a subclass of EnumDeserializer like this:
public class MyEnumDeserializer extends EnumDeserializer<MyEnum> {
public MyEnumDeserializer() {
super(MyEnum.class);
}
}
Then wherever you declare MyEnum:
#JsonDeserialize(using = MyEnumDeserializer.class)
public enum MyEnum {
...
}
I'm not familiar with Spring, but just in case, it may be easier to handle that on the client side:
Ext.define('My.form.Field', {
extend: 'Ext.form.field.Text',
getSubmitValue: function() {
var me = this,
value;
value = me.getRawValue();
if ( value === '' ) {
return ...;
}
}
});
You can also disallow submitting empty fields by setting their allowBlank property to false.
Ended up adding defaults in the EXT JS Model so there is always a value. Was hoping that I didn't have to this but it's not that big of a deal.
I have the same issue. I am reading a JSON stream with some empty strings. I am not in control of the JSON stream, because it is from a foreign service. And I am always getting the same error message. I tried this here:
mapper.getDeserializationConfig().with(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
But without any effect. Looks like a Bug.

Resources