Validating field values in a Java POJO - java

Use Case:
Let's assume this is my POJO:
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
I am reading certain values from an external file and creating a POJO using a parameterised constructor. All the fields have certain validation constraints for them.
What I am looking for is a way for those constraints to be evaluated automatically when I am creating an object using the parameterised constructor. If one or more validation constraints fail, it should throw an error.
What I have tried so far:
I have tried the Bean Validation approach in Spring by creating my own annotation and validator. The code is below:
POJO
#ValidChecker(groups = Default.class)
class Sample{
String field1;
String field2;
double field3;
LocalDateTime field4;
LocalDateTime field5;
//...getters, setters and parameterised constructor
}
ValidChecker Annotation
#Constraint(validatedBy = DataValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidChecker {
String message() default "Data is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DataValidator.java
#SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
public class DataValidator implements ConstraintValidator<ValidChecker, ValidationData> {
#Override
public void initialize(ValidChecker constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ValidationData validationData, ConstraintValidatorContext constraintValidatorContext) {
if (validationData == null) {
return false;
}
if (BigDecimal.valueOf(validationData.getField3()).scale() != 2) {
return false;
}
if (validationData.getField5().isBefore(validationData.getField4())) {
return false;
}
return true;
}
}
The above code didn't work.
Suggestions Needed
Problem with the above approach
Alternate approach using Spring
Alternate approach by using some third party library
I looked quite a bit but couldn't find an approach without Spring bean validation. Can someone please help?

You could use the Bean Validation API directly in your constructor:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Sample>> violations = validator.validate(this);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}

Related

javax.validation annotation for string base64

I have a rest service with my request body bean annotated with javax.validation like #NotBlank #NotNull #Pattern etc., and in one specific field I receive a file encoded as a string base64,
so, is there an annotation, or how could I write a custom validation annotation, so it would check if the string is really a base64 string?
I just need a validation like this in annotation form:
try {
Base64.getDecoder().decode(someString);
return true;
} catch(IllegalArgumentException iae) {
return false;
}
thnx in advance
Yes, you could write your own annotations and validators for them.
Your annotation would look like this:
#Documented
#Constraint(validatedBy = Base64Validator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface IsBase64 {
String message() default "The string is not base64 string";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint validator javax.validation implementation (I'm using here your code for the actual validation):
public class Base64Validator implements ConstraintValidator<IsBase64, String> {
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
try {
Base64.getDecoder().decode(value);
return true;
} catch(IllegalArgumentException iae) {
return false;
}
}
}
Example data class with the annotated field:
#Data
public class MyPayload {
#IsBase64
private String value;
}
And controller method example with #Valid annotation which is required:
#PostMapping
public String test(#Valid #RequestBody MyPayload myPayload) {
return "ok";
}

How to validate enum in DTO?

