Not sure if this possible. Wondering if I can create a custom constraint for a bean's field where it would use values of other field of the same class object. I tried implementing this with the code below:
public final class AWRRecordObject extends DoaAbstract implements
java.io.Serializable {
private static final long serialVersionUID = 1L;
#ValidateApplicationType
private String applicationType;
private String recordData;
private String programSource;
// ...
}
public class ApplicationTypeValidator implements
ConstraintValidator<ValidateApplicationType, AWRRecordObject> {
private Class<AWRRecordObject> awrRecObj; // ?
#Override
public void initialize(ValidateApplicationType constraintAnnotation) {
this.awrRecObj = constraintAnnotation.value();
}
#Override
public boolean isValid(String arg0, ConstraintValidatorContext arg1) {
// verify if the application type is A1 and length is 512
if ((awrRecObj.getApplicationType()
.equalsIgnoreCase(AWRConstant.AWR_W2))
&& (awrRecObj.getRecordData().toString().length() != AWRConstant.W2_RECORD_LEN)
&& (awrRecObj.getProgramSource()
.equalsIgnoreCase(AWRConstant.SOURCE_BATCH))) {
// create AWRRequestError Object and add it to AWRRecordObject
AWRUtility.createAWRRequestErrorObject(awrRecObj,
AWRErrorTypeCode.W2_RECORD_LENGTH_ERROR,
awrRecObj.getRecordData());
return false;
// ...
}
return true;
}
}
#Target({ METHOD, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
#Constraint(validatedBy = { ApplicationTypeValidator.class })
public #interface ValidateApplicationType {
String message() default "{com.myproject.validation.ApplicationType.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<AWRRecordObject> value();
}
The code reads a fixed length file and stores parsed values into a bean:
public static AWRRecordObject bufferedReader() throws IOException {
int lineCount = 0;
long time1 = System.currentTimeMillis();
File file = new File("D://TestData/test.txt");
BufferedReader br = new BufferedReader(new FileReader(file), 8192);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
String line;
while ((line = br.readLine()) != null) {
String type = line.length() > 2 ? line.substring(0, 2) : line;
AWRRecordObject record = new AWRRecordObject(line, type, lineCount);
Set<ConstraintViolation<AWRRecordObject>> constraintViolations = validator
.validate(record);
if (constraintViolations.size() > 0) {
Iterator it = constraintViolations.iterator();
while (it.hasNext()) {
ConstraintViolation<AWRRecordObject> cv = (ConstraintViolation<AWRRecordObject>) it
.next();
System.out.println(cv.getInvalidValue());
System.out.println(cv.getMessage());
}
}
return record;
}
}
Unfortunately, I am getting the following error message:
Exception in thread "main" javax.validation.UnexpectedTypeException: No validator could be found for type: java.lang.String
at org.hibernate.validator.engine.ConstraintTree.verifyResolveWasUnique(ConstraintTree.java:383)
Looks like there are two things you need to do here:
1 . You currently have your custom validation being applied to a field of your target bean. You need to move it to the class instead.
#ValidateApplicationType
public final class AWRRecordObject
2 . Your annotation definition needs to be changed, to allow the annotation to be applied to classes.
#Target({ TYPE, ANNOTATION_TYPE })
Related
I am a java beginner and I need help with testing this:
what I am trying to do: I need at least one of the fields with value to accept it.
How should test my implementation?
My classes:
#Constraint(validatedBy = BankOrBarPresenceValidador.class)
#Target({ TYPE, FIELD, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Documented
public #interface VerifyBankOrBarPresence {
String message() default "{constraints.VerifyBankOrBarPresence.message}";
Class <?> [] groups() default {};
Class <? extends Payload> [] payload() default {};
String barCodeInvoice();
String bankDestinationNumber();
}
Implementation:
public class BankOrBarPresenceValidador implements ConstraintValidator<VerifyBankOrBarPresence, Object> {
private String barCodeInvoice;
private String bankDestinationNumber;
#Override
public void initialize(VerifyBankOrBarPresence constraintAnnotation) {
barCodeInvoice = constraintAnnotation.barCodeInvoice();
bankDestinationNumber = constraintAnnotation.bankDestinationNumber();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
try{
boolean barcodeIsPresent = barCodeInvoice.length() > 0;
boolean bankDestinationNumberIsPresent = bankDestinationNumber.length() > 0;
return barcodeIsPresent || bankDestinationNumberIsPresent;
}
catch (final Exception e) {
throw new BusinessException("Failed validating if bar code or bank destination number are present", e);
}
}
}
MY DTO:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
#VerifyBankOrBarPresence(barCodeInvoice = "barCodeInvoice", bankDestinationNumber="bankDestinationNumber", message = "Bar Code Invoice or bank Destination Number code not be null")
public class VoucherDTO implements Serializable {
private String barCodeInvoice;
private String bankDestinationNumber;
}
Imagine there is a class:
#Something(someProperty = "some value")
public class Foobar {
//...
}
Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".
Is this possible? If so, how?
Warning: Not tested on OSX - see comment from #Marcel
Tested on OSX. Works fine.
Since I also had the need to change annotation values at runtime, I revisited this question.
Here is a modified version of #assylias approach (many thanks for the inspiration).
/**
* Changes the annotation value for the given key of the given annotation to newValue and returns
* the previous value.
*/
#SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
Object handler = Proxy.getInvocationHandler(annotation);
Field f;
try {
f = handler.getClass().getDeclaredField("memberValues");
} catch (NoSuchFieldException | SecurityException e) {
throw new IllegalStateException(e);
}
f.setAccessible(true);
Map<String, Object> memberValues;
try {
memberValues = (Map<String, Object>) f.get(handler);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
Object oldValue = memberValues.get(key);
if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
throw new IllegalArgumentException();
}
memberValues.put(key,newValue);
return oldValue;
}
Usage example:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ClassAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface FieldAnnotation {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MethodAnnotation {
String value() default "";
}
#ClassAnnotation("class test")
public static class TestClass{
#FieldAnnotation("field test")
public Object field;
#MethodAnnotation("method test")
public void method(){
}
}
public static void main(String[] args) throws Exception {
final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
System.out.println("old ClassAnnotation = " + classAnnotation.value());
changeAnnotationValue(classAnnotation, "value", "another class annotation value");
System.out.println("modified ClassAnnotation = " + classAnnotation.value());
Field field = TestClass.class.getField("field");
final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());
Method method = TestClass.class.getMethod("method");
final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
System.out.println("old MethodAnnotation = " + methodAnnotation.value());
changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}
The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.
Tested with Java 8.
This code does more or less what you ask for - it is a simple proof of concept:
a proper implementation needs to also deal with the declaredAnnotations
if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
I have no idea if there are side effects...
Output:
oldAnnotation = some value
modifiedAnnotation = another value
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Field field = Class.class.getDeclaredField("annotations");
field.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation #JsonIgnoreProperties(ignoreUnknown = true) from true to false.
final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());
final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
#Override public Class<? extends Annotation> annotationType() {
return matchedAnnotation.get(0).annotationType();
} #Override public String[] value() {
return new String[0];
} #Override public boolean ignoreUnknown() {
return false;
} #Override public boolean allowGetters() {
return false;
} #Override public boolean allowSetters() {
return false;
}
};
final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
SPRING can do this job very easily , might be useful for spring developer .
follow these steps :-
First Solution :-
1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with #Value annotation from DB or property file :-
#Value("${config.somePropertyValue}")
private String somePropertyValue;
#Bean
public String somePropertyValue(){
return somePropertyValue;
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{#somePropertyValue}")
public class Foobar {
//...
}
Second solution :-
1) create getter setter in bean :-
#Component
public class config{
#Value("${config.somePropertyValue}")
private String somePropertyValue;
public String getSomePropertyValue() {
return somePropertyValue;
}
public void setSomePropertyValue(String somePropertyValue) {
this.somePropertyValue = somePropertyValue;
}
}
2)After this , it is possible to inject the somePropertyValue into the #Something annotation like this :-
#Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
//...
}
Try this solution for Java 8
public static void main(String[] args) throws Exception {
final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
Annotation newAnnotation = new Something() {
#Override
public String someProperty() {
return "another value";
}
#Override
public Class<? extends Annotation> annotationType() {
return oldAnnotation.annotationType();
}
};
Method method = Class.class.getDeclaredMethod("annotationData", null);
method.setAccessible(true);
Object annotationData = method.invoke(getClass(), null);
Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
declaredAnnotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
annotations.put(Something.class, newAnnotation);
Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}
#Something(someProperty = "some value")
public static class Foobar {
}
#Retention(RetentionPolicy.RUNTIME)
#interface Something {
String someProperty();
}
i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,
try {
Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
annotationDataField.setAccessible(true);
Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
annotationsField.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?
Implementing a custom constraint annotation, like #MySize requires me testing it with unit tests to see if it functions correctly:
public class MySizeTest {
#Test
public void noMinMax() {
Dummy dummy = new Dummy();
// some asserts or so
dummy.setMyField("");
dummy.setMyField(null);
dummy.setMyField("My text");
}
#Test
public void onlyMin() {
// change #MySize to have min: #MySize(min = 1)
... how?
... then test with some setMyField:
Dummy dummy = new Dummy();
// some asserts or so
dummy.setMyField("");
dummy.setMyField(null);
dummy.setMyField("My text");
}
#Test
public void onlyMax() {
// change #MySize to have max: #MySize(max = 50)
...
}
#Test
public void bothMinMax() {
// change #MySize to have min and max: #MySize(min = 1, max = 50)
...
}
private class Dummy {
#MySize()
String myField;
public String getMyField() {
return myField;
}
public void setMyField(String myField) {
this.myField = myField;
}
}
}
I assume this has to be done with reflection, but I have no idea how.
Basicly don't have to use reflection just create a Validator instance and use that for validating.
For examaple:
When the annotation is:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyValidator.class)
public #interface MyAnnotation {
String message() default "Invalid value (it must be foo)";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
and the related validator is:
public class MyValidator implements ConstraintValidator<MyAnnotation, String> {
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (null == s) return true;
return "foo".equalsIgnoreCase(s);
}
}
Then the tests sould be like these:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyValidatorTest {
private Validator validator;
#BeforeAll
void init() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
private static class TestObject {
#MyAnnotation
private String testField;
TestObject() {
this(null);
}
TestObject(String value) {
testField = value;
}
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
}
#Test
void shouldValidForNullValue() {
var obj = new TestObject();
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size()));
}
#Test
void shouldValidForFooValue() {
var obj = new TestObject("foo");
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertTrue(violations.isEmpty(), String.format("Object should valid, but has %d violations", violations.size()));
}
#Test
void shouldInvalidForBarValue() {
var obj = new TestObject("bar");
var violations = validator.validate(obj); // Set<ConstraintViolation<TestObject>>
Assertions.assertEquals(1, violations.size());
}
}
Update (2020.05.21.) - Using attributes and AnnotationFactory
Based on comments I've updated my answer.
If you want to test only the validation logic then just create an Annotation instance and call the isValid method which is returns true or false
Hibernate Validator provides AnnotationFactory.create(...) method to make annotaion instance.
After that you can create an instance of your custom validator and call initialize and isValid methods in your test case.
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = MyHasAttributesValidator.class)
public #interface MyAnnotationHasAttributes {
String message() default "Invalid value (it must be foo)";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int attributeOne() default 10;
int attributeTwo() default 20;
}
related validator:
public class MyHasAttributesValidator implements ConstraintValidator<MyAnnotationHasAttributes, String> {
private MyAnnotationHasAttributes ann;
#Override
public void initialize(MyAnnotationHasAttributes constraintAnnotation) {
ann = constraintAnnotation;
}
#Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (null == s) return true;
return s.length() >= ann.attributeOne() && s.length() < ann.attributeTwo();
}
}
and the modified test (which has failing assertion):
public class HasAttributeValidatorTest {
private MyAnnotationHasAttributes createAnnotation(Integer one, Integer two) {
final Map<String, Object> attrs = new HashMap<>();
if (null != one) {
attrs.put("attributeOne", one);
}
if (null != two) {
attrs.put("attributeOne", two);
}
var desc = new AnnotationDescriptor.Builder<>(MyAnnotationHasAttributes.class, attrs).build();
return AnnotationFactory.create(desc);
}
#ParameterizedTest
#MethodSource("provideValues")
void testValidator(Integer one, Integer two, String input, boolean expected) {
MyAnnotationHasAttributes ann = createAnnotation(one, two);
MyHasAttributesValidator validator = new MyHasAttributesValidator();
validator.initialize(ann);
var result = validator.isValid(input, null);
Assertions.assertEquals(expected, result, String.format("Validation must be %s but found: %s with params: %d, %d, %s", expected, result, one, two, input));
}
private static Stream<Arguments> provideValues() {
return Stream.of(
Arguments.of(null, null, null, true),
Arguments.of(null, 20, "foo", true),
Arguments.of(null, null, RandomStringUtils.randomAlphabetic(30), false)
);
}
}
Limitations of this solution
Vendor lock
In this case your test using Hibernate Validator which is a specific implementation if the Bean Validation standards. Honestly I don't think it's a huge problem, because Hibernate Validator is the refecerence implementation and the most popular bean validation library. But technically it's a vendor lock.
Cross field validation is unavailable
This soulution works only in one-field situations. If you have e.g a cross-field validator (e.g. password and confirmPassword matching) this example won't fit.
Type independent validation needs more work
Like previously mentioned #Size annotation belongs to several different validator implementations based on type (primitives, collections, string, etc.).
Using this solution you always have to chose the certain validator manually and test it.
Only the isValid method can be tested
In this case you won't be able to test another things just the isValid method. I mean e.g. error message has expected format and parameters or something like this.
In sort, I know creating many different fields with different annotation attributes is boring but I strongly prefer that way because you can test everything you need about your validator.
I have a custom ConstraintValidator. What i want to do, is to add multiple constraint violations for the same property node, with the same message, and different dynamic payload. Is this possible?
Custom annotation:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = CustomFieldConstraintValidator.class)
public #interface CustomFieldConstraint {
String message() default "message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Custom constraint validator:
public class CustomFieldConstraintValidator implements ConstraintValidator<CustomFieldConstraint, Integer> {
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
var valid = true;
var hibernateConstraintValidatorContext = context.unwrap(HibernateConstraintValidatorContext.class);
hibernateConstraintValidatorContext.disableDefaultConstraintViolation();
if (value > 0) {
valid = false;
hibernateConstraintValidatorContext.withDynamicPayload(Map.of("must_be_less_than", 0))
.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("field")
.addConstraintViolation();
}
if (value > 5) {
valid = false;
hibernateConstraintValidatorContext.withDynamicPayload(Map.of("must_be_less_than", 5))
.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
.addPropertyNode("field")
.addConstraintViolation();
}
return valid;
}
}
Validator test:
public class HibernateValidatorTest {
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
#Test
void multipleFailuresForSameField() {
var foo = new Foo(10);
var constraintViolations = validator.validate(foo);
assertEquals(2, constraintViolations.size());
}
public static class Foo {
#CustomFieldConstraint
private int field;
public Foo(int field) {
this.field = field;
}
}
}
The test is failing with expected: <2> but was: <1>.
You have this issue because ConstraintViolation are returned as a set and, by design, the dynamic payload is not included in the equals() method. See https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/ConstraintViolationImpl.java#L251 .
Thus your 2 constraints are considered equal at the Set level.
You need to change the way you do things and enrich the payload with both information instead.
When writing custom constraints is it possible to have multiple annotations validated by one validator implementation. For example I have several annotations which stipulate different #size annotations but I want them all to point at the same validator class which does some global checking i.e. all must be match a certain regex. As far as I can see the implementation takes in one Annotation Type.
One annotation
#Target( { METHOD, FIELD, ANNOTATION_TYPE, TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = {UCNValidator.class})
#Documented
#Size(min = 9, max = 9, message = "{exactlength}")
public #interface UCN {
String message() default "{invalidFormat}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String fieldName() default "ucn";
}
The validator
public class UCNValidator implements ConstraintValidator<UCN, String>
{
private String pattern = "[a-zA-Z].*";
private String fieldName;
#Override
public void initialize( UCN constraintAnnotation )
{
this.fieldName = constraintAnnotation.fieldName();
}
#Override
public boolean isValid( String value, ConstraintValidatorContext constraintValidatorContext )
{
if ( value != null )
{
if ( !value.matches(pattern) )
{
//do some stuff
return false;
}
}
return true;
}
There doesn't seem to be a way to access other values from an object when validating one of its properties. The solution I use is to put the annotation on the class, then the validator will get the entire object in to validate against, and you can access just the info you need to perform the validation.
Here's one I wrote to compare two different properties of an object against each other:
#Target(TYPE)
#Retention(RUNTIME)
#Constraint(validatedBy = LessThanValidator.class)
#Documented
public #interface LessThan {
String message() default "{com.bullethq.constraints.LessThan}";
String bigValueProperty();
String littleValueProperty();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then the validator class is:
public class LessThanValidator implements ConstraintValidator<LessThan, Object> {
private LessThan constraint;
public void initialize(LessThan constraintAnnotation) {
constraint = constraintAnnotation;
}
public boolean isValid(Object object, ConstraintValidatorContext cvc) {
Object bigValue = getValue(object, constraint.bigValueProperty());
Object littleValue = getValue(object, constraint.littleValueProperty());
// If one of the values is null, then we do not perform validation.
if (bigValue == null || littleValue == null) {
return true;
}
if (bigValue instanceof Comparable && littleValue instanceof Comparable) {
boolean valid = ((Comparable<Object>) bigValue).compareTo(littleValue) > 0;
if (!valid) {
// If the values are not valid, then build a custom violations which has the correct path in it.
cvc.buildConstraintViolationWithTemplate(cvc.getDefaultConstraintMessageTemplate())
.addNode(constraint.littleValueProperty())
.addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
throw new IllegalArgumentException("Properties " + constraint.bigValueProperty() + " and " + constraint.littleValueProperty() + " both need to be comparable in " + object.getClass());
}
}
The getValue() method is just a static method using reflection to get the value from the object.