Java tutorial
/* * 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.handler; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import org.springframework.util.StringUtils; import org.springmodules.validation.bean.conf.MutableBeanValidationConfiguration; import org.springmodules.validation.bean.rule.AbstractValidationRule; import org.springmodules.validation.bean.rule.PropertyValidationRule; import org.springmodules.validation.bean.rule.resolver.ErrorArgumentsResolver; import org.springmodules.validation.bean.rule.resolver.FunctionErrorArgumentsResolver; import org.springmodules.validation.util.cel.ConditionExpressionBased; import org.springmodules.validation.util.cel.ConditionExpressionParser; import org.springmodules.validation.util.cel.valang.ValangConditionExpressionParser; import org.springmodules.validation.util.condition.Condition; import org.springmodules.validation.util.condition.common.AlwaysTrueCondition; import org.springmodules.validation.util.fel.FunctionExpressionBased; import org.springmodules.validation.util.fel.FunctionExpressionParser; import org.springmodules.validation.util.fel.parser.ValangFunctionExpressionParser; /** * A parent for all common {@link PropertyValidationAnnotationHandler} implementations that represent validation rules. * The following attributes are searched for in the handled annotations: * <ul> * <li>{@link #ERROR_CODE_ATTR} - A string indicating the error code for the validation rule</li> * <li>{@link #MESSAGE_ATTR} - A string indicating default error message for the validation rule</li> * <li>{@link #ARGS_ATTR} - A comma-separated string indicating error arguments (may be expressions to be * resolved against the validated object)</li> * <li>{@link #APPLY_IF_ATTR} - A condition expression that represents the applicability condition</li> * </ul> * * @author Uri Boness */ public abstract class AbstractPropertyValidationAnnotationHandler implements PropertyValidationAnnotationHandler, ConditionExpressionBased, FunctionExpressionBased { public final static String APPLY_IF_ATTR = "applyIf"; public final static String ERROR_CODE_ATTR = "errorCode"; public final static String MESSAGE_ATTR = "message"; public final static String ARGS_ATTR = "args"; public final static String CONTEXTS_ATTR = "contexts"; private Class[] supportedAnnotationTypes; private ConditionExpressionParser conditionExpressionParser; private FunctionExpressionParser functionExpressionParser; /** * Constructs a new AbstractPropertyValidationAnnotationHandler with a given supported annotation types. * * @param supportedAnnotationTypes The types of the supported annotations. */ public AbstractPropertyValidationAnnotationHandler(Class... supportedAnnotationTypes) { this.supportedAnnotationTypes = supportedAnnotationTypes; conditionExpressionParser = new ValangConditionExpressionParser(); functionExpressionParser = new ValangFunctionExpressionParser(); } /** * @see PropertyValidationAnnotationHandler#supports(java.lang.annotation.Annotation, Class, java.beans.PropertyDescriptor) */ public boolean supports(Annotation annotation, Class clazz, PropertyDescriptor descriptor) { for (Class supportedType : supportedAnnotationTypes) { if (supportedType.isInstance(annotation)) { return true; } } return false; } /** * Creates a validation rule out of the given property validation annoation and adds it to the given * bean validation configuration. * * @see PropertyValidationAnnotationHandler#handleAnnotation(java.lang.annotation.Annotation, Class, java.beans.PropertyDescriptor, org.springmodules.validation.bean.conf.MutableBeanValidationConfiguration) */ public void handleAnnotation(Annotation annotation, Class clazz, PropertyDescriptor descriptor, MutableBeanValidationConfiguration configuration) { AbstractValidationRule rule = createValidationRule(annotation, clazz, descriptor.getName()); String errorCode = extractErrorCode(annotation); if (errorCode != null) { rule.setErrorCode(errorCode); } String message = extractDefaultMessage(annotation); if (message != null) { rule.setDefaultErrorMessage(message); } ErrorArgumentsResolver argumentsResolver = extractArgumentsResolver(annotation); if (argumentsResolver != null) { rule.setErrorArgumentsResolver(argumentsResolver); } Condition applicabilityCondition = extractApplicabilityContidion(annotation); if (applicabilityCondition != null) { rule.setApplicabilityCondition(applicabilityCondition); } String[] applicableContexts = extractApplicableContexts(annotation); if (applicableContexts != null) { rule.setContextTokens(applicableContexts); } if (isConditionGloballyScoped(annotation)) { configuration.addPropertyRule(descriptor.getName(), rule); } else { PropertyValidationRule propertyRule = new PropertyValidationRule(descriptor.getName(), rule); // By definition, the applicability condition should be evaluated on the validated bean and not on the // validated bean property. Thus we need to explicitely set the applicability condition on the validation // rule otherwise the default applicability condition to be evaluated on the property value. if (applicabilityCondition != null) { propertyRule.setApplicabilityCondition(applicabilityCondition); } configuration.addPropertyRule(descriptor.getName(), propertyRule); } } /** * Extracts the validation rule error code from the given annotation. Expects a {@link #ERROR_CODE_ATTR} to be * present. * * @param annotation The property validation annotation. * @return The error code for the represented validation rule. */ protected String extractErrorCode(Annotation annotation) { return (String) extractAnnotationAttribute(annotation, ERROR_CODE_ATTR); } /** * Extracts the validation rule default message from the given annotation. Expects a {@link #MESSAGE_ATTR} to be * present. * * @param annotation The property validation annotation. * @return The default message for the represented validation rule. */ protected String extractDefaultMessage(Annotation annotation) { return (String) extractAnnotationAttribute(annotation, MESSAGE_ATTR); } /** * Extracts the {@link org.springmodules.validation.bean.rule.resolver.ErrorArgumentsResolver} for the validation rule represented by the given annotation. * Expects a {@link #ARGS_ATTR} to be present. * * @param annotation The property validation annotation. * @return The {@link org.springmodules.validation.bean.rule.resolver.ErrorArgumentsResolver} for the represented validation rule. */ protected ErrorArgumentsResolver extractArgumentsResolver(Annotation annotation) { String argsAsString = (String) extractAnnotationAttribute(annotation, ARGS_ATTR); argsAsString = (argsAsString == null) ? "" : argsAsString; String[] argsExpressions = StringUtils.commaDelimitedListToStringArray(argsAsString); if (argsExpressions.length == 0) { return null; } return new FunctionErrorArgumentsResolver(argsExpressions, functionExpressionParser); } /** * Extracts the validation rule applicability condition from the given annotation. Expects a {@link #APPLY_IF_ATTR} * to be present - the value of this attribute is parsed using the {@link ConditionExpressionParser} associated with this * handler. If the attribute is an empty string or <code>null</code> it is treated as if the validation rule is * always applicable (that is, the applicability condition is {@link AlwaysTrueCondition}). * * @param annotation The property validation annotation. * @return The applicability condition for the represented validation rule. */ protected Condition extractApplicabilityContidion(Annotation annotation) { String expression = (String) extractAnnotationAttribute(annotation, APPLY_IF_ATTR); return (StringUtils.hasText(expression)) ? conditionExpressionParser.parse(expression) : null; } /** * Extracts the names of the validation context in which the valiation rule is applicable. Expects a "contexts" * attribute to hold an array of context names. If it holds an empty array, <code>null </code> is returned. * As the contract of {@link AbstractValidationRule#setContextTokens(String[])} * defines <code>null</code> means that the rule always applies regardless of the context. * * @param annotation The validation rule annoation. * @return The names of the validation contexts in which the */ protected String[] extractApplicableContexts(Annotation annotation) { String[] contexts = (String[]) extractAnnotationAttribute(annotation, CONTEXTS_ATTR); return (contexts.length > 0) ? contexts : null; } /** * Returns whether the extracted condition is globally scoped. A globally scoped condition means that the condition * should be evaluated against the property's ower bean rather then the property value. By default <code>false</code> * is returned (that is, by default the extracted conditions are <b>NOT</b> globally scoped.<br/> * <p/> * This method can be overriden by sub-classes that create globally scoped conditions. * * @return <code>true</code> if the extracted condition is globally scoped, <code>false</code> otherwise. */ protected boolean isConditionGloballyScoped(Annotation annotation) { return false; } /** * Creates and returns the validation rule that is represented and initialized by and with the given annotation. * * @param annotation The annotation from which the validation rule should be created. * @param clazz The class of the valiated object. * @param propertyName The name of the validated property within the validated class. * @return The created validation rule. */ protected abstract AbstractValidationRule createValidationRule(Annotation annotation, Class clazz, String propertyName); //=============================================== Setter/Getter ==================================================== /** * @see ConditionExpressionBased#setConditionExpressionParser(org.springmodules.validation.util.cel.ConditionExpressionParser) */ public void setConditionExpressionParser(ConditionExpressionParser conditionExpressionParser) { this.conditionExpressionParser = conditionExpressionParser; } protected ConditionExpressionParser getConditionExpressionParser() { return conditionExpressionParser; } /** * @see FunctionExpressionBased#setFunctionExpressionParser(org.springmodules.validation.util.fel.FunctionExpressionParser) */ public void setFunctionExpressionParser(FunctionExpressionParser functionExpressionParser) { this.functionExpressionParser = functionExpressionParser; } protected FunctionExpressionParser getFunctionExpressionParser() { return functionExpressionParser; } //================================================== Helper Methods ================================================ protected Object extractAnnotationAttribute(Annotation annotation, String attributeName) { try { return annotation.getClass().getMethod(attributeName).invoke(annotation); } catch (Exception e) { throw new IllegalArgumentException("Expecting attribute '" + attributeName + "' in annotation '" + annotation.getClass().getName() + "'", e); } } }