In the domain model object I have the following field:
private TermStatus termStatus;
TermStatus is an enum:
public enum TermStatus {
NONE,
SUCCESS,
FAIL
}
In the DTO, I have the same field as in the domain object. The question is, how can I validate the passed value? If the API client now passes an incorrect string with the enum value as a parameter (for example, nOnE), it will not receive any information about the error, only the status 400 Bad Request. Is it possible to validate it like this, for example, in the case of javax.validation annotations like #NotBlank, #Size, where in case of an error it will at least be clear what it is. There was an idea to make a separate mapping for this, for example "items/1/complete-term" instead of direct enum transmission, so that in this case the server itself would set the SUCCESS value to the termStatus field. But as far as I know, these things don't look very good in REST API, so I need your ideas
Instead of validating enum directly, you could check whether String is valid for specific enum. To achieve such an effect you could create your own enum validation annotation.
#Documented
#Constraint(validatedBy = EnumValidatorConstraint.class)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#NotNull
public #interface EnumValidator {
Class<? extends Enum<?>> enum();
String message() default "must be any of enum {enum}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then you need to implement validator to check whether String exist as a part of this enum.
public class EnumValidatorConstraint implements ConstraintValidator<EnumValidator, String> {
Set<String> values;
#Override
public void initialize(EnumValidator constraintAnnotation) {
values = Stream.of(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toSet());
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return values.contains(value);
}
}
Lastly, you need to annotate your field with #EnumValidator.
#EnumValidator(enum = TermStatus.class)
private String termStatus;
In case of not matching String a MethodArgumentNotValidException will be thrown. Same as for #NotNull or other constraint validation.
Sounds like you need to implement your own response after validation and tell the API client that the data in your received DTO is invalid and return message with the actual received value (nOnE in your case) and maybe the list of your valid values (if that's not gonna be a security concern). Also, I think the ideal http status for your response would be 422 instead of a generic 400 Bad Request.
For your actual validation implementation, I think you can just directly compare the converted value from DTO to ENUM of the data you received from the API client against your ENUM values in the back-end. If equals to any of the ENUM values, then it's a valid request (200) else, 422.
Hope this helps!
You can make a utility method inside your enum like below
private String text;
TermStatus(String text) {
this.text = text;
}
public static TermStatus fromText(String text) {
return Arrays.stream(values())
.filter(bl -> bl.text.equalsIgnoreCase(text))
.findFirst()
.orElse(null);
}
And set value in dto like below
dto.setTermStatus(TermStatus.fromText(passedValue))
if(dto.getTermStatus()== null)
throw new Exception("Your message");
Hope this helps!
You should use String data type for termStatus. Because of client sends String value for this. Then you have to create Custom validation constraints to fix this as below.
ValueOfEnum
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = ValueOfEnumValidator.class)
public #interface ValueOfEnum
{
Class<? extends Enum<?>> enumClass();
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ValueOfEnumValidator
public class ValueOfEnumValidator implements ConstraintValidator<ValueOfEnum, CharSequence>
{
private List<String> acceptedValues;
#Override
public void initialize(ValueOfEnum annotation)
{
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
#Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context)
{
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}
Now you can #ValueOfEnum annotation for your domain model. Then add #Validated annotation in front of your controller class domain object.
#ValueOfEnum(enumClass = TermStatus.class, message = "Invalid Term Status")
private String termStatus;

How to get values from custom sub-annotation?

I have a question about getting custom annotation value which is value of another custom annotation. For example I have a #SqlInfo annotation interface which have two values which is also annotation interfaces.
SqlInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface SqlInfo {
CodificationInfo codificationInfo();
DocumentInfo documentInfo();
}
#CodificationInfo and #DocumentInfo is also annotation interfaces. Each of it has his own different values.
CodificationInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface CodificationInfo {
enum KEYS {
DOMAIN,
FILE,
TABLE,
CLASS
}
String domain() default "";
String fileName() default "";
String table() default "";
Class codificationClass();
}
While I am using only #CodificationInfo annotation for the class. I am getting values from it by using this method:
Annotation values getter method
public Object getClassAnnotationValue(Class c, String key) {
Annotation annotation = c.getAnnotation(CodificationInfo.class);
return getObjectByKey(annotation, key);
}
private Object getObjectByKey(Annotation annotation, String key) {
if (annotation instanceof CodificationInfo) {
if (key.equalsIgnoreCase(CodificationInfo.KEYS.TABLE.toString())) {
return ((CodificationInfo) annotation).table();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.CLASS.toString())) {
return ((CodificationInfo) annotation).codificationClass();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.DOMAIN.toString())) {
return ((CodificationInfo) annotation).domain();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.FILE.toString())) {
return ((CodificationInfo) annotation).fileName();
}
}
return null;
}
I want to know how to get #CodificationInfo values while I am using #SqlInfo annotation for the class? It means - how to get values from sub-annotation?
P.S.: I know that I can use both annotations separately for the class. But I want to know the any way how to get values from sub-annotation. For example hibernate use it for #AuditOverrides annotation.
If you have a type declared like:
#SqlInfo(codificationInfo = #CodificationInfo(codificationClass = AClass.class)
public class MyType { }
you can reflectively get the inner annotation values with:
final SqlInfo sqlInfoAnnotation = (SqlInfo) c.getAnnotation(SqlInfo.class);
if (sqlInfoAnnotation == null) return;
final CodificationInfo codInfoAnnotation = sqlInfoAnnotation.codificationInfo();
final Class<?> codClass = codInfoAnnotation.codificationClass();
Note: you can avoid having to cast the annotation by not using raw types (prefer Class<?> over Class).

