/*
* Copyright 2004-2005 the original author or authors.
*
* Licensed 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.springmodules.validation.bean.conf.loader.annotation;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springmodules.validation.bean.conf.BeanValidationConfiguration;
import org.springmodules.validation.bean.conf.DefaultBeanValidationConfiguration;
import org.springmodules.validation.bean.conf.MutableBeanValidationConfiguration;
import org.springmodules.validation.bean.conf.loader.BeanValidationConfigurationLoader;
import org.springmodules.validation.bean.conf.loader.annotation.handler.ClassValidationAnnotationHandler;
import org.springmodules.validation.bean.conf.loader.annotation.handler.PropertyValidationAnnotationHandler;
/**
* A {@link org.springmodules.validation.bean.conf.loader.BeanValidationConfigurationLoader} implementation that creates validation configuration based on
* validation rule extracted from class annoatations.
*
* @author Uri Boness
*/
public class AnnotationBeanValidationConfigurationLoader implements BeanValidationConfigurationLoader {
private final static Log logger = LogFactory.getLog(AnnotationBeanValidationConfigurationLoader.class);
private boolean checkValidatableAnnotation;
private Map<Class, BeanValidationConfiguration> configurationByClass;
private ValidationAnnotationHandlerRegistry handlerRegistry;
/**
* Constructs a new AnnotationBeanValidationConfigurationLoader. This loader supports all classes by default.
*/
public AnnotationBeanValidationConfigurationLoader() {
this(false);
}
/**
* Constructs a new AnnotationBeanValidationConfigurationLoader. if <code>checkValidatableAnnotation</code> is set
* to <code>true</code>, only classes annotated with the {@link @Validatable} annoation are supported.
*
* @param checkValidatableAnnotation Indicates whether this loader should only support {@link @Validatable}
* annotated classes.
*/
public AnnotationBeanValidationConfigurationLoader(boolean checkValidatableAnnotation) {
this.checkValidatableAnnotation = checkValidatableAnnotation;
configurationByClass = new HashMap<Class, BeanValidationConfiguration>();
handlerRegistry = new DefaultValidationAnnotationHandlerRegistry();
}
/**
* If {@link #isCheckValidatableAnnotation()} return <code>true</code>, then this loader supports the given
* class only if it has the {@link @Validatable} annotation. Otherwise, all classes are supported.
*
* @see BeanValidationConfigurationLoader#supports(Class)
*/
public boolean supports(Class clazz) {
return (checkValidatableAnnotation) ? clazz.isAnnotationPresent(Validatable.class) : true;
}
/**
* Loads the validation configuration for the given class based on validation annotations. The resolved configuration
* are cached after the first time they are being loaded.
*
* @see BeanValidationConfigurationLoader#loadConfiguration(Class)
*/
public BeanValidationConfiguration loadConfiguration(Class clazz) {
BeanValidationConfiguration configuration = configurationByClass.get(clazz);
if (configuration == null) {
configuration = createValidationConfiguration(clazz);
configurationByClass.put(clazz, configuration);
}
return configuration;
}
//=============================================== Setter/Getter ====================================================
/**
* Sets whether this loader should only support classes that are marked with the {@link @Validatable} annotation.
*
* @param checkValidatableAnnotation determine whether {@link @Validatable} should be considered.
*/
public void setCheckValidatableAnnotation(boolean checkValidatableAnnotation) {
this.checkValidatableAnnotation = checkValidatableAnnotation;
}
/**
* Returns whether this loader only supports classes that are marked with the {@link @Validatable} annotation.
*
* @return <code>true</code> if the {@link @Validatable} is considered, <code>false</code> otherwise.
*/
public boolean isCheckValidatableAnnotation() {
return checkValidatableAnnotation;
}
/**
* Sets the validation annotation handler registry this loader will use to find the proper handlers for the
* validation annotations.
*
* @param handlerRegistry The validation annotation registry to be used by this loader.
*/
public void setHandlerRegistry(ValidationAnnotationHandlerRegistry handlerRegistry) {
this.handlerRegistry = handlerRegistry;
}
//=============================================== Helper Methods ===================================================
/**
* Creates a new {@link BeanValidationConfiguration} for the given class based on the validation annotations it
* has.
*
* @param clazz The class to which the validation configuration should be created.
* @return The newly created {@link BeanValidationConfiguration}.
*/
protected BeanValidationConfiguration createValidationConfiguration(Class clazz) {
MutableBeanValidationConfiguration configuration = new DefaultBeanValidationConfiguration();
handleClassAnnotations(clazz, configuration);
handlePropertyAnnotations(clazz, configuration);
return configuration;
}
/**
* Handles all the class level annotation of the given class and manipulates the given validation configuration
* accordingly.
*
* @param clazz The annotated class.
* @param configuration The bean validation configuration to manipulate.
*/
protected void handleClassAnnotations(Class clazz, MutableBeanValidationConfiguration configuration) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
ClassValidationAnnotationHandler handler = handlerRegistry.findClassHanlder(annotation, clazz);
if (handler == null) {
logger.warn("No hanlder is defined for annotation '" + annotation.annotationType().getName() +
"'... Annotation will be ignored...");
} else {
handler.handleAnnotation(annotation, clazz, configuration);
}
}
}
/**
* Handles all the property level annotations of the given class and manipulates the given validation configuration
* accordingly. The property level annotations can either be placed on the <code>setter</code> methods of the
* properties or on the appropriate class fields.
*
* @param clazz The annotated class.
* @param configuration The bean validation configuration to mainpulate.
*/
protected void handlePropertyAnnotations(Class clazz, MutableBeanValidationConfiguration configuration) {
// extracting & handling all property annotation placed on property fields
List<Field> fields = extractFieldFromClassHierarchy(clazz);
for (Field field : fields) {
String fieldName = field.getName();
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(clazz, fieldName);
if (descriptor != null) {
Annotation[] annotations = field.getAnnotations();
handleProprtyAnnotations(annotations, clazz, descriptor, configuration);
}
}
// extracting & handling all property annotations placed on property setters
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(clazz);
for (PropertyDescriptor descriptor : descriptors) {
Method writeMethod = descriptor.getWriteMethod();
if (writeMethod == null) {
continue;
}
Annotation[] annotations = writeMethod.getAnnotations();
handleProprtyAnnotations(annotations, clazz, descriptor, configuration);
}
}
/**
* Extracts all fields in the class hierarchy of the given class.
*
* @param clazz The given class.
* @return All fields in the class hierarchy of the given class.
*/
protected List<Field> extractFieldFromClassHierarchy(Class clazz) {
List<Field> fields = new ArrayList<Field>();
while (clazz != null) {
CollectionUtils.addAll(fields, clazz.getDeclaredFields());
clazz = clazz.getSuperclass();
}
return fields;
}
/**
* Handles all the validation annotations of the given property.
*
* @param annotations The annotation to handle.
* @param validatedClass The annotated class.
* @param descriptor The property descriptor of the annotated property.
* @param configuration The bean validation configuration to manipulate.
*/
protected void handleProprtyAnnotations(Annotation[] annotations, Class validatedClass, PropertyDescriptor descriptor, MutableBeanValidationConfiguration configuration) {
for (Annotation annotation : annotations) {
PropertyValidationAnnotationHandler handler = handlerRegistry.findPropertyHanlder(annotation, validatedClass, descriptor);
if (handler != null) {
handler.handleAnnotation(annotation, validatedClass, descriptor, configuration);
}
}
}
}