Is there a way to define a Hibernate validation rule using annotations as defined here, stating that at least one field shall be not null?
This would be a hypothetical example (#OneFieldMustBeNotNullConstraint does not really exist):
#Entity
#OneFieldMustBeNotNullConstraint(list={fieldA,fieldB})
public class Card {
#Id
#GeneratedValue
private Integer card_id;
#Column(nullable = true)
private Long fieldA;
#Column(nullable = true)
private Long fieldB;
}
In the illustrated case, fieldA can be null or fieldB can be null, but not both.
One way would be to create my own validator, but I'd like to avoid if it already exists. Please share one validator if you have one already made... thanks!
I finally wrote the whole validator:
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import org.apache.commons.beanutils.PropertyUtils;
#Target( { TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckAtLeastOneNotNull.CheckAtLeastOneNotNullValidator.class)
#Documented
public #interface CheckAtLeastOneNotNull {
String message() default "{com.xxx.constraints.checkatleastnotnull}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fieldNames();
public static class CheckAtLeastOneNotNullValidator implements ConstraintValidator<CheckAtLeastOneNotNull, Object> {
private String[] fieldNames;
public void initialize(CheckAtLeastOneNotNull constraintAnnotation) {
this.fieldNames = constraintAnnotation.fieldNames();
}
public boolean isValid(Object object, ConstraintValidatorContext constraintContext) {
if (object == null) {
return true;
}
try {
for (String fieldName:fieldNames){
Object property = PropertyUtils.getProperty(object, fieldName);
if (property != null) return true;
}
return false;
} catch (Exception e) {
return false;
}
}
}
}
Example of usage:
#Entity
#CheckAtLeastOneNotNull(fieldNames={"fieldA","fieldB"})
public class Reward {
#Id
#GeneratedValue
private Integer id;
private Integer fieldA;
private Integer fieldB;
[...] // accessors, other fields, etc.
}
Just write your own validator. Is't should be pretty simple: iterate over field names and get field values by using reflection.
Concept:
Collection<String> values = Arrays.asList(
BeanUtils.getProperty(obj, fieldA),
BeanUtils.getProperty(obj, fieldB),
);
return CollectionUtils.exists(values, PredicateUtils.notNullPredicate());
There I used methods from commons-beanutils and commons-collections.
This is a bit like Resh32's answer but this will also bind the validation message with a specific field of the validated object.
The validation annotation class will be like the following one.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* #author rumman
* #since 9/23/19
*/
#Target(TYPE)
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = NotNullAnyValidator.class)
public #interface NotNullAny {
String[] fieldNames();
String errorOnProperty();
String messageKey() default "{error.required}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator class will be like the following.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Objects;
import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
/**
* #author rumman
* #since 9/23/19
*/
public class NotNullAnyValidator implements ConstraintValidator<NotNullAny, Object> {
private String[] fieldNames;
private String errorOnProperty;
private String messageKey;
#Override
public void initialize(NotNullAny validateDateRange) {
fieldNames = validateDateRange.fieldNames();
errorOnProperty = validateDateRange.errorOnProperty();
messageKey = validateDateRange.messageKey();
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext validatorContext) {
Object[] fieldValues = new Object[fieldNames.length];
try {
for (int i = 0; i < fieldValues.length; i++) {
fieldValues[i] = getPropertyDescriptor(obj.getClass(), fieldNames[i]).getReadMethod().invoke(obj);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
if (Arrays.stream(fieldValues).noneMatch(Objects::nonNull)) {
validatorContext.buildConstraintViolationWithTemplate(messageKey)
.addPropertyNode(errorOnProperty)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
return true;
}
}
Pay attention to the last if condition block, this checks if no non null value is found then specifies the error message, the property with which the error message will be bound to and will add the constraint violation.
To use the annotation in a class
/**
* #author rumman
* #since 9/23/19
*/
#NotNullAny(fieldNames = {"field1", "field2", "field3"},
errorOnProperty = "field1",
messageKey = "my.error.msg.key")
public class TestEntityForValidation {
private String field1;
private String field2;
private String field3;
// standard constructor(s) and getter & setters below
}
If you could use javax.validation then I would use #AssertTrue.
Using javax.validation shouldn't be a problem since hibernate-validation is an implementation of javax.validation's interfaces/annotations which itself is an "implementation" of the Bean Validation 2.0 specification
#Entity
#OneFieldMustBeNotNullConstraint(list={fieldA,fieldB})
public class Card {
#Id
#GeneratedValue
private Integer card_id;
#Column(nullable = true)
private Long fieldA;
#Column(nullable = true)
private Long fieldB;
#AssertTrue(message = "at least one should be non-null")
public boolean isValid1() {
return Objects.nonNull(fieldA) || Objects.nonNull(fieldB);
}
}
Related
I am creating a shared component for Request Date constraints, Begin Date is before End Date.
I want to take my current Validation request, and make it common, so I type in the (Begin and EndDate class members for any Class), and it will work. How can this be done? I use annotations above the request class, in ProductRequest below .
Note: How do I set Start and End date parameters in the annotation; they may not always be "Start/End" field members, sometimes they could be "Begin/Finish" in another class .
#DatesRequestConstraint
public class ProductRequest {
private Long productId;
private DateTime startDate;
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ TYPE, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
public #interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, ProductRequest> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(ProductRequest productRequest, ConstraintValidatorContext constraintValidatorContext) {
if (productRequest.getStartDate() != null &&
productRequest.getEndDate() != null &&
productRequest.getStartDate().isAfter(productRequest.getEndDate())) {
return false;
}
else return true;
}
You can:
Implement ConstraintValidator<DatesMatch, Object> so that you can apply the #DatesMatch annotation on any type;
Add custom String fields to the #DatesMatch annotation where you can specify the names of the fields you want to validate;
Use reflection at runtime to access the field values by their specified name.
There's a similar example of class-level validation over multiple custom fields here: Baeldung: Spring MVC Custom Validation (scroll down to "9. Custom Class Level Validation").
Customized to your example, something like this should work:
#Constraint(validatedBy = DatesMatchValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "The dates don't match.";
String startField();
String endField();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#interface List {
DatesMatch[] value();
}
}
// Accept a list of items so that you can validate more than one pair of dates on the same object if needed
#DatesMatch.List({
#DatesMatch(
startField = "startDate",
endField = "endDate",
message = "The end date must be after the start date."
)
})
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class DatesMatchValidator implements ConstraintValidator<DatesMatch, Object> {
private String startField;
private String endField;
public void initialize(DatesMatch constraintAnnotation) {
this.startField = constraintAnnotation.startField();
this.endField = constraintAnnotation.endField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Instant startFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(startField);
Instant endFieldValue = (Instant) new BeanWrapperImpl(value)
.getPropertyValue(endField);
if (startFieldValue == null || endFieldValue == null) {
return true;
}
return endFieldValue.isAfter(startFieldValue);
}
}
Update: (in response to comment):
this answer is great, allows multiple pair of dates, however isn't type-string safe, person can type in whatever for the fields in the product fields
Implementing ConstraintValidator<DatesMatch, Object> is meant as an easy catch-all solution you can apply to any class.
But you can absolutely do it in a more type-safe way by implementing a separate ConstraintValidator for each type you want to validate (i.e. ConstraintValidator<DatesMatch, ProductRequest>, ConstraintValidator<DatesMatch, AnotherRequest>, ...) and then specify all of them in the #Constraint(validatedBy={...}) attribute:
#Constraint(validatedBy = {ProductRequestDatesMatchValidator.class, AnotherRequestDatesMatchValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface DatesMatch {
String message() default "Invalid dates request.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
#DatesMatch(message = "Start and end dates do not match!")
public class ProductRequest {
private Long productId;
private Instant startDate;
private Instant endDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
#DatesMatch(message = "Begin and finish dates do not match!")
public class AnotherRequest {
private Long productId;
private Instant beginDate;
private Instant finishDate;
private List<String> productStatus;
/* Getters and setters omitted */
}
public class ProductRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, ProductRequest> {
#Override
public boolean isValid(ProductRequest value, ConstraintValidatorContext context) {
// No need to cast here
Instant startDate = value.getStartDate();
Instant endDate = value.getEndDate();
// You could reuse this logic between each implementation by putting it in a parent class or a utility method
if (startDate == null || endDate == null) {
return true;
}
return startDate.isBefore(endDate);
}
}
public class AnotherRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, AnotherRequest> {
#Override
public boolean isValid(AnotherRequest value, ConstraintValidatorContext context) {
Instant beginDate = value.getBeginDate();
Instant finishDate = value.getFinishDate();
if (beginDate == null || finishDate == null) {
return true;
}
return beginDate.isBefore(finishDate);
}
}
Do note, however, that this is still not compile-time type-safe, as you could put the #DatesMatch annotation on a class for which you haven't written an implementation and the validation will only fail at runtime.
(You could achieve compile-time type-safety using annotation processing, but this another topic for another time.)
You can annotate startDate and endDate with custom annotations something like:
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime endDate;
Then in your isValid(), you can access both startDate and endDate fields by their annotations by iterating over all class fields (in your case, all ProductRequest fields) and checking the following:
field.isAnnotationPresent(StartDateField.class)
field.isAnnotationPresent(EndDateField.class)
The complete code could be as follows:
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ ANNOTATION_TYPE.TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = ProductValidator.class)
#Documented
#interface DatesRequestConstraint {
String message() default "Invalid dates request.";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
}
#DatesRequestConstraint
class ProductRequest {
private Long productId;
#StartDateField
private DateTime startDate;
#EndDateField
private DateTime EndDate;
private List<String> productStatus;
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface StartDateField {
}
#Target({ ElementType.FIELD })
#Retention(RUNTIME)
#Documented
#interface EndDateField {
}
public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, Object> {
#Override
public void initialize(DatesRequestConstraint constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
#Override
public boolean isValid(Object requestObject, ConstraintValidatorContext constraintValidatorContext) {
DateTime startDate = getDateFieldByAnnotation(requestObject, StartDateField.class);
DateTime endDate = getDateFieldByAnnotation(requestObject, EndDateField.class);
if (startDate != null &&
endDate != null &&
startDate.isAfter(endDate)) {
return false;
} else return true;
}
private DateTime getDateFieldByAnnotation(Object requestObject, Class<? extends Annotation> annotationClass) {
return Arrays.stream(requestObject.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(annotationClass)).map(field -> {
try {
return field.get(requestObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}).map(DateTime.class::cast).findAny().orElse(null);
}
}
Is there an implementation of (or third-party implementation for) cross field validation in Hibernate Validator 4.x? If not, what is the cleanest way to implement a cross field validator?
As an example, how can you use the API to validate two bean properties are equal (such as validating a password field matches the password verify field).
In annotations, I'd expect something like:
public class MyBean {
#Size(min=6, max=50)
private String pass;
#Equals(property="pass")
private String passVerify;
}
Each field constraint should be handled by a distinct validator annotation, or in other words it's not suggested practice to have one field's validation annotation checking against other fields; cross-field validation should be done at the class level. Additionally, the JSR-303 Section 2.2 preferred way to express multiple validations of the same type is via a list of annotations. This allows the error message to be specified per match.
For example, validating a common form:
#FieldMatch.List({
#FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
#FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
})
public class UserRegistrationForm {
#NotNull
#Size(min=8, max=25)
private String password;
#NotNull
#Size(min=8, max=25)
private String confirmPassword;
#NotNull
#Email
private String email;
#NotNull
#Email
private String confirmEmail;
}
The Annotation:
package constraints;
import constraints.impl.FieldMatchValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
* Validation annotation to validate that 2 fields have the same value.
* An array of fields and their matching confirmation fields can be supplied.
*
* Example, compare 1 pair of fields:
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
*
* Example, compare more than 1 pair of fields:
* #FieldMatch.List({
* #FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
* #FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = FieldMatchValidator.class)
#Documented
public #interface FieldMatch
{
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* #return The first field
*/
String first();
/**
* #return The second field
*/
String second();
/**
* Defines several <code>#FieldMatch</code> annotations on the same element
*
* #see FieldMatch
*/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List
{
FieldMatch[] value();
}
}
The Validator:
package constraints.impl;
import constraints.FieldMatch;
import org.apache.commons.beanutils.BeanUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
{
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final FieldMatch constraintAnnotation)
{
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
try
{
final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
}
catch (final Exception ignore)
{
// ignore
}
return true;
}
}
I suggest you another possible solution. Perhaps less elegant, but easier!
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
#NotNull
private LocalDate passExpiry;
#NotNull
private LocalDate dateOfJoining;
#AssertTrue(message = "Fields `pass` and `passVerify` should be equal")
// Any method name is ok als long it begins with `is`
private boolean isValidPass() {
//return pass == null && passVerify == null || pass.equals(passVerify);
// Since Java 7:
return Objects.equals(pass, passVerify);
}
#AssertTrue(message = "Field `passExpiry` should be later than `dateOfJoining`")
// Other rules can also be validated in other methods
private boolean isPassExpiryAfterDateOfJoining() {
return dateOfJoining.isBefore(passExpiry);
}
}
The isValid() and isPassExpiryAfterDateOfJoining() methods are invoked automatically by the validator. The property paths reported in the ConstraintViolations will be extracted from the method names: valid and passExpiryAfterDateOfJoining.
I'm surprised this isn't available out of the box. Anyway, here is a possible solution.
I've created a class level validator, not the field level as described in the original question.
Here is the annotation code:
package com.moa.podium.util.constraints;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MatchesValidator.class)
#Documented
public #interface Matches {
String message() default "{com.moa.podium.util.constraints.matches}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String field();
String verifyField();
}
And the validator itself:
package com.moa.podium.util.constraints;
import org.mvel2.MVEL;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MatchesValidator implements ConstraintValidator<Matches, Object> {
private String field;
private String verifyField;
public void initialize(Matches constraintAnnotation) {
this.field = constraintAnnotation.field();
this.verifyField = constraintAnnotation.verifyField();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldObj = MVEL.getProperty(field, value);
Object verifyFieldObj = MVEL.getProperty(verifyField, value);
boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
if (neitherSet) {
return true;
}
boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
if (!matches) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("message")
.addNode(verifyField)
.addConstraintViolation();
}
return matches;
}
}
Note that I've used MVEL to inspect the properties of the object being validated. This could be replaced with the standard reflection APIs or if it is a specific class you are validating, the accessor methods themselves.
The #Matches annotation can then be used used on a bean as follows:
#Matches(field="pass", verifyField="passRepeat")
public class AccountCreateForm {
#Size(min=6, max=50)
private String pass;
private String passRepeat;
...
}
As a disclaimer, I wrote this in the last 5 minutes, so I probably haven't ironed out all the bugs yet. I'll update the answer if anything goes wrong.
With Hibernate Validator 4.1.0.Final I recommend using #ScriptAssert. Exceprt from its JavaDoc:
Script expressions can be written in any scripting or expression
language, for which a JSR 223 ("Scripting for the JavaTM Platform")
compatible engine can be found on the classpath.
Note: the evaluation is being performed by a scripting "engine" running in the Java VM, therefore on Java "server side", not on "client side" as stated in some comments.
Example:
#ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
or with shorter alias and null-safe:
#ScriptAssert(lang = "javascript", alias = "_",
script = "_.passVerify != null && _.passVerify.equals(_.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
or with Java 7+ null-safe Objects.equals():
#ScriptAssert(lang = "javascript", script = "Objects.equals(_this.passVerify, _this.pass)")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
Nevertheless, there is nothing wrong with a custom class level validator #Matches solution.
Cross fields validations can be done by creating custom constraints.
Example:- Compare password and confirmPassword fields of User instance.
CompareStrings
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy=CompareStringsValidator.class)
#Documented
public #interface CompareStrings {
String[] propertyNames();
StringComparisonMode matchMode() default EQUAL;
boolean allowNull() default false;
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
StringComparisonMode
public enum StringComparisonMode {
EQUAL, EQUAL_IGNORE_CASE, NOT_EQUAL, NOT_EQUAL_IGNORE_CASE
}
CompareStringsValidator
public class CompareStringsValidator implements ConstraintValidator<CompareStrings, Object> {
private String[] propertyNames;
private StringComparisonMode comparisonMode;
private boolean allowNull;
#Override
public void initialize(CompareStrings constraintAnnotation) {
this.propertyNames = constraintAnnotation.propertyNames();
this.comparisonMode = constraintAnnotation.matchMode();
this.allowNull = constraintAnnotation.allowNull();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext context) {
boolean isValid = true;
List<String> propertyValues = new ArrayList<String> (propertyNames.length);
for(int i=0; i<propertyNames.length; i++) {
String propertyValue = ConstraintValidatorHelper.getPropertyValue(String.class, propertyNames[i], target);
if(propertyValue == null) {
if(!allowNull) {
isValid = false;
break;
}
} else {
propertyValues.add(propertyValue);
}
}
if(isValid) {
isValid = ConstraintValidatorHelper.isValid(propertyValues, comparisonMode);
}
if (!isValid) {
/*
* if custom message was provided, don't touch it, otherwise build the
* default message
*/
String message = context.getDefaultConstraintMessageTemplate();
message = (message.isEmpty()) ? ConstraintValidatorHelper.resolveMessage(propertyNames, comparisonMode) : message;
context.disableDefaultConstraintViolation();
ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(message);
for (String propertyName : propertyNames) {
NodeBuilderDefinedContext nbdc = violationBuilder.addNode(propertyName);
nbdc.addConstraintViolation();
}
}
return isValid;
}
}
ConstraintValidatorHelper
public abstract class ConstraintValidatorHelper {
public static <T> T getPropertyValue(Class<T> requiredType, String propertyName, Object instance) {
if(requiredType == null) {
throw new IllegalArgumentException("Invalid argument. requiredType must NOT be null!");
}
if(propertyName == null) {
throw new IllegalArgumentException("Invalid argument. PropertyName must NOT be null!");
}
if(instance == null) {
throw new IllegalArgumentException("Invalid argument. Object instance must NOT be null!");
}
T returnValue = null;
try {
PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, instance.getClass());
Method readMethod = descriptor.getReadMethod();
if(readMethod == null) {
throw new IllegalStateException("Property '" + propertyName + "' of " + instance.getClass().getName() + " is NOT readable!");
}
if(requiredType.isAssignableFrom(readMethod.getReturnType())) {
try {
Object propertyValue = readMethod.invoke(instance);
returnValue = requiredType.cast(propertyValue);
} catch (Exception e) {
e.printStackTrace(); // unable to invoke readMethod
}
}
} catch (IntrospectionException e) {
throw new IllegalArgumentException("Property '" + propertyName + "' is NOT defined in " + instance.getClass().getName() + "!", e);
}
return returnValue;
}
public static boolean isValid(Collection<String> propertyValues, StringComparisonMode comparisonMode) {
boolean ignoreCase = false;
switch (comparisonMode) {
case EQUAL_IGNORE_CASE:
case NOT_EQUAL_IGNORE_CASE:
ignoreCase = true;
}
List<String> values = new ArrayList<String> (propertyValues.size());
for(String propertyValue : propertyValues) {
if(ignoreCase) {
values.add(propertyValue.toLowerCase());
} else {
values.add(propertyValue);
}
}
switch (comparisonMode) {
case EQUAL:
case EQUAL_IGNORE_CASE:
Set<String> uniqueValues = new HashSet<String> (values);
return uniqueValues.size() == 1 ? true : false;
case NOT_EQUAL:
case NOT_EQUAL_IGNORE_CASE:
Set<String> allValues = new HashSet<String> (values);
return allValues.size() == values.size() ? true : false;
}
return true;
}
public static String resolveMessage(String[] propertyNames, StringComparisonMode comparisonMode) {
StringBuffer buffer = concatPropertyNames(propertyNames);
buffer.append(" must");
switch(comparisonMode) {
case EQUAL:
case EQUAL_IGNORE_CASE:
buffer.append(" be equal");
break;
case NOT_EQUAL:
case NOT_EQUAL_IGNORE_CASE:
buffer.append(" not be equal");
break;
}
buffer.append('.');
return buffer.toString();
}
private static StringBuffer concatPropertyNames(String[] propertyNames) {
//TODO improve concating algorithm
StringBuffer buffer = new StringBuffer();
buffer.append('[');
for(String propertyName : propertyNames) {
char firstChar = Character.toUpperCase(propertyName.charAt(0));
buffer.append(firstChar);
buffer.append(propertyName.substring(1));
buffer.append(", ");
}
buffer.delete(buffer.length()-2, buffer.length());
buffer.append("]");
return buffer;
}
}
User
#CompareStrings(propertyNames={"password", "confirmPassword"})
public class User {
private String password;
private String confirmPassword;
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getConfirmPassword() { return confirmPassword; }
public void setConfirmPassword(String confirmPassword) { this.confirmPassword = confirmPassword; }
}
Test
public void test() {
User user = new User();
user.setPassword("password");
user.setConfirmPassword("paSSword");
Set<ConstraintViolation<User>> violations = beanValidator.validate(user);
for(ConstraintViolation<User> violation : violations) {
logger.debug("Message:- " + violation.getMessage());
}
Assert.assertEquals(violations.size(), 1);
}
Output Message:- [Password, ConfirmPassword] must be equal.
By using the CompareStrings validation constraint, we can also compare more than two properties and we can mix any of four string comparison methods.
ColorChoice
#CompareStrings(propertyNames={"color1", "color2", "color3"}, matchMode=StringComparisonMode.NOT_EQUAL, message="Please choose three different colors.")
public class ColorChoice {
private String color1;
private String color2;
private String color3;
......
}
Test
ColorChoice colorChoice = new ColorChoice();
colorChoice.setColor1("black");
colorChoice.setColor2("white");
colorChoice.setColor3("white");
Set<ConstraintViolation<ColorChoice>> colorChoiceviolations = beanValidator.validate(colorChoice);
for(ConstraintViolation<ColorChoice> violation : colorChoiceviolations) {
logger.debug("Message:- " + violation.getMessage());
}
Output Message:- Please choose three different colors.
Similarly, we can have CompareNumbers, CompareDates, etc cross-fields validation constraints.
P.S. I have not tested this code under production environment (though I tested it under dev environment), so consider this code as Milestone Release. If you find a bug, please write a nice comment. :)
If you’re using the Spring Framework then you can use the Spring Expression Language (SpEL) for that. I’ve wrote a small library that provides JSR-303 validator based on SpEL – it makes cross-field validations a breeze! Take a look at https://github.com/jirutka/validator-spring.
This will validate length and equality of the password fields.
#SpELAssert(value = "pass.equals(passVerify)",
message = "{validator.passwords_not_same}")
public class MyBean {
#Size(min = 6, max = 50)
private String pass;
private String passVerify;
}
You can also easily modify this to validate the password fields only when not both empty.
#SpELAssert(value = "pass.equals(passVerify)",
applyIf = "pass || passVerify",
message = "{validator.passwords_not_same}")
public class MyBean {
#Size(min = 6, max = 50)
private String pass;
private String passVerify;
}
I have tried Alberthoven's example (hibernate-validator 4.0.2.GA) and i get an ValidationException: „Annotated methods must follow the JavaBeans naming convention. match() does not.“ too. After I renamed the method from „match“ to "isValid" it works.
public class Password {
private String password;
private String retypedPassword;
public Password(String password, String retypedPassword) {
super();
this.password = password;
this.retypedPassword = retypedPassword;
}
#AssertTrue(message="password should match retyped password")
private boolean isValid(){
if (password == null) {
return retypedPassword == null;
} else {
return password.equals(retypedPassword);
}
}
public String getPassword() {
return password;
}
public String getRetypedPassword() {
return retypedPassword;
}
}
I like the idea from Jakub Jirutka to use Spring Expression Language. If you don't want to add another library/dependency (assuming that you already use Spring), here is a simplified implementation of his idea.
The constraint:
#Constraint(validatedBy=ExpressionAssertValidator.class)
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ExpressionAssert {
String message() default "expression must evaluate to true";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
The validator:
public class ExpressionAssertValidator implements ConstraintValidator<ExpressionAssert, Object> {
private Expression exp;
public void initialize(ExpressionAssert annotation) {
ExpressionParser parser = new SpelExpressionParser();
exp = parser.parseExpression(annotation.value());
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
return exp.getValue(value, Boolean.class);
}
}
Apply like this:
#ExpressionAssert(value="pass == passVerify", message="passwords must be same")
public class MyBean {
#Size(min=6, max=50)
private String pass;
private String passVerify;
}
I made a small adaptation in Nicko's solution so that it is not necessary to use the Apache Commons BeanUtils library and replace it with the solution already available in spring, for those using it as I can be simpler:
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> {
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final FieldMatch constraintAnnotation) {
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object object, final ConstraintValidatorContext context) {
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(object);
final Object firstObj = beanWrapper.getPropertyValue(firstFieldName);
final Object secondObj = beanWrapper.getPropertyValue(secondFieldName);
boolean isValid = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
if (!isValid) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode(firstFieldName)
.addConstraintViolation();
}
return isValid;
}
}
I don't have the reputation for commenting on the first answer but wanted to add that I have added unit tests for the winning answer and have the following observations:
If you get the first or field names wrong then you get a validation error as though the values don't match. Don't get tripped up by spelling mistakes e.g.
#FieldMatch(first="invalidFieldName1", second="validFieldName2")
The validator will accept equivalent data types i.e. these will all pass with FieldMatch:
private String stringField = "1";
private Integer integerField = new Integer(1)
private int intField = 1;
If the fields are of an object type which does not implement equals, the validation will fail.
Very nice solution bradhouse. Is there any way to apply the #Matches annotation to more than one field?
EDIT:
Here's the solution I came up with to answer this question, I modified the Constraint to accept an array instead of a single value:
#Matches(fields={"password", "email"}, verifyFields={"confirmPassword", "confirmEmail"})
public class UserRegistrationForm {
#NotNull
#Size(min=8, max=25)
private String password;
#NotNull
#Size(min=8, max=25)
private String confirmPassword;
#NotNull
#Email
private String email;
#NotNull
#Email
private String confirmEmail;
}
The code for the annotation:
package springapp.util.constraints;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = MatchesValidator.class)
#Documented
public #interface Matches {
String message() default "{springapp.util.constraints.matches}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] fields();
String[] verifyFields();
}
And the implementation:
package springapp.util.constraints;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.beanutils.BeanUtils;
public class MatchesValidator implements ConstraintValidator<Matches, Object> {
private String[] fields;
private String[] verifyFields;
public void initialize(Matches constraintAnnotation) {
fields = constraintAnnotation.fields();
verifyFields = constraintAnnotation.verifyFields();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
boolean matches = true;
for (int i=0; i<fields.length; i++) {
Object fieldObj, verifyFieldObj;
try {
fieldObj = BeanUtils.getProperty(value, fields[i]);
verifyFieldObj = BeanUtils.getProperty(value, verifyFields[i]);
} catch (Exception e) {
//ignore
continue;
}
boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
if (neitherSet) {
continue;
}
boolean tempMatches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
if (!tempMatches) {
addConstraintViolation(context, fields[i]+ " fields do not match", verifyFields[i]);
}
matches = matches?tempMatches:matches;
}
return matches;
}
private void addConstraintViolation(ConstraintValidatorContext context, String message, String field) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation();
}
}
You need to call it explicitly. In the example above, bradhouse has given you all the steps to write a custom constraint.
Add this code in your caller class.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);
in the above case it would be
Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);
Why not try Oval: http://oval.sourceforge.net/
I looks like it supports OGNL so maybe you could do it by a more natural
#Assert(expr = "_value ==_this.pass").
You guys are awesome. Really amazing ideas. I like Alberthoven's and McGin's most, so I decided to combine both ideas. And develop some generic solution to cater all cases. Here is my proposed solution.
#Documented
#Constraint(validatedBy = NotFalseValidator.class)
#Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface NotFalse {
String message() default "NotFalse";
String[] messages();
String[] properties();
String[] verifiers();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotFalseValidator implements ConstraintValidator<NotFalse, Object> {
private String[] properties;
private String[] messages;
private String[] verifiers;
#Override
public void initialize(NotFalse flag) {
properties = flag.properties();
messages = flag.messages();
verifiers = flag.verifiers();
}
#Override
public boolean isValid(Object bean, ConstraintValidatorContext cxt) {
if(bean == null) {
return true;
}
boolean valid = true;
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
for(int i = 0; i< properties.length; i++) {
Boolean verified = (Boolean) beanWrapper.getPropertyValue(verifiers[i]);
valid &= isValidProperty(verified,messages[i],properties[i],cxt);
}
return valid;
}
boolean isValidProperty(Boolean flag,String message, String property, ConstraintValidatorContext cxt) {
if(flag == null || flag) {
return true;
} else {
cxt.disableDefaultConstraintViolation();
cxt.buildConstraintViolationWithTemplate(message)
.addPropertyNode(property)
.addConstraintViolation();
return false;
}
}
}
#NotFalse(
messages = {"End Date Before Start Date" , "Start Date Before End Date" } ,
properties={"endDateTime" , "startDateTime"},
verifiers = {"validDateRange" , "validDateRange"})
public class SyncSessionDTO implements ControllableNode {
#NotEmpty #NotPastDate
private Date startDateTime;
#NotEmpty
private Date endDateTime;
public Date getStartDateTime() {
return startDateTime;
}
public void setStartDateTime(Date startDateTime) {
this.startDateTime = startDateTime;
}
public Date getEndDateTime() {
return endDateTime;
}
public void setEndDateTime(Date endDateTime) {
this.endDateTime = endDateTime;
}
public Boolean getValidDateRange(){
if(startDateTime != null && endDateTime != null) {
return startDateTime.getTime() <= endDateTime.getTime();
}
return null;
}
}
Solution realated with question:
How to access a field which is described in annotation property
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Match {
String field();
String message() default "";
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MatchValidator.class)
#Documented
public #interface EnableMatchConstraint {
String message() default "Fields must match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MatchValidator implements ConstraintValidator<EnableMatchConstraint, Object> {
#Override
public void initialize(final EnableMatchConstraint constraint) {}
#Override
public boolean isValid(final Object o, final ConstraintValidatorContext context) {
boolean result = true;
try {
String mainField, secondField, message;
Object firstObj, secondObj;
final Class<?> clazz = o.getClass();
final Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Match.class)) {
mainField = field.getName();
secondField = field.getAnnotation(Match.class).field();
message = field.getAnnotation(Match.class).message();
if (message == null || "".equals(message))
message = "Fields " + mainField + " and " + secondField + " must match!";
firstObj = BeanUtils.getProperty(o, mainField);
secondObj = BeanUtils.getProperty(o, secondField);
result = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
if (!result) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(mainField).addConstraintViolation();
break;
}
}
}
} catch (final Exception e) {
// ignore
//e.printStackTrace();
}
return result;
}
}
And how to use it...? Like this:
#Entity
#EnableMatchConstraint
public class User {
#NotBlank
private String password;
#Match(field = "password")
private String passwordConfirmation;
}
I'd like to create a property editor for my Java Beans. For that I need a class that implements BeanInfo. The problem: I really don't feel comfortable with declaring an attribute name as String like this (floodColor, fillColor, percent):
import java.beans.*;
public class BarChartBeanBeanInfo extends SimpleBeanInfo
{
private final static Class myClass = BarChartBean.class;
public PropertyDescriptor[] getPropertyDescriptors()
{
try {
PropertyDescriptor flc = new PropertyDescriptor("floodColor", myClass);
PropertyDescriptor fic = new PropertyDescriptor("fillColor", myClass);
PropertyDescriptor pct = new PropertyDescriptor("percent", myClass);
PropertyDescriptor[] list = { flc, fic, pct };
return list;
}
catch (IntrospectionException iexErr)
{
throw new Error(iexErr.toString());
}
}
};
I got this from an example article about how to create a custom property editor: The trick to controlling bean customization. The article is from the year 1997.
How do you create a property editor in the year 2016 without using string declarations of variables which obviously will lead to runtime exceptions once someone changes the variable name?
I mean other than to use the Introspector. Is there e. g. some kind of annotation support for attribute names of classes?
Thank you very much for the expertise!
I tried with custom annotations, it seems to work. At least it's typesafe now and coupled to the fields.
Code
ExampleBean.java
import annotations.Descriptor;
import annotations.Property;
#Descriptor(displayName = "Example Bean", shortDescription = "This is an example bean")
public class ExampleBean {
#Property(displayName = "Integer Value", shortDescription = "This is an integer value")
int integerValue;
#Property(displayName = "Double Value", shortDescription = "This is a double value")
double doubleValue;
#Property(displayName = "String Value", shortDescription = "This is a string value")
String stringValue;
public int getIntegerValue() {
return integerValue;
}
public void setIntegerValue(int integerValue) {
this.integerValue = integerValue;
}
public double getDoubleValue() {
return doubleValue;
}
public void setDoubleValue(double doubleValue) {
this.doubleValue = doubleValue;
}
public String getStringValue() {
return stringValue;
}
public void setStringValue(String stringValue) {
this.stringValue = stringValue;
}
}
Descriptor.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface Descriptor {
public String displayName() default "";
public String shortDescription() default "";
}
Property.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.METHOD })
public #interface Property {
public String displayName() default "";
public String shortDescription() default "";
}
ExampleBeanBeanInfo.java
import java.beans.BeanDescriptor;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import annotations.Descriptor;
import annotations.Property;
public class ExampleBeanBeanInfo extends SimpleBeanInfo {
private final static Class<ExampleBean> myClass = ExampleBean.class;
public PropertyDescriptor[] getPropertyDescriptors() {
List<PropertyDescriptor> propertyDescriptors = new ArrayList<>();
try {
for (Field field : myClass.getDeclaredFields()) {
if (field.isAnnotationPresent(Property.class)) {
Annotation annotation = field.getAnnotation(Property.class);
Property property = (Property) annotation;
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), myClass);
propertyDescriptor.setDisplayName(property.displayName());
propertyDescriptor.setShortDescription(property.shortDescription());
propertyDescriptors.add(propertyDescriptor);
}
}
return propertyDescriptors.toArray(new PropertyDescriptor[propertyDescriptors.size()]);
} catch (Exception iexErr) {
throw new Error(iexErr.toString());
}
}
public BeanDescriptor getBeanDescriptor() {
BeanDescriptor desc = new BeanDescriptor(myClass);
if (myClass.isAnnotationPresent(Descriptor.class)) {
Annotation annotation = myClass.getAnnotation(Descriptor.class);
Descriptor descriptor = (Descriptor) annotation;
desc.setDisplayName(descriptor.displayName());
desc.setShortDescription(descriptor.shortDescription());
}
return desc;
}
}
Main.java
import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(ExampleBean.class);
BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
System.out.printf( "Bean display name = '%s', description = '%s'\n", beanDescriptor.getDisplayName(), beanDescriptor.getShortDescription());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String propertyName = propertyDescriptor.getName();
System.out.printf("Property field name = '%s', display name = '%s', description = '%s'\n", propertyName, propertyDescriptor.getDisplayName(), propertyDescriptor.getShortDescription());
}
System.exit(0);
;
}
}
Console output:
Bean display name = 'Example Bean', description = 'This is an example bean'
Property field name = 'doubleValue', display name = 'Double Value', description = 'This is a double value'
Property field name = 'integerValue', display name = 'Integer Value', description = 'This is an integer value'
Property field name = 'stringValue', display name = 'String Value', description = 'This is a string value'
This example exposes the displayname and shortdescription methods, one would have to add others of the bean and property descriptor.
If anyone has a better way, please let me know.
I've got a web service which manages Parada objects. What I want to achieve seems straightforward: return lists of these objects:
List<Parada> list
This list is returned using a Service class which uses another DAO class, just commenting it out.
Besides, my common practise is that every web method return a Response using ResponseBuilder, as in here:
return Response.ok(obj, MediaType.APPLICATION_JSON).build();
This is an example of one of my web methods:
#GET
#Consumes(value = MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_JSON)
#Path("{idParadaGtfs}")
public Response getParadasPorIdGtfs(
#PathParam(value = "idParadaGtfs") Integer pCodigoParadaEnGtfs
){
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
return Response.ok(paradas, MediaType.APPLICATION_JSON).build();
}catch(HibernateException e){
String msg = "Error HibernateException: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}catch(Exception e){
String msg = "Error Exception: " + e.getMessage();
LogHelper.logError(logger, msg, true);
e.printStackTrace();
return Response.serverError().tag(msg).build();
}
}
Unfortunately, I'm not receiving any object and I get the following error everytime I execute the web method described above:
nov 26, 2015 2:20:16 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
GRAVE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<model.Parada>.
What do I have to implement to let my web methods build Responses using Lists?
Thank you!
EDIT:
I've been able to make it work by making some changes and additions, which I'll describe now.
First of all, I've added a Parada container class, ParadaContainer:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import com.ingartek.ws.paradasasociadasws.model.Parada;
#XmlRootElement
public class ParadaContainer implements Serializable {
private static final long serialVersionUID = 6535386309072039406L;
private List<Parada> paradas;
public ParadaContainer(ArrayList<Parada> pParadas) {
this.setParadas(pParadas);
}
public List<Parada> getParadas() {
return paradas;
}
public void setParadas(List<Parada> paradas) {
this.paradas = paradas;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ParadaContainer [");
if (paradas != null) {
builder.append("paradas=");
for(Parada p : paradas){
builder.append(p.toString());
}
}
builder.append("]");
return builder.toString();
}
}
Now, I'm not returning a List of Parada objects, instead I return a single ParadaContainer object:
ParadaContainer paradas = new ParadaContainer(new ArrayList<Parada>(service.getParadas()));
return Response
.ok(paradas)
.type(MediaType.APPLICATION_JSON)
.build();
I don't know whether they are mandatory or not, but I've created another class (MyObjectMapperProvider)...
import javax.ws.rs.ext.ContextResolver;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(SerializationFeature.INDENT_OUTPUT, true);
return result;
}
}
...and edited my Application class and added some lines (see as of *Jackson * comment until Clases de Servicios comment):
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import org.glassfish.jersey.jackson.JacksonFeature;
import com.ingartek.ws.paradasasociadasws.ws.ParadasWS;
public class App extends Application {
private final Set<Class<?>> classes;
public App() {
HashSet<Class<?>> c = new HashSet<Class<?>>();
// Filtro CORS:
c.add(CORSFilter.class);
// Jackson
c.add(MyObjectMapperProvider.class);
c.add(JacksonFeature.class);
// Clases de Servicios:
c.add(ParadasWS.class);
classes = Collections.unmodifiableSet(c);
}
#Override
public Set<Class<?>> getClasses() {
return classes;
}
}
Afterwards, I've edited my class model by adding some annotations to them (#XmlRootElement and #JsonProperty; removed irrelevant getters, setters, hashCode, equals and toString methods):
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "grupo")
#Entity
#Table(name = "grupos_cercania_exacta")
public class Grupo implements Serializable {
#Transient
private static final long serialVersionUID = -5679016396196675191L;
#JsonProperty("id")
#Id
#Column(name = "id_grupo")
private Integer id;
...
}
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "operador")
#Entity
#Table(name = "operadores_asociados")
public class Operador implements Serializable {
#Transient
private static final long serialVersionUID = -7557099187432476588L;
/*
Atributos
*/
#JsonProperty("codigo")
#Id
#Column(name = "codigo_operador", insertable = false, updatable = false)
private Integer codigo;
#JsonProperty("nombre")
#Column(name = "descripcion_corta", insertable = false, updatable = false)
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion_larga", insertable = false, updatable = false)
private String descripcion;
#JsonProperty("web")
#Column(name = "direccion_web", insertable = false, updatable = false)
private String web;
#JsonProperty("telefono")
#Column(name = "telefono", insertable = false, updatable = false)
private String telefono;
...
}
import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;
import com.fasterxml.jackson.annotation.JsonProperty;
#XmlRootElement(name = "parada")
#Entity
#Table(name = "paradas_asociadas")
public class Parada implements Serializable {
#Transient
private static final long serialVersionUID = -3594254497389126197L;
#JsonProperty("id")
#Id
#Column(name = "id")
private UUID id;
#JsonProperty("codigoMunicipio")
#Column(name = "codigo_municipio")
private Integer codigoMunicipio;
#JsonProperty("nombre")
#Column(name = "nombre")
private String nombre;
#JsonProperty("descripcion")
#Column(name = "descripcion")
private String descripcion;
#JsonProperty("idGtfs")
#Column(name = "id_gtfs")
private Integer idGtfs;
#JsonProperty("idWs")
#Column(name = "id_ws")
private Integer idWs;
#JsonProperty("latitud")
#Column(name = "latitud")
private Double latitud;
#JsonProperty("longitud")
#Column(name = "longitud")
private Double longitud;
#JsonProperty("utmX")
#Column(name = "utm_x")
private Double utmX;
#JsonProperty("utmY")
#Column(name = "utm_y")
private Double utmY;
#JsonProperty("grupo")
#ManyToOne
#JoinColumn(name = "grupo_cercania_exacta_id")
private Grupo grupo;
#JsonProperty("operador")
#ManyToOne
#JoinColumn(name = "operador")
private Operador operador;
...
}
I've to admit that I've had some problems just after these changes. Sharp people could've realised that there is a missing attribute regarding the previous Parada class: the lack of Point attribute. This attribute was causing me some problems, this is, the absence of a Serializer and a Serializer was preventing me from creating a successful JSON. So I googled it out and found three options:
Remove the Point item. This was my ultimate choice, as Point was superfluous due to the existence of Latitude and Longitude elements and because it just could bother or confuse the final user.
Creating a custom Serializer and Deserializer. Fortunately I found the following link, which describes the process of creating them. The following is described in here:
Add these annotations to our coordinates field:
#JsonSerialize(using = PointToJsonSerializer.class)
#JsonDeserialize(using = JsonToPointDeserializer.class)
Create such serializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.vividsolutions.jts.geom.Point;
public class PointToJsonSerializer extends JsonSerializer<Point> {
#Override
public void serialize(Point value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
String jsonValue = "null";
try
{
if(value != null) {
double lat = value.getY();
double lon = value.getX();
jsonValue = String.format("POINT (%s %s)", lat, lon);
}
}
catch(Exception e) {}
jgen.writeString(jsonValue);
}
}
Create such deserializer:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.PrecisionModel;
public class JsonToPointDeserializer extends JsonDeserializer<Point> {
private final static GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 26910);
#Override
public Point deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
String text = jp.getText();
if(text == null || text.length() <= 0)
return null;
String[] coordinates = text.replaceFirst("POINT ?\\(", "").replaceFirst("\\)", "").split(" ");
double lat = Double.parseDouble(coordinates[0]);
double lon = Double.parseDouble(coordinates[1]);
Point point = geometryFactory.createPoint(new Coordinate(lat, lon));
return point;
}
catch(Exception e){
return null;
}
}
}
The last option is to use Jackson Datatype JTS library, whose github repository lays here.
I lasted some hours so that I could find these solutions, but finally I got them. Hope it helps to someone. Thank you!
Either you don't have a JSON provider (I am guessing you do) or you are using MOXy. Under the latter assumption, with MOXy, it needs to know the type information in order to be able to serialize. When you return Response, you are wrapping the object, which takes away type information (because of type erasure), as opposed to if you were doing
#GET
public List<Parada> get() {}
Here the type information is known. But doing
#GET
public Response get() {
List<Parada> list..
return Response.ok(list)...
}
The type is hidden and erased by the time the entity reaches the serialization phase of the processing.
To get around this, GenericEntity was introduced
Normally type erasure removes generic type information such that a Response instance that contains, e.g., an entity of type List<String> appears to contain a raw List<?> at runtime. When the generic type is required to select a suitable MessageBodyWriter, this class may be used to wrap the entity and capture its generic type.
So you can do
List<Parada> paradas = ...
GenericEntity<List<Parada>> entity = new GenericEntity<List<Parada>>(paradas){};
return Response.ok(entity, ...)...
Second option, is to instead of using MOXy, use Jackson instead. With Jackson, the type info is not needed (in most cases), as the serializer just introspects and the bean bean properties to get the data.
It is not allowed to send a List back. Probably because List has no #XmlRootElement notation. You can create your own container:
#XmlRootElement
public class ParadaContainer implements Serializable {
private List<Parada> list;
public List<Parada> getList() {
return list;
}
public void setList(List<Parada> list) {
this.list = list;
}
}
You part will look like:
try{
getServiceIfNecessary();
List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
ParadaContainer paradaContainer = new ParadaContainer();
paradaContainer.setList(paradas);
return Response.ok(paradaContainer, MediaType.APPLICATION_JSON).build();
}
I'm looking to do a little custom validation with JSR-303 javax.validation.
I have a field. And If a certain value is entered into this field I want to require that a few other fields are not null.
I'm trying to figure this out. Not sure exactly what I would call this to help find an explanation.
Any help would be appreciated. I am pretty new to this.
At the moment I'm thinking a Custom Constraint. But I'm not sure how to test the value of the dependent field from within the annotation. Basically I'm not sure how to access the panel object from the annotation.
public class StatusValidator implements ConstraintValidator<NotNull, String> {
#Override
public void initialize(NotNull constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("Canceled".equals(panel.status.getValue())) {
if (value != null) {
return true;
}
} else {
return false;
}
}
}
It's the panel.status.getValue(); giving me trouble.. not sure how to accomplish this.
Define method that must validate to true and put the #AssertTrue annotation on the top of it:
#AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
The method must start with 'is'.
In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.
Use the following code as an idea (I've not test it).
Validator interface
/**
* Validates that field {#code dependFieldName} is not null if
* field {#code fieldName} has value {#code fieldValue}.
**/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
#Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
#Documented
public #interface NotNullIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "{NotNullIfAnotherFieldHasValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Validator implementation
/**
* Implementation of {#link NotNullIfAnotherFieldHasValue} validator.
**/
public class NotNullIfAnotherFieldHasValueValidator
implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
#Override
public void initialize(NotNullIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
String fieldValue = BeanUtils.getProperty(value, fieldName);
String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
.addNode(dependFieldName)
.addConstraintViolation();
return false;
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return true;
}
}
Validator usage example (hibernate-validator >= 6 with Java 8+)
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne")
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Validator usage example (hibernate-validator < 6; the old example)
#NotNullIfAnotherFieldHasValue.List({
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne"),
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
})
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.
See also this great answer: Cross field validation with Hibernate Validator (JSR 303)
You should make use of custom DefaultGroupSequenceProvider<T>:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation {}
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm> {
#Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField())) {
sequence.add(ConditionalValidation.class);
}
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
}
}
MyCustomForm.java
#GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {
private String someField;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
#NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
}
See also related question on this topic.
Here's my take on it, tried to keep it as simple as possible.
The interface:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = OneOfValidator.class)
#Documented
public #interface OneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
Validation implementation:
public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
private String[] fields;
#Override
public void initialize(OneOf annotation) {
this.fields = annotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("{" + template + "}")
.addConstraintViolation();
}
}
Usage:
#OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Messages:
one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
A different approach would be to create a (protected) getter that returns an object containing all dependent fields. Example:
public class MyBean {
protected String status;
protected String name;
#StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator can now access StatusAndSomething.status and StatusAndSomething.something and make a dependent check.
Sample below:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
#SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner {
private final Validator validator;
public SampleJavaXValidation(Validator validator) {
this.validator = validator;
}
public static void main(String[] args) {
SpringApplication.run(SampleJavaXValidation.class, args);
}
#Override
public void run(String... args) throws Exception {
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
}
public enum SampleTypes {
TYPE_A,
TYPE_B;
}
#Valid
public static class SampleDataCls {
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB) {
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
}
public SampleTypes getType() {
return type;
}
public String getValueA() {
return valueA;
}
public String getValueB() {
return valueB;
}
#Pattern(regexp = "TRUE")
public String getConditionalValueA() {
if (type.equals(SampleTypes.TYPE_A)) {
return valueA != null ? "TRUE" : "";
}
return "TRUE";
}
#Pattern(regexp = "TRUE")
public String getConditionalValueB() {
if (type.equals(SampleTypes.TYPE_B)) {
return valueB != null ? "TRUE" : "";
}
return "TRUE";
}
}
}