| /* |
| * 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 org.apache.bval.jsr303; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.validation.ConstraintDeclarationException; |
| import javax.validation.ConstraintValidator; |
| import javax.validation.OverridesAttribute; |
| import javax.validation.Payload; |
| import javax.validation.ReportAsSingleViolation; |
| |
| import org.apache.bval.jsr303.groups.GroupsComputer; |
| import org.apache.bval.jsr303.xml.AnnotationProxyBuilder; |
| import org.apache.bval.util.AccessStrategy; |
| |
| /** |
| * 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/> |
| */ |
| final class AnnotationConstraintBuilder<A extends Annotation> { |
| private static final Logger log = Logger.getLogger(AnnotationConstraintBuilder.class.getName()); |
| |
| private final ConstraintValidation<?> constraintValidation; |
| private List<ConstraintOverrides> overrides; |
| |
| /** |
| * Create a new AnnotationConstraintBuilder instance. |
| * |
| * @param validatorClasses |
| * @param constraintValidator |
| * @param annotation |
| * @param owner |
| * @param access |
| */ |
| public AnnotationConstraintBuilder(Class<? extends ConstraintValidator<A, ?>>[] validatorClasses, |
| ConstraintValidator<A, ?> constraintValidator, A annotation, Class<?> owner, AccessStrategy access) { |
| boolean reportFromComposite = |
| annotation != null && annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class); |
| constraintValidation = |
| new ConstraintValidation<A>(validatorClasses, constraintValidator, annotation, owner, access, |
| reportFromComposite); |
| buildFromAnnotation(); |
| } |
| |
| /** build attributes, payload, groups from 'annotation' */ |
| private void buildFromAnnotation() { |
| if (constraintValidation.getAnnotation() != null) { |
| run(new PrivilegedAction<Object>() { |
| public Object run() { |
| for (Method method : constraintValidation.getAnnotation().annotationType().getDeclaredMethods()) { |
| // groups + payload must also appear in attributes (also |
| // checked by TCK-Tests) |
| if (method.getParameterTypes().length == 0) { |
| try { |
| if (ConstraintAnnotationAttributes.PAYLOAD.getAttributeName().equals(method.getName())) { |
| buildPayload(method); |
| } else if (ConstraintAnnotationAttributes.GROUPS.getAttributeName().equals( |
| method.getName())) { |
| buildGroups(method); |
| } else { |
| constraintValidation.getAttributes().put(method.getName(), |
| method.invoke(constraintValidation.getAnnotation())); |
| } |
| } catch (Exception e) { // do nothing |
| log.log(Level.WARNING, String.format("Error processing annotation: %s ", constraintValidation.getAnnotation()), e); |
| } |
| } |
| } |
| return null; |
| } |
| }); |
| } |
| } |
| |
| 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; |
| } |
| |
| if (garr == null || garr.length == 0) { |
| garr = GroupsComputer.getDefaultGroupArray(); |
| } |
| constraintValidation.setGroups(new HashSet<Class<?>>(Arrays.asList(garr))); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void buildPayload(Method method) throws IllegalAccessException, InvocationTargetException { |
| Class<? extends Payload>[] payload_raw = |
| (Class<? extends Payload>[]) method.invoke(constraintValidation.getAnnotation()); |
| Set<Class<? extends Payload>> payloadSet; |
| if (payload_raw == null) { |
| payloadSet = Collections.<Class<? extends Payload>> emptySet(); |
| } else { |
| payloadSet = new HashSet<Class<? extends Payload>>(payload_raw.length); |
| payloadSet.addAll(Arrays.asList(payload_raw)); |
| } |
| constraintValidation.setPayload(payloadSet); |
| } |
| |
| /** |
| * Get the configured {@link ConstraintValidation}. |
| * |
| * @return {@link ConstraintValidation} |
| */ |
| 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); |
| |
| // Search for the overrides to apply |
| ConstraintOverrides generalOverride = findOverride(composite.getAnnotation().annotationType(), -1); |
| if (generalOverride != null) { |
| if (index > 0) { |
| throw new ConstraintDeclarationException("Wrong OverridesAttribute declaration for " |
| + generalOverride.constraintType |
| + ", it needs a defined index when there is a list of constraints"); |
| } |
| generalOverride.applyOn(composite); |
| } |
| |
| ConstraintOverrides override = findOverride(composite.getAnnotation().annotationType(), index); |
| if (override != null) { |
| override.applyOn(composite); |
| } |
| |
| } |
| } |
| |
| /** |
| * Calculates the index of the composite constraint. The index represents |
| * the order in which it is added in reference to other constraints of the |
| * same type. |
| * |
| * @param composite |
| * The composite constraint (not yet added). |
| * @return An integer index always >= 0 |
| */ |
| private int computeIndex(ConstraintValidation<?> composite) { |
| int idx = 0; |
| 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<ConstraintOverrides>(); |
| 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<String, Object>(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private void applyOn(ConstraintValidation<?> composite) { |
| // Update the attributes |
| composite.getAttributes().putAll(values); |
| |
| // And the annotation |
| Annotation originalAnnot = composite.getAnnotation(); |
| AnnotationProxyBuilder<Annotation> apb = new AnnotationProxyBuilder<Annotation>(originalAnnot); |
| for (String key : values.keySet()) { |
| apb.putValue(key, values.get(key)); |
| } |
| Annotation newAnnot = apb.createAnnotation(); |
| ((ConstraintValidation<Annotation>) composite).setAnnotation(newAnnot); |
| } |
| } |
| |
| private static <T> T run(PrivilegedAction<T> action) { |
| if (System.getSecurityManager() != null) { |
| return AccessController.doPrivileged(action); |
| } else { |
| return action.run(); |
| } |
| } |
| } |