Spring validation annotation - How can I verify that a 2 character entry into a string is an actual US state?

I am trying to use spring to check user online input to ensure that the two characters they enter is an actual US state, is there any way of doing this, hopefully using a preset pattern? like, #State or something (if that was a legit annotation). Also, is there a good annotation commonly used for a String street, and String city field? That is other than #NotNull and #NotEmpty
Any help would be greatly appreciated!!
Unfortunately there is no out of the box however you can create your own #State annotation , all you need is to define your annotation and class implementing ConstraintValidator(which handles the validation logic) E.g.
#Constraint(validatedBy = StateConstraintValidator.class)
#Target( { ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface State {
String message() default "{State}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class StateConstraintValidator implements ConstraintValidator<String, String> {
private static final Set<String> CODE_MAP = new HashSet<>(){
{add("AR");}
{add("AK");} //add more codes ...
};
#Override
public void initialize(String state) { }
#Override
public boolean isValid(String value, ConstraintValidatorContext cxt) {
if(value == null) {
return false;
}
return CODE_MAP.contains(value);
}
}
In the similar manner you can create other annotations.

Cross field validation (JSR 303) problem

I have a simple bean, i.e.:
public class MyBean {
private boolean selected;
private String someString;
...
}
So if selected is true, I want someString to be #NotNull etc. .
Any hints, links how to achieve this behaviour?
Thanks
Jonny
If you’re using Spring Framework then you can use Spring Expression Language (SpEL) for that. I’ve wrote small library that provides JSR-303 validator based on SpEL that makes cross-field validations very easy. Take a look at https://github.com/jirutka/validator-spring.
And there’s example for your case:
#SpELAssert(value = "someString != null", applyIf = "selected",
message = "{validator.missing_some_string}")
public class MyBean {
private boolean selected;
private String someString;
...
}
Actually this was too easy. Try something more interesting, maybe an equality of password fields when one of them is not null.
#SpELAssert(value = "password.equals(passwordVerify)",
applyIf = "password || passwordVerify",
message = "{validator.passwords_not_same}")
public class User {
private String password;
private String passwordVerify;
}
And you can even use your own “helper methods” in these expressions!
Compared to Hibernate Validator’s #ScriptAssert annotation, this is pure Java solution, it’s not using any JSR-223 compliant scripting language which may be a little problematic. On the other side, this solution is interesting only for Spring-based applications.
You could do this by annotating MyBean with a custom validator, for example:
#ValidMyBean
public class MyBean {
private boolean selected;
private String someString;
...
}
ValidMyBean:
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyBeanValidator.class)
public #interface ValidMyBean {
boolean allViolationMessages() default true;
Class<?>[] constraints() default {};
Class<?>[] groups() default {};
String message() default "{ValidMyBean.message}";
Class<? extends Payload>[] payload() default {};
}
MyBeanValidator:
public final class MyBeanValidator implements
ConstraintValidator<ValidMyBean, MyBean> {
#Override
public void initialize(
#SuppressWarnings("unused") final ValidMyBean constraintAnnotation) {
}
#Override
public boolean isValid(final MyBean value,
final ConstraintValidatorContext context) {
boolean isValid = true;
//your validation here
return isValid;
}
}

Categories

Resources