@Test void testAddClassChecksWithCache() throws Exception { ValidationContext context = spy(new ValidationContext()); try { context.addClassChecks(ClassForCheck1.class); context.addClassChecks(ClassForCheck1.class); } finally { context.clearCache(); } //collectProperty must be invoked only for first call of addClassChecks. Two calls for two annotations verify(context, times(2)).collectPropertyAnnotationChecks(any(List.class), any(PropertyDescriptor.class)); }
/** * Adds all checks from provided list. */ public void addAll(final List<Check> checkList) { for (Check check : checkList) { add(check); } }
@Test void testCollectPropertyAnnotationChecks(){ ValidationContext context = spy(new ValidationContext()); PropertyDescriptor propertyDescriptor = mock(PropertyDescriptor.class); List<Check> list = new ArrayList<>(); context.collectPropertyAnnotationChecks(list, propertyDescriptor); verify(context, never()).collectAnnotationChecks(any(List.class), any(Class.class),any(String.class), any(Annotation[].class)); }
/** * Resolve validation context for provided target class. * @see #addClassChecks(Class) */ public static ValidationContext resolveFor(final Class<?> target) { ValidationContext vc = new ValidationContext(); vc.addClassChecks(target); return vc; }
/** * Parses class annotations and adds all checks. * @see #resolveFor(Class) */ public void addClassChecks(final Class target) { final List<Check> list = cache.get(target, () -> { final List<Check> newList = new ArrayList<>(); final ClassDescriptor cd = ClassIntrospector.get().lookup(target); final PropertyDescriptor[] allProperties = cd.getAllPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : allProperties) { collectPropertyAnnotationChecks(newList, propertyDescriptor); } return newList; }); addAll(list); }
@Test void testManualConfig() { ValidationContext vctx = new ValidationContext(); vctx.add(new Check("string", new MinLengthConstraint(2))); Vtor vtor = new Vtor(); vtor.validate(vctx, new Foo()); assertTrue(vtor.hasViolations()); List<Violation> vlist = vtor.getViolations(); assertFalse(vlist.isEmpty()); assertEquals(1, vlist.size()); Violation v = vlist.get(0); assertEquals("string", v.getName()); assertEquals("1", v.getInvalidValue()); assertEquals("string", v.getCheck().getName()); assertNull(v.getCheck().getMessage()); assertEquals(MinLengthConstraint.class, v.getConstraint().getClass()); // new context that contains previous ValidationContext vctx2 = new ValidationContext(); vctx2.add(new Check("foo", new AssertValidConstraint(vctx))); vtor = new Vtor(); vtor.validate(vctx2, new Boo()); vlist = vtor.getViolations(); assertFalse(vlist.isEmpty()); assertEquals(1, vlist.size()); v = vlist.get(0); assertEquals("foo.string", v.getName()); assertEquals("1", v.getInvalidValue()); assertEquals("string", v.getCheck().getName()); assertEquals(MinLengthConstraint.class, v.getConstraint().getClass()); }
/** * Create new constraint. The following rules are used: * <ul> * <li>use default constructor if exist.</li> * <li>otherwise, use constructor with ValidationContext parameter.</li> * </ul> */ protected <V extends ValidationConstraint> V newConstraint(final Class<V> constraint, final Class targetType) throws Exception { Constructor<V> ctor; try { ctor = constraint.getConstructor(); return ctor.newInstance(); } catch (NoSuchMethodException ignore) { ctor = constraint.getConstructor(ValidationContext.class); return ctor.newInstance(resolveFor(targetType)); } }
vc = newConstraint(constraintClass, targetType); } catch (Exception ex) { throw new VtorException("Invalid constraint: " + constraintClass.getClass().getName(), ex); copyDefaultCheckProperties(check, annotation); annChecks.add(check);
/** * Process all annotations of provided properties. */ protected void collectPropertyAnnotationChecks(final List<Check> annChecks, final PropertyDescriptor propertyDescriptor) { FieldDescriptor fd = propertyDescriptor.getFieldDescriptor(); if (fd != null) { Annotation[] annotations = fd.getField().getAnnotations(); collectAnnotationChecks(annChecks, propertyDescriptor.getType(), propertyDescriptor.getName(), annotations); } MethodDescriptor md = propertyDescriptor.getReadMethodDescriptor(); if (md != null) { Annotation[] annotations = md.getMethod().getAnnotations(); collectAnnotationChecks(annChecks, propertyDescriptor.getType(), propertyDescriptor.getName(), annotations); } md = propertyDescriptor.getWriteMethodDescriptor(); if (md != null) { Annotation[] annotations = md.getMethod().getAnnotations(); collectAnnotationChecks(annChecks, propertyDescriptor.getType(), propertyDescriptor.getName(), annotations); } }
protected ValidationContext mockValidationContext(Map<String, List<Check>> constraints) { ValidationContext res = new ValidationContext(); res.map.putAll(constraints); return res; } }
@Test void testAddClassThrowVtorException() throws Exception { ValidationContext context = new ValidationContext() { @Override protected <V extends ValidationConstraint> V newConstraint(Class<V> constraint, Class targetType) throws Exception { throw new RuntimeException("terrible error"); } }; assertThrows(VtorException.class, () -> context.addClassChecks(ClassForCheck1.class)); }
/** * Parses class annotations and adds all checks. * @see #resolveFor(Class) */ public void addClassChecks(Class target) { List<Check> list = cache.get(target); if (list == null) { list = new ArrayList<Check>(); ClassDescriptor cd = ClassIntrospector.lookup(target); Field[] fields = cd.getAllFields(true); for (Field field : fields) { collectFieldAnnotationChecks(list, field); } cache.put(target, list); } addAll(list); }
/** * Resolve validation context for provided target class. * @see #addClassChecks(Class) */ public static ValidationContext resolveFor(Class<?> target) { ValidationContext vc = new ValidationContext(); vc.addClassChecks(target); return vc; }
@Test void testManualAddViolation() { ValidationContext vctx = new ValidationContext(); vctx.add(new Check("string", new MinLengthConstraint(2))); Vtor vtor = new Vtor(); Foo foo = new Foo(); vtor.validate(vctx, foo); vtor.addViolation(new Violation("number", foo, null)); List<Violation> vlist = vtor.getViolations(); assertFalse(vlist.isEmpty()); assertEquals(2, vlist.size()); Violation v = vlist.get(0); assertEquals("string", v.getName()); assertEquals("1", v.getInvalidValue()); assertEquals("string", v.getCheck().getName()); assertEquals(MinLengthConstraint.class, v.getConstraint().getClass()); v = vlist.get(1); assertEquals("number", v.getName()); assertNull(v.getInvalidValue()); assertNull(v.getCheck()); assertNull(v.getConstraint()); } }
/** * Validate object using context from the annotations. */ public List<Violation> validate(final Object target) { return validate(ValidationContext.resolveFor(target.getClass()), target); }
/** * Collect annotations for some target. */ @SuppressWarnings({"unchecked"}) protected void collectAnnotationChecks(List<Check> annChecks, Class targetType, String targetName, Annotation[] annotations) { for (Annotation annotation : annotations) { Constraint c = annotation.annotationType().getAnnotation(Constraint.class); if (c == null) { continue; } Class<? extends ValidationConstraint> constraintClass = c.value(); ValidationConstraint vc; try { vc = newConstraint(constraintClass, targetType); } catch (Exception ex) { throw new VtorException("Unable to create constraint: " + constraintClass.getClass().getName(), ex); } vc.configure(annotation); Check check = new Check(targetName, vc); copyDefaultCheckProperties(check, annotation); annChecks.add(check); } }
/** * Process all annotations of provided field. */ protected void collectFieldAnnotationChecks(List<Check> annChecks, Field field) { Annotation[] annotations = field.getAnnotations(); if (annotations.length > 0) { collectAnnotationChecks(annChecks, field.getType(), field.getName(), annotations); } }
@Test void testResolveFor() throws Exception { //when //Class ForCheck1 contains two fields. field1 has two constraints. Second field2 has a non constraint annotation ValidationContext context = ValidationContext.resolveFor(ClassForCheck1.class); //then assertEquals(context.map.size(), 1, "context must return a map with one element when resolve an object which has one field with constraint annotations"); assertNotNull(context.map.get("field1"), "context must return a map with key field1 when resolve an object which has field with name field1 and constraint annotations"); assertNull(context.map.get("field2"), "context must not return a map with key field2 when resolve an object which has a field with name field2 without constraint annotations"); assertEquals(context.map.get("field1").size(), 2, "context must return a map with two checks when resolve an object which has a field with two constraint annotations"); }
/** * Adds all checks from provided list. */ public void addAll(List<Check> checkList) { for (Check check : checkList) { add(check); } }
/** * Create new constraint. The following rules are used: * <li>use default constructor if exist. * <li>otherwise, use constructor with ValidationContext parameter. */ protected <V extends ValidationConstraint> V newConstraint(Class<V> constraint, Class targetType) throws Exception { Constructor<V> ctor; try { ctor = constraint.getConstructor(); return ctor.newInstance(); } catch (NoSuchMethodException ignore) { ctor = constraint.getConstructor(ValidationContext.class); return ctor.newInstance(resolveFor(targetType)); } }