package de.zalando.sprocwrapper.validation; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.validation.ConstraintValidator; import javax.validation.Payload; import javax.validation.ReportAsSingleViolation; import javax.validation.ValidationException; import javax.validation.metadata.ConstraintDescriptor; public class SimpleConstraintDescriptor<T extends Annotation> implements ConstraintDescriptor<T> { private static final String PAYLOAD = "payload"; private T annotation; private Set<Class<?>> groups; private Set<Class<? extends Payload>> payload; private List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses; private Map<String, Object> attributes; private Set<ConstraintDescriptor<?>> composingConstraints; private boolean reportAsSingleViolation; public SimpleConstraintDescriptor(final T annotation, final Set<Class<?>> groups, final List<Class<? extends ConstraintValidator<T, ?>>> constraintValidatorClasses, final Set<ConstraintDescriptor<?>> composingConstraints) { this.annotation = annotation; this.groups = groups; this.payload = buildPayloadSet(annotation); this.constraintValidatorClasses = constraintValidatorClasses; this.attributes = buildAnnotationParameterMap(annotation); this.composingConstraints = composingConstraints; this.reportAsSingleViolation = ((Class<T>) this.annotation.annotationType()).isAnnotationPresent( ReportAsSingleViolation.class); } @Override public T getAnnotation() { return annotation; } @Override public Set<Class<?>> getGroups() { return groups; } @Override public Set<Class<? extends Payload>> getPayload() { return payload; } @Override public List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses() { return constraintValidatorClasses; } @Override public Map<String, Object> getAttributes() { return attributes; } @Override public Set<ConstraintDescriptor<?>> getComposingConstraints() { return composingConstraints; } @Override public boolean isReportAsSingleViolation() { return reportAsSingleViolation; } private Map<String, Object> buildAnnotationParameterMap(final Annotation annotation) { final Method[] declaredMethods = annotation.annotationType().getDeclaredMethods(); Map<String, Object> parameters = new HashMap<String, Object>(declaredMethods.length); for (Method m : declaredMethods) { try { parameters.put(m.getName(), m.invoke(annotation)); } catch (IllegalAccessException e) { throw new ValidationException("Unable to read annotation attributes: " + annotation.getClass(), e); } catch (InvocationTargetException e) { throw new ValidationException("Unable to read annotation attributes: " + annotation.getClass(), e); } } return Collections.unmodifiableMap(parameters); } private Set<Class<? extends Payload>> buildPayloadSet(final T annotation) { Set<Class<? extends Payload>> payloadSet = new HashSet<Class<? extends Payload>>(); Class<Payload>[] payloadFromAnnotation; try { payloadFromAnnotation = getAnnotationParameter(annotation, PAYLOAD, Class[].class); } catch (ValidationException e) { payloadFromAnnotation = null; } if (payloadFromAnnotation != null) { payloadSet.addAll(Arrays.asList(payloadFromAnnotation)); } return Collections.unmodifiableSet(payloadSet); } private <P> P getAnnotationParameter(final Annotation annotation, final String parameterName, final Class<P> type) { try { Method m = annotation.getClass().getMethod(parameterName); m.setAccessible(true); Object o = m.invoke(annotation); if (o.getClass().getName().equals(type.getName())) { return (P) o; } else { String msg = "Wrong parameter type. Expected: " + type.getName() + " Actual: " + o .getClass().getName(); throw new ValidationException(msg); } } catch (NoSuchMethodException e) { String msg = "The specified annotation defines no parameter '" + parameterName + "'."; throw new ValidationException(msg, e); } catch (IllegalAccessException e) { String msg = "Unable to get '" + parameterName + "' from " + annotation.getClass().getName(); throw new ValidationException(msg, e); } catch (InvocationTargetException e) { String msg = "Unable to get '" + parameterName + "' from " + annotation.getClass().getName(); throw new ValidationException(msg, e); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("SimpleConstraintDescriptor [annotation="); builder.append(annotation); builder.append(", groups="); builder.append(groups); builder.append(", payload="); builder.append(payload); builder.append(", constraintValidatorClasses="); builder.append(constraintValidatorClasses); builder.append(", attributes="); builder.append(attributes); builder.append(", composingConstraints="); builder.append(composingConstraints); builder.append(", reportAsSingleViolation="); builder.append(reportAsSingleViolation); builder.append("]"); return builder.toString(); } }