I have the following annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = IdValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
}
that I'm using on my class
public class Person {
#Id
String firstName;
#Id
String lastName;
}
When I call
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<FakePerson>> violations = validator.validate(person);
I want a ConstrainstViolation to have a message that says "firstName cannot be null". How can I do that?
Is there a way to create the messages associate with the path inside the ConstraintValidator?
public class IdValidator implements ConstraintValidator<Id, Object> {
#Override
public boolean isValid(Object value, ConstraintValidatorContext context)
{
//Ideally I can build the messages associate with the property here with ConstraintValidatorContext
}
}
There are a couple of options.
The first would be to add a new attribute to your annotation, shown below is label.
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = PKFieldValidator.class)
public #interface Id {
String message() default "{Id field cannot be null}";
String label();
}
Then in the class, you'd use
#Id(label="firstName")
private String firstName;
#Id(label="lastName")
private String lastName;
Then simply make your message be something like
Id.message={label} is required.
But the problem with that approach is that you're dropping in bean field names into translations that could be in other languages. How much sense does Se requiere el firstName make in Spanish?
My preferred suggestion is to actually keep bean validation messages generic. Things such as Field is required or Must be greater than 0 seem very reasonable to me and allows for reuse.
For situations where you need to associate these violation messages with the properties, simply iterate the violations, get the propertyPath and print the path plus the message to your logs or whatnot.
From web frameworks, I prefer to do precisely that, iterate the constraint violations, adding them to a Map<String,String> where the key is the propertyPath and the value is the violation message. Then in the UI, I associate the message next to each of the field's input controls, keeping things clean and concise all the while easy for the user to understand and read.
Related
I'm working on a java spring mvc application and have an important question about mapping view model objects to database model objects. Our application uses dozer mapper for that purpose.
Suppose I have a Person model and BaseInformation model. The BaseInformation model is for general data that can be used in all other models, for example genders, colors, units, ....
BaseInformation:
class BaseInformation{
private Long id;
private String category;
private String title;
}
This can has a database table like this:
Id | Category | Title
-------------------------
1 | "gender" | "male"
2 | "gender" | "female"
3 | "color" | "red"
4 | "color" | "green"
...
This is part of my Person Model:
public class Person{
...
private BaseInformation gender;
...
}
And this is part of my RegisterPersonViewModel
public class RegisterPersonViewModel{
...
private Integer gender_id;
...
}
In the register person view, I have a <select> that be filled from BaseInfromation with gender category. When a user submit that form, an ajax request sends to a methods of controller like this:
#RequestMapping("/person/save", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody Person create(#Valid #RequestBody RegisterPersonViewModel viewModel) throws Exception {
//Mapping viewModel to Model via dozer mapper
//and passing generated model to service layer
}
Now, here is my question:
A user can change value of gender combobox in the view manually(for example set a value of color instead of gender) and send invalid related data to controller's method. Dozer mapper map viewModel to model and this invalid data go through data access layer and persist in database. In the other words, An invalid data can save into database without any control. I want to know the best way for controlling relational data with minimum code.
The BaseInformation class is way too generic: gender has nothing to do with color. You need to break it up. It's a case of "One True Lookup Table" and even mentioned on Wikipedia:
In the database world, developers are sometimes tempted to bypass the RDBMS, for example by storing everything in one big table with three columns labelled entity ID, key, and value.
... which corresponds to your id, category and title.
While this entity-attribute-value model allows the developer to break out from the structure imposed by an SQL database, it loses out on all the benefits, [1] since all of the work that could be done efficiently by the RDBMS is forced onto the application instead. Queries become much more convoluted, [2] the indexes and query optimizer can no longer work effectively, and data validity constraints are not enforced.
The part in bold describes the issue you're having pretty well.
You should move the different categories into their own classes and tables. For gender an enum is good enough:
public enum Gender {
Female, Male, Unknown, Unspecified
}
And use it in the Person class like this:
public class Person {
...
private Gender gender;
...
}
If you're using Spring data binding to convert the input data to Java objects only the values specified in the Gender enum may be used and no further checks are necessary.
For color you could similarly use an enum if the colors don't need to be changed at runtime or a class otherwise.
You have to validate the view model and before persisting you can also validate the entity.
It seems you are using Bean Validation because you are using #Validannotation in your controller method. So just add the validation constraints for the model properties. For example:
public class RegisterPersonViewModel {
...
#Min(0) #Max(1)
private Integer gender_id;
...
}
But of cause if someone sends 1 and means color green and not female you are lost. Therefore it would be much better to use a enum for the gender and the other properties if possible.
Bean Validation can also be used for your database objects (entities).
You can use Hibernate Validator 5.x and Validation API 1.1 in Spring validation mechanism.
The new version of Hibernate Validator can validate your persistent bean as a method argument and throw javax.validation.ConstraintViolationException on your controller.
#Inject MyPersonService myPersonService;
#RequestMapping("/person/save", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody Person create( #RequestBody RegisterPersonViewModel viewModel) throws Exception {
Person person = ...; // map
try{
myPersonService.persist(person);
}catch (ConstraintViolationException cvex) {
for (ConstraintViolation cv : cvex.getConstraintViolations()) {
String errorMessage = cv.getMessage();
}
}
}
service:
#Service
public class MyPsersonService{
public void persist(#Valid Person person){
// do persist here without checking related data
}
}
pserson:
public class Person{
...
#ValidCategory("gender")
private BaseInformation gender;
...
}
ValidCategory:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Constraint(validatedBy = CategoryValidator.class)
public #interface ValidCategory {
String message() default "{info.invalid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String value(); // the category name goes here
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
#interface List {
ValidCategory[] value();
}
}
CategoryValidator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CategoryValidator implements ConstraintValidator<ValidCategory, BaseInformation> {
private String category;
#Override
public void initialize(ValidCategory validCat) {
this.category = validCat.value();
}
#Override
public boolean isValid(BaseInformation baseInfo, ConstraintValidatorContext cvc) {
if(!this.category.equals(baseInfo.getCategory()){
addError(cvc,"you've entered invalid category!");
return false;
}else{
return true;
}
}
private void addError(ConstraintValidatorContext cvc, String m){
cvc.buildConstraintViolationWithTemplate(m).addConstraintViolation();
}
}
define two beans in applicationContext. Spring will automatically detect them ( another question).
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
At the first glance it doesn't sound minimal code, but it is so neat and cleans the domain. It is the best solution.
I have a problem to combine the javax annotation with custom ConstraintValidators.
I have an example class Person, where the name and the age are required.
#PersonConstraint
public class Person {
#NotNull
private String name;
#NotNull
private Integer age;
...
}
And I have an additional constraint:
#Constraint(validatedBy = PersonValidator.class)
#Target(TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface PersonConstraint{
String message() default "...";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
For the custom Validator:
public class PersonValidator implements ConstraintValidator<PersonConstraint, Person> {
...
#Override
public boolean isValid(Person value, ConstraintValidatorContext context) {
if(value.getAge() < 18)
return false;
...
}
}
When a Person object is validated now and the age is null, I got a Nullpointer Exception in the PersonValidator. I thoguht, this is something, which is checked by the javax Annoation #NotNull.
Is there a solution to combine the annotations with the custom validators or do I have to check the null values in the validor by myself?
(The code is just an example - but this is a general question)
Unless you are working with the group and group sequences, there is no guaranteed order in which constraints are going to be evaluated. You cannot rely in your custom constraint that the #NotNull already has occured. And even it it had you would still get a NullPointerException. Bean Validation will per default not stop after the first constraint violation, but collect all violations of a required validation (hence a set of ConstraintViolations are returned. So there might actually be already a constraint violation for age, but in your custom ConstraintValidator you would still be accessing a null value. You cannot get around doing a null check at this stage.
I am writing code that has explicit call to Bean Validation (JSR-303) something like this:
public class Example {
#DecimalMin(value = "0")
private static final String ANNOTATED = "";
public void isPossitiveNumber(String str){
ValidatorFactory factory =
Validation.buildDefaultValidatorFactory();
ConstraintValidator<DecimalMin, String>
validator =
factory.getConstraintValidatorFactory().getInstance(
DecimalMinValidatorForString.class);
validator.initialize(
ReflectionUtils.findField(getClass(), "ANNOTATED")
.getAnnotation(
DecimalMin.class));
boolean isValid = validator.isValid(str, null);
return isValid;
}
}
Note the line boolean isValid = validator.isValid(str, null);
I transfer null for ConstraintValidatorContext because I found no way to obtain/construct it. In this particular case, this if fine, because there is no use of the ConstraintValidatorContext internally, but it is obvious a hack. How should I get ConstraintValidatorContext?
ADDED
I was asked to provide use-cases. So, for example, I am writting custom validator and I want to reuse exisiting validations. Or I am writting plane Java code as desribed above and I want to reuse exisiting validation.
I recently had exactly the same issue as the OP. However contrary to the accepted answer it is possible to write Unit tests that include the ConstraintValidationContext. This excellent link explains how to do it, http://farenda.com/java/bean-validation-unit-testing/
Basically you need to use the ValidatorFactory to obtain a Validator interface, then call validate(c) on that interface, where the parameter c is an instance of the class containing the bean validation annotations. A code example is clearer, code sample taken from the above link.
public class Player {
// name have to be 3 chars:
#Size(min = 3, max = 3)
private String name;
// possible score in game:
#Min(0) #Max(100)
private int score;
public Player(String name, int score) {
this.name = name;
this.score = score;
}
// just for logs
#Override
public String toString() {
return "Player{name='" + name + '\'' + ", score=" + score + '}';
}
}
public class PlayerValidationTest {
private static ValidatorFactory validatorFactory;
private static Validator validator;
#BeforeClass
public static void createValidator() {
validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
#AfterClass
public static void close() {
validatorFactory.close();
}
#Test
public void shouldDetectInvalidName() {
//given too short name:
Player player = new Player("a", 44);
//when:
Set<ConstraintViolation<Player>> violations
= validator.validate(player);
//then:
assertEquals(violations.size(), 1);
}
}
The simple answer is you cannot. ConstraintValidatorContext is an interface and there is no Bean Validation API to get an instance like this. You could write your own implementation, but to implement it properly you would have to re-implement a lot of functionality of a Bean Validation provider. Look for example at the Hibernate Validator specific implementation - https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/constraintvalidation/ConstraintValidatorContextImpl.java
That said, I believe your attempt of reuse is misguided. This is not in the indent of Bean Validation and you are ending up with non portable and hard to maintain code. If you want to reuse existing constraints have a look at constraint composition, for example #NotEmpty reusing #NotNull and #Size
#Documented
#Constraint(validatedBy = { })
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#ReportAsSingleViolation
#NotNull
#Size(min = 1)
public #interface NotEmpty {
String message() default "{org.hibernate.validator.constraints.NotEmpty.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {#code #NotEmpty} annotations on the same element.
*/
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
#Documented
public #interface List {
NotEmpty[] value();
}
}
You should declare a group for the constraints you validate in that case. Then you can call the normal validation for that group. See sections 2.1.1.2 and section 3.4 of the spec for group definitions and their semantics. For validating the group, you then just need to call Validator.validate(T Object, Class<?>... groups). There is no need to mess around with the ConstraintValidatorContext in this case.
I need to create a custom constraint annotation which can access the value of another field of my bean. I'll use this annotation to validate the field because it depends on the value of the other but the way I define it the compiler says "The value for annotation attribute" of my field "must be a constant expression".
I've defined it in this way:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy=EqualsFieldValidator.class)
#Documented
public #interface EqualsField {
public String field();
String message() default "{com.myCom.annotations.EqualsField.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EqualsFieldValidator implements ConstraintValidator<EqualsField, String>{
private EqualsField equalsField;
#Override
public void initialize(EqualsField equalsField) {
this.equalsField = equalsField;
}
#Override
public boolean isValid(String thisField, ConstraintValidatorContext arg1) {
//my validation
}
}
and in my bean I want something like this:
public class MyBean{
private String field1;
#EqualsField(field=field1)
private String field2;
}
Is there any way to define the annotation so the field value can be a variable?
Thanks
The easiest thing to do is take one step back: the constraint/validator you have written works on a field-level, but what you want to enforce is a cross-field dependency i.e. a class-level constraint.
Rewrite your constraint and validator to work at the class level (i.e. the annotation will go on the class, not on the field). That way you'll get access to the entire class. In your isValid(..) method, simply do a get on both the fields, compare, and return appropriately.
As the compiler said annotations must be constant (i.e. you can determine the value at compile time.) Now If I'm guessing correctly it looks like you are using this annotation to denote that the values of those fields should be equal when run through the equals field validator. One approach you could take is using reflection. Instead of trying to annotate with the value, annotate with the field name instead
public class MyBean{
private String field1;
#EqualsField("field1")
private String field2;
}
Then in your validator you can read the name of the field and use reflection to access it
Object o = object.getClass().getDeclaredField(annotationValue).get(object);
o == object.(field with annotation) OR
o.equals(object.(field with annotation));
Depending on what you are trying to do you may need to add in logic based on the field type, but still the same general principle.
Check out this previous question, has multiple solutions for cross-field validation: Cross field validation with Hibernate Validator (JSR 303)
I am looking at using Hibernate Validator for a requirement of mine. I want to validate a JavaBean where properties may have multiple validation checks. For example:
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
private String myProperty;
}
But if this property fails validation I want a specific error code to be associated with the ConstraintViolation, regardless of whether it failed because of #Required or #Length, although I would like to preserve the error message.
class MyValidationBean
{
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode( "1234" )
private String myProperty;
}
Something like the above would be good but it doesn't have to be structured exactly like that. I can't see a way to do this with Hibernate Validator. Is it possible?
You could create a custom annotation to get the behaviour you are looking for and then on validating and using refelection you could extract the value of the annotation. Something like the following:
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ErrorCode {
String value();
}
In your bean:
#NotNull
#Length( min = 5, max = 10 )
#ErrorCode("1234")
public String myProperty;
On validating your bean:
Set<ConstraintViolation<MyValidationBean>> constraintViolations = validator.validate(myValidationBean);
for (ConstraintViolation<MyValidationBean>cv: constraintViolations) {
ErrorCode errorCode = cv.getRootBeanClass().getField(cv.getPropertyPath().toString()).getAnnotation(ErrorCode.class);
System.out.println("ErrorCode:" + errorCode.value());
}
Having said that I probably would question the requirements for wanting error codes for these types of messages.
From the section 4.2. ConstraintViolation of the specification:
The getMessageTemplate method returns the non-interpolated error message (usually the message attribute on the constraint declaration). Frameworks can use this as an error code key.
I think this is your best option.
What I would try to do is isolate this behavior on the DAO Layer of the application.
Using your example we would have:
public class MyValidationBeanDAO {
public void persist(MyValidationBean element) throws DAOException{
Set<ConstraintViolation> constraintViolations = validator.validate(element);
if(!constraintViolations.isEmpty()){
throw new DAOException("1234", contraintViolations);
}
// it's ok, just persist it
session.saveOrUpdate(element);
}
}
And the following exception class:
public class DAOException extends Exception {
private final String errorCode;
private final Set<ConstraintViolation> constraintViolations;
public DAOException(String errorCode, Set<ConstraintViolation> constraintViolations){
super(String.format("Errorcode %s", errorCode));
this.errorCode = errorCode;
this.constraintViolations = constraintViolations;
}
// getters for properties here
}
You could add some annotation information based on what property has not validated from here, but always doing this on the DAO method.
I hope this helped.