I am confused about the case that have multiple constraint annotations on a field, below:
public class Student
{
#NotNull
#Size(min = 2, max = 14, message = "The name '${validatedValue}' must be between {min} and {max} characters long")
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
Test case:
public class StudentTest
{
private static Validator validator;
#BeforeClass
public static void setUp()
{
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
System.out.println(Locale.getDefault());
}
#Test
public void nameTest()
{
Student student = new Student();
student.setName(null);
Set<ConstraintViolation<Student>> constraintViolations = validator.validateProperty(student, "name");
System.out.println(constraintViolations.size());
System.out.println(constraintViolations.iterator().next().getMessage());
}
}
The result is:
1
Can't be null
That is, when the #NotNull constraint is violated, it will not continue. Yes, this is the right situation. When one check is failed, we don't want it check the next constraint. But the situation is different when I used custom constraint.
I defined two custom constraints ACheck and BCheck.
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { ACheckValidator.class })
public #interface ACheck
{
String message() default "A check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { BCheckValidator.class })
public #interface BCheck
{
String message() default "B check error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
public class ACheckValidator implements ConstraintValidator<ACheck, String>
{
public void initialize(ACheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
public class BCheckValidator implements ConstraintValidator<BCheck, String>
{
public void initialize(BCheck constraintAnnotation)
{
}
public boolean isValid(String value, ConstraintValidatorContext context)
{
return false;
}
}
There is not specific info about custom constraint, and I change the Student.java and use custom constraint like that:
#ACheck
#BCheck
private String name;
Test again, and the result is:
2
B check error
That is, when the #ACheck constraint is violatedm, it also wil check #BCheck, Why this happens, anything else I had ignored?
when the #NotNull constraint is violated, it will not continue
That is incorrect. It will continue checking all the other constraints. It's just that the Size validator considers a null value as an acceptable value. The reason is that typically, you want
a non-null, minimum size value: then you apply both constraints
or a nullable value, which must have a minimum size if the value is present: then you only apply Size.
You're misunderstanding those validators - they have no guarantee of order in which they are evaluated.
By default, constraints are evaluated in no particular order, regardless of which groups they belong to.
So that means that either your ACheck or your BCheck could have failed, or both; it's not determined which failure will occur first.
If you want to be able to define an ordering with two distinct annotations, then you would have to use a #GroupSequence to specify that.
Alternatively, if you want to fail fast, then configure the validator to do so.
Validator validator = Validation.byProvider( HibernateValidator.class )
.configure()
.failFast( true )
.buildValidatorFactory()
.getValidator();
I would personally discourage that approach as it implies that a user that fails validations must make repeated requests to the resource every time one thing is wrong, as opposed to getting everything that is wrong up front.
Related
I have the following controller:
public interface SaveController {
#PostMapping(value = "/save")
#ResponseStatus(code = HttpStatus.CREATED)
void save(#RequestBody #Valid SaveRequest saveRequest);
}
SaveRequest corresponds to:
public class SaveRequest {
#NotNull
private SaveType type;
private String name;
}
and SaveType:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
}
The controller does not receive the enum itself, but a camelCase String. I need to convert that String into the corresponding enum. For instance:
daily should become DAILY.
weekly should become WEEKLY.
monthly should become MONTHLY.
Any other String should become null.
I've tried using the Spring Converter class, which does not work when the enum is inside an object (at least I don't know how to make it work in such times).
I honestly don't know what else to try
https://www.baeldung.com/jackson-serialize-enums
This site should probably give you plenty of options.
Best is probably something like this:
public enum SaveType {
DAILY, WEEKLY, MONTHLY;
#JsonCreator
public static SaveType saveTypeforValue(String value) {
return SaveType.valueOf(value.toUpperCase());
}
}
What you require is to have custom annotation with a custom validation class for Enum.
javax.validation library doesn't have inbuilt support for enums.
Validation class
public class SaveTypeSubSetValidator implements ConstraintValidator<SaveTypeSubset, SaveType> {
private SaveType[] subset;
#Override
public void initialize(SaveTypeSubset constraint) {
this.subset = constraint.anyOf();
}
#Override
public boolean isValid(SaveType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset).contains(value);
}
}
interface for validation annotation with validation message
#Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = SaveTypeSubSetValidator.class)
public #interface SaveTypeSubset {
SaveType[] anyOf();
String message() default "must be any of {anyOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Usage
#SaveTypeSubset(anyOf = {SaveType.NEW, SaveType.OLD})
private SaveType SaveType;
This is one way. More ways are mentioned in this article.
I have an entity class with the following unique constraint setup:
#Table(name = "foo", uniqueConstraints = {
#UniqueConstraint(columnNames = {"service", "system", "priority", "is_default"})})
Where service and system are foreign keys for other entity classes, priority is an integer holding the priority of the entry among entries with the same service and system objects and is_default is a boolean indicating a default configuration entry.
This unique constraint almost does what I want to do, but what I would need is a setup where, if is_default is FALSE then there can be multiple entries with the same service and system keys just with different integer priority, while if is_default is TRUE then there can be only 1 entry with the given service and system keys, meaning there can be only 1 default entry for a given service and system. How could I achive such a constraint?
The key is to create your own validation annotation able to validate whether the row data is unique based on a particular criteria.
Let the service iterface extend UniqueValidated and implement the method that performs the validation
public interface UniqueValidated {
boolean isUnique(Object value, String fieldName) throws UnsupportedOperationException;
}
public interface FooService extends UniqueValidated {
// add, delete...
}
public class FooServiceImpl implements FooService {
// add, delete...
#Override
public boolean isUnique(Object value, String fieldName)
throws UnsupportedOperationException {
// the logic of validation itself, feel free to use DAO implementations
}
}
Create the annotation that you put over the mapped attribute.
#Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE
})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueValidator.class)
#Documented
public #interface Unique {
String message() default "{validation.unique}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends UniqueValidated> service();
String serviceQualifier() default "";
String fieldName();
}
Finally create the class handling with the annotation.
public class UniqueValidator implements ConstraintValidator<Unique, Object> {
#Autowired
private ApplicationContext applicationContext;
private UniqueValidated service;
private String fieldName;
#Override
public void initialize(Unique unique) {
Class<? extends UniqueValidated> clazz = unique.service();
this.fieldName = unique.fieldName();
this.service = this.applicationContext.getBean(clazz);
}
#Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if (this.service == null || this.service.isUnique(o, this.fieldName)) {
constraintValidatorContext.disableDefaultConstraintViolation();
return true;
}
return false;
}
}
I have got inspired with the tutorial available online on the JBoss Docs. This is quite complex structure, however it leads the result well. The biggest advantage is undoubtly you can make the custom unique validation over any service implementing the UniqueValidated. Anyway, you need to these snippets above customize for your project.
Mapping is simple:
#Unique(service = FooService.class, fieldName = "theNameOfThisField"
#Column(name = "...")
private String theNameOfThisField;
I'm trying to apply multiple common annotations at once with a custom validation annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Length(max=25, min=1, message="invalid length")
#NotNull
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
public #interface MyAnnotation {
}
And using it in my model classes like this:
#MyAnnotation
public String firstName;
None of these validation are working, but they work as expected when used in the model class itself. I also tried registering MyAnnotation in the applications run method, and that didn't work either.
environment.jersey().register(MyAnnotation.class);
What else do I need to do in order to use custom validations?
Either annotate the String directly:
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
#NotNull
#Length(max=25, min=1, message="invalid length")
public String firstName;
Or create a validator, something like:
class MyAnnotatationValidator implements ConstraintValidator<MyAnnotation, String>{
#Override
public void initialize(MyAnnotation a){}
#Override
public boolean isValid(String s, ConstraintValidationContext c) {
return s != null && (s.length() > 0 && s.length() < 26) && s.matches("[a-zA-Z0-9]{1, 25})";
}
}
And have
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy=MyAnnotatationValidator.class)
public #interface MyAnnotation {
String message() default = "{MyAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload> payload() deafult {};
}
According to the JSR-303
A constraint definition may have attributes that are specified at the time the constraint is applied to a JavaBean. The properties are mapped as annotation elements. The annotation element names message, groups and payload are considered reserved names; annotation elements starting with valid are not allowed; a constraint may use any other element name for its attributes.
So you must add message, groups and payload attributes to your #MyAnnotation.
A composition is done by annotating the composed constraint, example:
#Length(max=25, min=1, message="invalid length")
#NotNull
#Pattern(regexp = "[a-zA-Z0-9]{1, 25})")
#Documented
#Target({ANNOTATION_TYPE, METHOD, FIELD, CONSTRUCTOR, PARAMETER})
#Retention(RUNTIME)
public #interface MyAnnotation {
String message() default "My annotation message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Is there are any java annotation(s) that can validate like the example below?
String test;
test = null; //valid
test = ""; //invalid
test = " "; //invalid
test = "Some values"; //valid
You need to create a custom annotation: #NullOrNotBlank
First create the custom annotation: NullOrNotBlank.java
#Target( {ElementType.FIELD})
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NullOrNotBlankValidator.class)
public #interface NullOrNotBlank {
String message() default "{javax.validation.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
Then the actual validator: NullOrNotBlankValidator.java
public class NullOrNotBlankValidator implements ConstraintValidator<NullOrNotBlank, String> {
public void initialize(NullOrNotBlank parameters) {
// Nothing to do here
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
return value == null || value.trim().length() > 0;
}
}
There isn't such an annotation in either javax.validation or Hibernate Validator. There was a request to add one to Hibernate Validator but it was closed as "won't fix" due to the possibility of writing your own relatively easily. The suggest solution was to either use your own annotation type defined like this:
#ConstraintComposition(OR)
#Null
#NotBlank
#ReportAsSingleViolation
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = { })
public #interface NullOrNotBlank {
String message() default "{org.hibernate.validator.constraints.NullOrNotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
or to use the #Pattern annotation with a regular expression that requires a non-whitespace character to be present (as the Pattern annotation accepts nulls and does not match them against the pattern).
Where is a nice javax.validation.constraints.Pattern annotation.
You can annotate the field with:
#Pattern(regexp = "^(?!\\s*$).+", message = "must not be blank")
This checks if field matches regex. The regex itself is something but not blank (see details here). It uses negative lookahead.
This is possible without creating a custom annotation, by using javax.validation.constraints.Size
// Null values are considered valid
#Size(min=1) String test;
The best way is to create your own constraint validator,
//custom annotation
#Documented
#Constraint(validatedBy = CustomCheck.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomConstarint {
String message() default "Invalid data";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
//validation logic goes here
public class CustomCheck implements
ConstraintValidator<CustomConstarint, String> {
#Override
public void initialize(CustomConstarint customConstarint) {
}
#Override
public boolean isValid(String field,
ConstraintValidatorContext cxt) {
//write your logic to validate the field
}
}
Did you try Hibernate-Validator? I think that's what you are looking for.
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;
public class MyModel {
#NotNull
private String str1;
#NotEmpty
private String str2;
#NotBlank
private String str3;
}
#Pattern(regexp = "^[M|F]{1}$", message ="Must be M or F")
private Character gender;
Result:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for type: java.lang.Character.
How can achieve the following:
apply hibernate validation to the character using a regex pattern
restricting the return type to be a single letter (that's why I chose char)
to everything of this in a single method?
I had similiar problem and i didnt find any default hibernate validator annotation for that. But there is easy way to create custom annotation. (look here https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/validator-customconstraints.html) Below example with sex:
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = SexValidator.class)
#Documented
public #interface Sex
{
String message() default "{customValidator.sex";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ FIELD, METHOD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
#interface List
{
Sex[] value();
}
}
public class SexValidator implements ConstraintValidator<Sex, Character> {
public void initialize(Sex sex)
{
// used only if your annotation has attributes
}
public boolean isValid(Character sex, ConstraintValidatorContext constraintContext)
{
// Bean Validation specification recommends to consider null values as
// being valid. If null is not a valid value for an element, it should
// be annotated with #NotNull explicitly.
if (sex == null)
{
return true;
}
if (sex.equals('F') || sex.equals('M'))
return true;
else
{
return false;
}
}
}
#Column(name = "sex", columnDefinition = "char(1)")
#NotNull
#Sex
private Character sex;