Перекрестная проверка полевой валидаторы Hibernate (JSR 303)
-
21-09-2019 - |
Вопрос
Существует ли реализация (или сторонней реализации для) валидации поперечного поля в Hibernate Validator 4.x? Если нет, то какой самый чистый способ реализовать валидатор поперечного поле?
В качестве примера, как вы можете использовать API для проверки двух свойств бобов равны (например, проверка поля пароля соответствует поле проверки пароля).
В аннотациях я ожидаю чего -то вроде:
public class MyBean {
@Size(min=6, max=50)
private String pass;
@Equals(property="pass")
private String passVerify;
}
Решение
Каждое ограничение поля должно быть обработано с помощью отчетливой аннотации валидатора, или, другими словами, не предполагается, что практика проверяет проверку аннотации одного поля против других областей; Перекрестная проверка должна выполняться на уровне класса. Кроме того, JSR-303 Раздел 2.2 Предпочтительный способ выражения нескольких валидаций одного типа находится через список аннотаций. Это позволяет указать сообщение об ошибке в соответствии с матчем.
Например, проверка общей формы:
@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;
}
Аннотация:
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();
}
}
Валидатор:
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;
}
}
Другие советы
Я предлагаю вам еще одно возможное решение. Возможно, менее элегантно, но проще!
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
@AssertTrue(message="passVerify field should be equal than pass field")
private boolean isValid() {
return this.pass.equals(this.passVerify);
}
}
А isValid
Метод автоматически вызывается валидатором.
Я удивлен, что это не доступно из коробки. Во всяком случае, вот возможное решение.
Я создал валидатор уровня класса, а не уровень полевого уровня, как описано в исходном вопросе.
Вот код аннотации:
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();
}
И сам валидатор:
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;
}
}
Обратите внимание, что я использовал MVEL, чтобы осмотреть свойства подтвержденного объекта. Это может быть заменено стандартным API -интерфейсом отражения или, если это конкретный класс, который вы проверяете, сами методы аксессуара.
Аннотация @matches может использоваться на фасоли следующим образом:
@Matches(field="pass", verifyField="passRepeat")
public class AccountCreateForm {
@Size(min=6, max=50)
private String pass;
private String passRepeat;
...
}
Как отказ от ответственности, я написал это за последние 5 минут, поэтому я, вероятно, еще не выгласил всех ошибок. Я обновлю ответ, если что -то пойдет не так.
С Warmernate Validator 4.1.0.final я рекомендую использовать @ScriptAssert. Анкет Exceprt от Javadoc:
Выражения сценариев могут быть написаны на любом языке сценариев или выражения, для которого JSR 223 («Сценарий для платформы Javatm») Совместимый с двигателем можно найти на классе.
Примечание. Оценка выполняется сценарием "двигатель«Запуск в виртуальной машине Java, поэтому на Java Server», нет на «Клиентской стороне», как указано в некоторых комментариях.
Пример:
@ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
или с более коротким псевдонимом и нулевым безопасным:
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.passVerify != null && _.passVerify.equals(_.pass)")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
или с 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;
}
Тем не менее, нет ничего плохого в пользовательском валидаторе уровня класса @Спички решение.
Проверки Cross Fields могут быть выполнены путем создания пользовательских ограничений.
Пример:- Сравните поля пароля и подтверждают слова экземпляра пользователя.
Сравнения
@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
}
Сравнение Vvalidator
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;
}
}
Пользователь
@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; }
}
Тест
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);
}
Выход Message:- [Password, ConfirmPassword] must be equal.
Используя ограничение проверки сравнения, мы также можем сравнить более двух свойств, и мы можем смешать любой из четырех методов сравнения строк.
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;
......
}
Тест
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());
}
Выход Message:- Please choose three different colors.
Аналогичным образом, мы можем иметь сравнительные, сравниваемые и т. Д.
Пса Я не проверил этот код в производственной среде (хотя я проверил его в Dev Environment), поэтому рассматривайте этот код как выпуск Milestone. Если вы найдете ошибку, напишите хороший комментарий. :)
Я попробовал пример Альбертовен (Hibernate-Validator 4.0.2.ga), и я получаю ValidationException: «Аннотированные методы должны следовать Конвенции о именовании Javabeans. match () нет. "тоже. После того, как я переименовал метод от «матча» до «Isvalid», он работает.
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;
}
}
Если вы используете Spring Framework, вы можете использовать для этого язык выражения Spring (SPEL). Я написал небольшую библиотеку, которая предоставляет валидатор JSR-303 на основе SPEL-это делает валидации по полю настолько ветерок! Взгляни на https://github.com/jirutka/validator-spring.
Это подтвердит длину и равенство полей пароля.
@SpELAssert(value = "pass.equals(passVerify)",
message = "{validator.passwords_not_same}")
public class MyBean {
@Size(min = 6, max = 50)
private String pass;
private String passVerify;
}
Вы также можете легко изменить это, чтобы проверить поля пароля только тогда, когда они не пустые.
@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;
}
У меня нет репутации комментировать первый ответ, но я хотел добавить, что я добавил модульные тесты для выигрыша и имею следующие наблюдения:
- Если вы неправильно понимаете первые или имена поля, то вы получите ошибку проверки, как если бы значения не совпадают. Не разжигайся по орфографическим ошибкам, например
@FieldMatch (First = "инвалидFieldName1 ", Second =" valifieldName2 ")
- Валидатор будут Принять эквивалентные типы данных, т. Е. Все они пройдут с FieldMatch:
Private String Stringfield = "1";
частное целое число Integerfield = Новое целое число (1)
private int intfield = 1;
- Если поля имеют тип объекта, который не реализует, равна, проверка потерпит неудачу.
Мне нравится идея из Якуб Джирутка использовать пружинный язык выражения. Если вы не хотите добавлять другую библиотеку/зависимость (при условии, что вы уже используете Spring), вот упрощенная реализация его идеи.
Ограничение:
@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();
}
Валидатор:
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);
}
}
Примените так:
@ExpressionAssert(value="pass == passVerify", message="passwords must be same")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
Очень хорошее решение, Брэдхаус. Есть ли способ применять аннотацию @matches на более чем одно поле?
РЕДАКТИРОВАТЬ: Вот решение, которое я придумал, чтобы ответить на этот вопрос, я изменил ограничение, чтобы принять массив вместо одного значения:
@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;
}
Код для аннотации:
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();
}
И реализация:
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();
}
}
Вы должны назвать это явно. В приведенном выше примере Брэдхаус дал вам все шаги, чтобы написать пользовательское ограничение.
Добавьте этот код в свой класс вызывающего абонента.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);
В приведенном выше случае это будет
Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);
Почему бы не попробовать овал: http://oval.sourceforge.net/
Я выглядит так, как будто он поддерживает ognl, так что, возможно, вы могли бы сделать это более естественным
@Assert(expr = "_value ==_this.pass").
Вы, ребята, классные. Действительно удивительные идеи. Мне нравится Alberthoven's а также Макгин Большинство, поэтому я решил объединить обе идеи. И разработать какое -то общее решение, чтобы удовлетворить все случаи. Вот мое предложенное решение.
@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;
}
}
Решение реализовано с вопросом:Как получить доступ к полю, которое описано в свойстве аннотации
@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;
}
}
И как его использовать ...? Как это:
@Entity
@EnableMatchConstraint
public class User {
@NotBlank
private String password;
@Match(field = "password")
private String passwordConfirmation;
}