blob: d6bf24d6a78e25d18d459c9d4a8c3a4848d0e3ed [file] [log] [blame]
/*
* 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();
}
}
}