Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.agimatec.validation.jsr303; import com.agimatec.validation.jsr303.groups.GroupsComputer; import com.agimatec.validation.jsr303.util.SecureActions; import com.agimatec.validation.util.AccessStrategy; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.validation.*; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.PrivilegedAction; import java.util.*; /** * Description: helper class that builds a {@link ConstraintValidation} or its * composite constraint validations by parsing the jsr303-annotations * and providing information (e.g. for @OverridesAttributes) <br/> * User: roman <br/> * Date: 28.10.2009 <br/> * Time: 13:53:13 <br/> * Copyright: Agimatec GmbH */ final class AnnotationConstraintBuilder { private static final Log log = LogFactory.getLog(AnnotationConstraintBuilder.class); private static final String ANNOTATION_PAYLOAD = "payload"; private static final String ANNOTATION_GROUPS = "groups"; private final ConstraintValidation<?> constraintValidation; private List<ConstraintOverrides> overrides; public AnnotationConstraintBuilder(Class<? extends ConstraintValidator<?, ?>>[] validatorClasses, ConstraintValidator constraintValidator, Annotation annotation, Class owner, AccessStrategy access) { boolean reportFromComposite = annotation != null && annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class); constraintValidation = new ConstraintValidation(validatorClasses, constraintValidator, annotation, owner, access, reportFromComposite); buildFromAnnotation(); } /** build attributes, payload, groups from 'annotation' */ private void buildFromAnnotation() { if (constraintValidation.getAnnotation() != null) { SecureActions.run(new PrivilegedAction<Object>() { public Object run() { for (Method method : constraintValidation.getAnnotation().annotationType() .getDeclaredMethods()) { // enhancement: clarify: should groups + payload also appear in attributes? if (method.getParameterTypes().length == 0) { try { if (ANNOTATION_PAYLOAD.equals(method.getName())) { buildPayload(method); } else if (ANNOTATION_GROUPS.equals(method.getName())) { buildGroups(method); } else { constraintValidation.getAttributes().put(method.getName(), method.invoke(constraintValidation.getAnnotation())); } } catch (Exception e) { // do nothing log.warn("error processing annotation: " + constraintValidation.getAnnotation(), e); } } } return null; } }); } try { /* spec: Invalid constraint definitions causes are multiple but include missing or illegal message or groups elements */ if (constraintValidation.getGroups() == null) { throw new ConstraintDefinitionException( constraintValidation.getAnnotation().annotationType().getName() + " does not contain a groups parameter."); } if (constraintValidation.getMessageTemplate() == null) { throw new ConstraintDefinitionException( constraintValidation.getAnnotation().annotationType().getName() + " does not contain a message parameter."); } if (constraintValidation.getPayload() == null) { throw new ConstraintDefinitionException( constraintValidation.getAnnotation().annotationType().getName() + " does not contain a payload parameter."); } } catch (ConstraintDefinitionException e) { throw e; } catch (Exception e) { throw new IllegalArgumentException(e); // execution never reaches this point } } private void buildGroups(Method method) throws IllegalAccessException, InvocationTargetException { Object raw = method.invoke(constraintValidation.getAnnotation()); Class<?>[] garr; if (raw instanceof Class) { garr = new Class<?>[] { (Class) raw }; } else if (raw instanceof Class<?>[]) { garr = (Class<?>[]) raw; } else { garr = null; } garr = (garr == null || garr.length == 0) ? GroupsComputer.DEFAULT_GROUP_ARRAY : garr; constraintValidation.setGroups(new HashSet(Arrays.asList(garr))); } private void buildPayload(Method method) throws IllegalAccessException, InvocationTargetException { Class<Payload>[] payload_raw = (Class<Payload>[]) method.invoke(constraintValidation.getAnnotation()); if (payload_raw == null) { constraintValidation.setPayload(Collections.<Class<? extends Payload>>emptySet()); } else { Set pl = new HashSet(payload_raw.length); pl.addAll(Arrays.asList(payload_raw)); constraintValidation.setPayload(pl); } } public ConstraintValidation getConstraintValidation() { return constraintValidation; } /** * initialize a child composite 'validation' with @OverridesAttribute * from 'constraintValidation' and add to composites. */ public void addComposed(ConstraintValidation composite) { applyOverridesAttributes(composite); constraintValidation.addComposed(composite); // add AFTER apply() } private void applyOverridesAttributes(ConstraintValidation composite) { if (null == overrides) buildOverridesAttributes(); if (!overrides.isEmpty()) { int index = computeIndex(composite); // assume composite not yet added! (+1) if (index < 0) { ConstraintOverrides override = // search for constraintIndex = -1 findOverride(composite.getAnnotation().annotationType(), -1); if (override != null) override.applyOn(composite); else override = // search for constraintIndex == 0 findOverride(composite.getAnnotation().annotationType(), 0); if (override != null) override.applyOn(composite); } else { // search for constraintIndex > 0 ConstraintOverrides override = findOverride(composite.getAnnotation().annotationType(), index + 1); if (override != null) override.applyOn(composite); } } } private int computeIndex(ConstraintValidation composite) { int idx = -1; for (ConstraintValidation<?> each : constraintValidation.getComposingValidations()) { if (each.getAnnotation().annotationType() == composite.getAnnotation().annotationType()) { idx++; } } return idx; } /** read overridesAttributes from constraintValidation.annotation */ private void buildOverridesAttributes() { overrides = new LinkedList(); for (Method method : constraintValidation.getAnnotation().annotationType().getDeclaredMethods()) { OverridesAttribute.List annoOAL = method.getAnnotation(OverridesAttribute.List.class); if (annoOAL != null) { for (OverridesAttribute annoOA : annoOAL.value()) { parseConstraintOverride(method.getName(), annoOA); } } OverridesAttribute annoOA = method.getAnnotation(OverridesAttribute.class); if (annoOA != null) { parseConstraintOverride(method.getName(), annoOA); } } } private void parseConstraintOverride(String methodName, OverridesAttribute oa) { ConstraintOverrides target = findOverride(oa.constraint(), oa.constraintIndex()); if (target == null) { target = new ConstraintOverrides(oa.constraint(), oa.constraintIndex()); overrides.add(target); } target.values.put(oa.name(), constraintValidation.getAttributes().get(methodName)); } private ConstraintOverrides findOverride(Class<? extends Annotation> constraint, int constraintIndex) { for (ConstraintOverrides each : overrides) { if (each.constraintType == constraint && each.constraintIndex == constraintIndex) { return each; } } return null; } /** * Holds the values to override in a composed constraint * during creation of a composed ConstraintValidation */ private static final class ConstraintOverrides { final Class<? extends Annotation> constraintType; final int constraintIndex; /** key = attributeName, value = overridden value */ final Map<String, Object> values; private ConstraintOverrides(Class<? extends Annotation> constraintType, int constraintIndex) { this.constraintType = constraintType; this.constraintIndex = constraintIndex; values = new HashMap(); } public void applyOn(ConstraintValidation composite) { composite.getAttributes().putAll(values); } } }