Revision: 662
          http://stripes.svn.sourceforge.net/stripes/?rev=662&view=rev
Author:   bengunter
Date:     2007-12-11 09:22:44 -0800 (Tue, 11 Dec 2007)

Log Message:
-----------
Fixed STS-260:  Expose validation metadata via a public interface. Validation 
metadata is now globally accessible through the ValidationMetadataProvider 
interface, an instance of which can be obtained from Configuration.

Modified Paths:
--------------
    trunk/stripes/src/net/sourceforge/stripes/config/Configuration.java
    trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
    trunk/stripes/src/net/sourceforge/stripes/config/RuntimeConfiguration.java
    
trunk/stripes/src/net/sourceforge/stripes/controller/BindingPolicyManager.java
    
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java

Added Paths:
-----------
    
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java
    
trunk/stripes/src/net/sourceforge/stripes/validation/ValidationMetadataProvider.java

Modified: trunk/stripes/src/net/sourceforge/stripes/config/Configuration.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/config/Configuration.java 
2007-12-11 06:23:42 UTC (rev 661)
+++ trunk/stripes/src/net/sourceforge/stripes/config/Configuration.java 
2007-12-11 17:22:44 UTC (rev 662)
@@ -20,6 +20,7 @@
 import net.sourceforge.stripes.localization.LocalizationBundleFactory;
 import net.sourceforge.stripes.localization.LocalePicker;
 import net.sourceforge.stripes.validation.TypeConverterFactory;
+import net.sourceforge.stripes.validation.ValidationMetadataProvider;
 import net.sourceforge.stripes.tag.TagErrorRendererFactory;
 import net.sourceforge.stripes.tag.PopulationStrategy;
 import net.sourceforge.stripes.format.FormatterFactory;
@@ -191,4 +192,13 @@
      * @return MultipartWrapperFactory an instance of the wrapper factory
      */
     MultipartWrapperFactory getMultipartWrapperFactory();
+
+    /**
+     * Returns an instance of [EMAIL PROTECTED] ValidationMetadataProvider} 
that can be used by Stripes to
+     * determine what validations need to be applied during
+     * [EMAIL PROTECTED] LifecycleStage#BindingAndValidation}.
+     * 
+     * @return an instance of [EMAIL PROTECTED] ValidationMetadataProvider}
+     */
+    ValidationMetadataProvider getValidationMetadataProvider();
 }

Modified: 
trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java  
2007-12-11 06:23:42 UTC (rev 661)
+++ trunk/stripes/src/net/sourceforge/stripes/config/DefaultConfiguration.java  
2007-12-11 17:22:44 UTC (rev 662)
@@ -50,7 +50,9 @@
 import net.sourceforge.stripes.tag.TagErrorRendererFactory;
 import net.sourceforge.stripes.util.Log;
 import net.sourceforge.stripes.validation.DefaultTypeConverterFactory;
+import net.sourceforge.stripes.validation.DefaultValidationMetadataProvider;
 import net.sourceforge.stripes.validation.TypeConverterFactory;
+import net.sourceforge.stripes.validation.ValidationMetadataProvider;
 
 /**
  * <p>Centralized location for defaults for all Configuration properties.  
This implementation does
@@ -90,6 +92,7 @@
     private Map<LifecycleStage,Collection<Interceptor>> interceptors;
     private ExceptionHandler exceptionHandler;
     private MultipartWrapperFactory multipartWrapperFactory;
+    private ValidationMetadataProvider validationMetadataProvider;
 
     /** Gratefully accepts the BootstrapPropertyResolver handed to the 
Configuration. */
     public void setBootstrapPropertyResolver(BootstrapPropertyResolver 
resolver) {
@@ -176,6 +179,12 @@
                 this.multipartWrapperFactory.init(this);
             }
 
+            this.validationMetadataProvider = initValidationMetadataProvider();
+            if (this.validationMetadataProvider == null) {
+                this.validationMetadataProvider = new 
DefaultValidationMetadataProvider();
+                this.validationMetadataProvider.init(this);
+            }
+
             this.interceptors = new HashMap<LifecycleStage, 
Collection<Interceptor>>();
             Map<LifecycleStage, Collection<Interceptor>> map = 
initCoreInterceptors();
             if (map != null) {
@@ -347,6 +356,20 @@
     protected MultipartWrapperFactory initMultipartWrapperFactory() { return 
null; }
 
     /**
+     * Returns an instance of [EMAIL PROTECTED] ValidationMetadataProvider} 
that can be used by Stripes to
+     * determine what validations need to be applied during
+     * [EMAIL PROTECTED] LifecycleStage#BindingAndValidation}.
+     * 
+     * @return an instance of [EMAIL PROTECTED] ValidationMetadataProvider}
+     */
+    public ValidationMetadataProvider getValidationMetadataProvider() {
+        return this.validationMetadataProvider;
+    }
+
+    /** Allows subclasses to initialize a non-default [EMAIL PROTECTED] 
ValidationMetadataProvider}. */
+    protected ValidationMetadataProvider initValidationMetadataProvider() { 
return null; }
+
+    /**
      * Returns a list of interceptors that should be executed around the 
lifecycle stage
      * indicated.  By default returns a single element list containing the 
      * [EMAIL PROTECTED] BeforeAfterMethodInterceptor}.

Modified: 
trunk/stripes/src/net/sourceforge/stripes/config/RuntimeConfiguration.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/config/RuntimeConfiguration.java  
2007-12-11 06:23:42 UTC (rev 661)
+++ trunk/stripes/src/net/sourceforge/stripes/config/RuntimeConfiguration.java  
2007-12-11 17:22:44 UTC (rev 662)
@@ -35,6 +35,7 @@
 import net.sourceforge.stripes.util.ReflectUtil;
 import net.sourceforge.stripes.util.StringUtil;
 import net.sourceforge.stripes.validation.TypeConverterFactory;
+import net.sourceforge.stripes.validation.ValidationMetadataProvider;
 
 /**
  * <p>Configuration class that uses the BootstrapPropertyResolver to look for 
configuration values,
@@ -90,7 +91,10 @@
 
     /** The Configuration Key for looking up the name of the 
MultipartWrapperFactory class */
     public static final String MULTIPART_WRAPPER_FACTORY = 
"MultipartWrapperFactory.Class";
-    
+
+    /** The Configuration Key for looking up the name of the 
ValidationMetadataProvider class */
+    public static final String VALIDATION_METADATA_PROVIDER = 
"ValidationMetadataProvider.Class";
+
     /** The Configuration Key for looking up the comma separated list of core 
interceptor classes. */
     public static final String CORE_INTERCEPTOR_LIST = 
"CoreInterceptor.Classes";
 
@@ -163,6 +167,11 @@
         return initializeComponent(MultipartWrapperFactory.class, 
MULTIPART_WRAPPER_FACTORY);
     }
 
+    /** Looks for a class name in config and uses that to create the 
component. */
+    @Override protected ValidationMetadataProvider 
initValidationMetadataProvider() {
+        return initializeComponent(ValidationMetadataProvider.class, 
VALIDATION_METADATA_PROVIDER);
+    }
+
     /**
      * Looks for a list of class names separated by commas under the 
configuration key
      * [EMAIL PROTECTED] #CORE_INTERCEPTOR_LIST}.  White space surrounding the 
class names is trimmed,

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/BindingPolicyManager.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/controller/BindingPolicyManager.java  
    2007-12-11 06:23:42 UTC (rev 661)
+++ 
trunk/stripes/src/net/sourceforge/stripes/controller/BindingPolicyManager.java  
    2007-12-11 17:22:44 UTC (rev 662)
@@ -14,27 +14,25 @@
  */
 package net.sourceforge.stripes.controller;
 
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import net.sourceforge.stripes.action.ActionBean;
 import net.sourceforge.stripes.action.ActionBeanContext;
 import net.sourceforge.stripes.action.StrictBinding;
 import net.sourceforge.stripes.action.StrictBinding.Policy;
 import net.sourceforge.stripes.exception.StripesRuntimeException;
 import net.sourceforge.stripes.util.Log;
 import net.sourceforge.stripes.util.bean.PropertyExpressionEvaluation;
-import net.sourceforge.stripes.validation.Validate;
-import net.sourceforge.stripes.validation.ValidateNestedProperties;
+import net.sourceforge.stripes.validation.ValidationMetadata;
+import net.sourceforge.stripes.validation.ValidationMetadataProvider;
 
 /**
  * Manages the policies observed by [EMAIL PROTECTED] 
DefaultActionBeanPropertyBinder} when binding properties
@@ -181,96 +179,20 @@
     }
 
     /**
-     * Get all the properties and nested properties of the given class to 
which a [EMAIL PROTECTED] Validate}
-     * annotation is applied. The annotation may be applied to the property's 
read method, write
-     * method, or field declaration. If a property has a [EMAIL PROTECTED] 
ValidateNestedProperties}
-     * annotation, then the nested properties named in its [EMAIL PROTECTED] 
Validate} annotations will be
-     * included as well. However, the [EMAIL PROTECTED] 
ValidateNestedProperties} annotation alone is not
-     * sufficient to include a property; it must have its own [EMAIL 
PROTECTED] Validate} annotation.
+     * Get all the properties and nested properties of the given class for 
which there is a
+     * corresponding [EMAIL PROTECTED] ValidationMetadata}, as returned by
+     * [EMAIL PROTECTED] 
ValidationMetadataProvider#getValidationMetadata(Class, String)}. The idea here 
is
+     * that if the bean property must be validated, then it is expected that 
the property may be
+     * bound to the bean.
      * 
      * @param beanClass a class
      * @return The validated properties. If no properties are annotated then 
null.
+     * @see ValidationMetadataProvider#getValidationMetadata(Class)
      */
     protected String[] getValidatedProperties(Class<?> beanClass) {
-        // for the sake of pretty code, i declare these first
-        Method method;
-        Validate simple;
-        ValidateNestedProperties nested;
-        List<Validate> simples = new ArrayList<Validate>();
-        List<ValidateNestedProperties> nesteds = new 
ArrayList<ValidateNestedProperties>();
-        List<String> properties = new ArrayList<String>();
-        try {
-            PropertyDescriptor[] pds = 
Introspector.getBeanInfo(beanClass).getPropertyDescriptors();
-            for (PropertyDescriptor pd : pds) {
-                String propertyName = pd.getName();
-                simples.clear();
-                nesteds.clear();
-
-                // check getter method
-                method = pd.getReadMethod();
-                if (method != null) {
-                    simple = method.getAnnotation(Validate.class);
-                    nested = 
method.getAnnotation(ValidateNestedProperties.class);
-                    if (simple != null)
-                        simples.add(simple);
-                    if (nested != null)
-                        nesteds.add(nested);
-                }
-
-                // check setter method
-                method = pd.getWriteMethod();
-                if (method != null) {
-                    simple = method.getAnnotation(Validate.class);
-                    nested = 
method.getAnnotation(ValidateNestedProperties.class);
-                    if (simple != null)
-                        simples.add(simple);
-                    if (nested != null)
-                        nesteds.add(nested);
-                }
-
-                // check the field (possibly declared in a superclass) with 
the same
-                // name as the property for annotations
-                Field field = null;
-                for (Class<?> c = beanClass; c != null && field == null; c = 
c.getSuperclass()) {
-                    try {
-                        field = c.getDeclaredField(propertyName);
-                    }
-                    catch (NoSuchFieldException e) {
-                    }
-                }
-                if (field != null) {
-                    simple = field.getAnnotation(Validate.class);
-                    nested = 
field.getAnnotation(ValidateNestedProperties.class);
-                    if (simple != null)
-                        simples.add(simple);
-                    if (nested != null)
-                        nesteds.add(nested);
-                }
-
-                // add to allow list if @Validate present
-                if (!simples.isEmpty())
-                    properties.add(propertyName);
-
-                // add all sub-properties referenced in 
@ValidateNestedProperties
-                for (ValidateNestedProperties vnp : nesteds) {
-                    Validate[] validates = vnp.value();
-                    if (validates != null) {
-                        for (Validate validate : validates) {
-                            if (validate.field() != null) {
-                                properties.add(propertyName + '.' + 
validate.field());
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        catch (Exception e) {
-            log.error(e, "%%% Failure checking @Validate annotations ", 
getClass().getName());
-            StripesRuntimeException sre = new 
StripesRuntimeException(e.getMessage(), e);
-            sre.setStackTrace(e.getStackTrace());
-            throw sre;
-        }
-        return properties.size() == 0 ? null : properties.toArray(new 
String[properties.size()]);
+        Set<String> properties = 
StripesFilter.getConfiguration().getValidationMetadataProvider()
+                .getValidationMetadata(beanClass).keySet();
+        return new ArrayList<String>(properties).toArray(new 
String[properties.size()]);
     }
 
     /**

Modified: 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
   2007-12-11 06:23:42 UTC (rev 661)
+++ 
trunk/stripes/src/net/sourceforge/stripes/controller/DefaultActionBeanPropertyBinder.java
   2007-12-11 17:22:44 UTC (rev 662)
@@ -53,9 +53,6 @@
 public class DefaultActionBeanPropertyBinder implements 
ActionBeanPropertyBinder {
     private static final Log log = 
Log.getInstance(DefaultActionBeanPropertyBinder.class);
 
-    /** Map of validation annotations that is built at startup. */
-    private Map<Class<? extends ActionBean>, Map<String, ValidationMetadata>> 
validations;
-
     /** Configuration instance passed in at initialization time. */
     private Configuration configuration;
 
@@ -65,96 +62,9 @@
      */
     public void init(Configuration configuration) throws Exception {
         this.configuration = configuration;
-        Set<Class<? extends ActionBean>> beanClasses = 
ActionClassCache.getInstance()
-                .getActionBeanClasses();
-        this.validations = new HashMap<Class<? extends ActionBean>, 
Map<String, ValidationMetadata>>();
-
-        for (Class<? extends ActionBean> beanClass : beanClasses) {
-            Map<String, ValidationMetadata> fieldValidations = new 
HashMap<String, ValidationMetadata>();
-            processClassAnnotations(beanClass, fieldValidations);
-            this.validations.put(beanClass, fieldValidations);
-
-            // Print out a pretty debug message showing what validations got 
configured
-            StringBuilder builder = new StringBuilder(128);
-            for (Map.Entry<String, ValidationMetadata> entry : 
fieldValidations.entrySet()) {
-                if (builder.length() > 0) {
-                    builder.append(", ");
-                }
-                builder.append(entry.getKey());
-                builder.append("->");
-                builder.append(entry.getValue());
-            }
-
-            log.debug("Loaded validations for ActionBean ", 
beanClass.getSimpleName(), ": ",
-                    builder.length() > 0 ? builder : "<none>");
-        }
     }
 
     /**
-     * Helper method that processes a class looking for validation 
annotations. Will recurse and
-     * process the superclasses first in order to ensure that annotations 
lower down the inheritance
-     * hierarchy take precedence over those higher up.
-     * 
-     * @param clazz the ActionBean subclasses (or parent thereof) in question
-     * @param fieldValidations a map of fieldname->ValidationMetadata in which 
to store validations
-     */
-    protected void processClassAnnotations(Class<?> clazz,
-            Map<String, ValidationMetadata> fieldValidations) {
-        Class<?> superclass = clazz.getSuperclass();
-        if (superclass != null) {
-            processClassAnnotations(superclass, fieldValidations);
-        }
-
-        // Process the methods on the class
-        Method[] methods = clazz.getDeclaredMethods();
-        for (Method method : methods) {
-            if (!Modifier.isPublic(method.getModifiers())) {
-                continue; // only public methods!
-            }
-
-            processPropertyAnnotations(getPropertyName(method.getName()), 
method
-                    .getAnnotation(Validate.class), method
-                    .getAnnotation(ValidateNestedProperties.class), 
fieldValidations);
-        }
-
-        // Process the fields for validation annotations
-        Field[] fields = clazz.getDeclaredFields();
-        for (Field field : fields) {
-            processPropertyAnnotations(field.getName(), 
field.getAnnotation(Validate.class), field
-                    .getAnnotation(ValidateNestedProperties.class), 
fieldValidations);
-        }
-    }
-
-    /**
-     * Helper method that takes the name of a property on the ActionBean along 
with the property's
-     * (potentially null) Validate and ValidateNestedProperties annotations 
and stores the
-     * information in to the internval cache of validation information.
-     */
-    protected void processPropertyAnnotations(String name, Validate validate,
-            ValidateNestedProperties nested, Map<String, ValidationMetadata> 
fieldValidations) {
-        if (validate != null) {
-            fieldValidations.put(name, new ValidationMetadata(name, validate));
-        }
-
-        if (nested != null) {
-            Validate[] validations = nested.value();
-            for (Validate nestedValidate : validations) {
-                if ("".equals(nestedValidate.field())) {
-                    log.warn("Nested validation on field ", name,
-                            " used without nested field name: ", ReflectUtil
-                                    .toString(nestedValidate));
-                }
-                else {
-                    String nestedName = name + "." + nestedValidate.field();
-                    fieldValidations.put(nestedName, new 
ValidationMetadata(nestedName,
-                            nestedValidate));
-                }
-            }
-        }
-
-    }
-
-    /**
      * <p>
      * Loops through the parameters contained in the request and attempts to 
bind each one to the
      * supplied ActionBean. Invokes validation for each of the properties on 
the bean before binding
@@ -174,6 +84,8 @@
      */
     public ValidationErrors bind(ActionBean bean, ActionBeanContext context, 
boolean validate) {
         ValidationErrors fieldErrors = context.getValidationErrors();
+        Map<String, ValidationMetadata> validationInfos = this.configuration
+                
.getValidationMetadataProvider().getValidationMetadata(bean.getClass());
 
         // Take the ParameterMap and turn the keys into ParameterNames
         Map<ParameterName, String[]> parameters = getParameters(context);
@@ -210,8 +122,7 @@
                     log.trace("Running binding for property with name: ", 
name);
 
                     // Determine the target type
-                    ValidationMetadata validationInfo = 
this.validations.get(bean.getClass()).get(
-                            name.getStrippedName());
+                    ValidationMetadata validationInfo = 
validationInfos.get(name.getStrippedName());
                     PropertyExpressionEvaluation eval = new 
PropertyExpressionEvaluation(
                             PropertyExpression.getExpression(pname), bean);
                     Class<?> type = eval.getType();
@@ -520,24 +431,6 @@
     }
 
     /**
-     * Helper method that returns the name of the property when supplied with 
the corresponding get
-     * or set method. Does not do anything particularly intelligent, just 
drops the first three
-     * characters and makes the next character lower case.
-     */
-    protected String getPropertyName(String methodName) {
-        if (methodName.length() > 3
-                && (methodName.startsWith("get") || 
methodName.startsWith("set"))) {
-            return methodName.substring(3, 4).toLowerCase() + 
methodName.substring(4);
-        }
-        else if (methodName.length() > 2 && methodName.startsWith("is")) {
-            return methodName.substring(2, 3).toLowerCase() + 
methodName.substring(3);
-        }
-        else {
-            return "";
-        }
-    }
-
-    /**
      * Validates that all required fields have been submitted. This is done by 
looping through the
      * set of validation annotations and checking that each field marked as 
required was submitted
      * in the request and submitted with a non-empty value.
@@ -556,7 +449,8 @@
             }
         }
 
-        Map<String, ValidationMetadata> validationInfos = 
this.validations.get(bean.getClass());
+        Map<String, ValidationMetadata> validationInfos = this.configuration
+                
.getValidationMetadataProvider().getValidationMetadata(bean.getClass());
         StripesRequestWrapper req = 
StripesRequestWrapper.findStripesWrapper(bean.getContext()
                 .getRequest());
 
@@ -738,12 +632,13 @@
     protected void doPostConversionValidations(ActionBean bean,
             Map<ParameterName, List<Object>> convertedValues, ValidationErrors 
errors) {
 
+        Map<String, ValidationMetadata> validationInfos = this.configuration
+                
.getValidationMetadataProvider().getValidationMetadata(bean.getClass());
         for (Map.Entry<ParameterName, List<Object>> entry : 
convertedValues.entrySet()) {
             // Sort out what we need to validate this field
             ParameterName name = entry.getKey();
             List<Object> values = entry.getValue();
-            ValidationMetadata validationInfo = 
this.validations.get(bean.getClass()).get(
-                    name.getStrippedName());
+            ValidationMetadata validationInfo = 
validationInfos.get(name.getStrippedName());
 
             if (values.size() == 0 || validationInfo == null) {
                 continue;
@@ -875,7 +770,7 @@
      *         not guaranteed to be the same length as the values array passed 
in.
      */
     @SuppressWarnings("unchecked")
-    private List<Object> convert(ActionBean bean, ParameterName propertyName, 
String[] values,
+    protected List<Object> convert(ActionBean bean, ParameterName 
propertyName, String[] values,
             Class propertyType, ValidationMetadata validationInfo, 
List<ValidationError> errors)
             throws Exception {
 

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java
                         (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/DefaultValidationMetadataProvider.java
 2007-12-11 17:22:44 UTC (rev 662)
@@ -0,0 +1,177 @@
+/* Copyright 2007 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.validation;
+
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.config.Configuration;
+import net.sourceforge.stripes.exception.StripesRuntimeException;
+import net.sourceforge.stripes.util.Log;
+
+/**
+ * Provides a globally accessible cache of validation metadata for properties 
of [EMAIL PROTECTED] ActionBean}
+ * classes.
+ * 
+ * @author Ben Gunter
+ * @since Stripes 1.5
+ */
+public class DefaultValidationMetadataProvider implements 
ValidationMetadataProvider {
+    private static final Log log = 
Log.getInstance(DefaultValidationMetadataProvider.class);
+    private Configuration configuration;
+
+    /** Map class -> field -> validation meta data */
+    private final Map<Class<?>, Map<String, ValidationMetadata>> cache = new 
HashMap<Class<?>, Map<String, ValidationMetadata>>();
+
+    /** Currently does nothing except store a reference to [EMAIL PROTECTED] 
configuration}. */
+    public void init(Configuration configuration) throws Exception {
+        this.configuration = configuration;
+    }
+
+    /** Get the [EMAIL PROTECTED] Configuration} object that was passed into 
[EMAIL PROTECTED] #init(Configuration)}. */
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public Map<String, ValidationMetadata> getValidationMetadata(Class<?> 
beanType) {
+        Map<String, ValidationMetadata> meta = cache.get(beanType);
+        if (meta == null) {
+            meta = loadForClass(beanType);
+            cache.put(beanType, meta);
+        }
+
+        return meta;
+    }
+
+    public ValidationMetadata getValidationMetadata(Class<?> beanType, String 
field) {
+        return getValidationMetadata(beanType).get(field);
+    }
+
+    /**
+     * Get validation information for all the properties and nested properties 
of the given class.
+     * The [EMAIL PROTECTED] Validate} and/or [EMAIL PROTECTED] 
ValidateNestedProperties} annotations may be applied to
+     * the property's read method, write method, or field declaration. If a 
property has a
+     * [EMAIL PROTECTED] ValidateNestedProperties} annotation, then the nested 
properties named in its
+     * [EMAIL PROTECTED] Validate} annotations will be included as well.
+     * 
+     * @param clazz a class
+     * @return A map of (possibly nested) property names to [EMAIL PROTECTED] 
ValidationMetadata} for the
+     *         property.
+     */
+    protected Map<String, ValidationMetadata> loadForClass(Class<?> clazz) {
+        Map<String, ValidationMetadata> meta = new HashMap<String, 
ValidationMetadata>();
+        try {
+            PropertyDescriptor[] pds = 
Introspector.getBeanInfo(clazz).getPropertyDescriptors();
+            for (PropertyDescriptor pd : pds) {
+                String propertyName = pd.getName();
+                Validate simple = null;
+                ValidateNestedProperties nested = null;
+
+                // check getter method
+                Method method = pd.getReadMethod();
+                if (method != null && 
Modifier.isPublic(method.getModifiers())) {
+                    simple = method.getAnnotation(Validate.class);
+                    nested = 
method.getAnnotation(ValidateNestedProperties.class);
+                }
+
+                // check setter method
+                method = pd.getWriteMethod();
+                if (method != null && 
Modifier.isPublic(method.getModifiers())) {
+                    if (simple == null)
+                        simple = method.getAnnotation(Validate.class);
+                    if (nested == null)
+                        nested = 
method.getAnnotation(ValidateNestedProperties.class);
+                }
+
+                // check the field (possibly declared in a superclass) with 
the same
+                // name as the property for annotations
+                Field field = null;
+                for (Class<?> c = clazz; c != null && field == null; c = 
c.getSuperclass()) {
+                    try {
+                        field = c.getDeclaredField(propertyName);
+                    }
+                    catch (NoSuchFieldException e) {
+                    }
+                }
+                if (field != null) {
+                    if (simple == null)
+                        simple = field.getAnnotation(Validate.class);
+                    if (nested == null)
+                        nested = 
field.getAnnotation(ValidateNestedProperties.class);
+                }
+
+                // add to allow list if @Validate present
+                if (simple != null) {
+                    if (simple.field() != null) {
+                        meta.put(propertyName, new 
ValidationMetadata(propertyName, simple));
+                    }
+                    else {
+                        log.warn("Field name present in @Validate but should 
be omitted: ", clazz,
+                                ", property ", propertyName, ", given field 
name ", simple.field());
+                    }
+                }
+
+                // add all sub-properties referenced in 
@ValidateNestedProperties
+                if (nested != null) {
+                    Validate[] validates = nested.value();
+                    if (validates != null) {
+                        for (Validate validate : validates) {
+                            if (validate.field() != null) {
+                                String fullName = propertyName + '.' + 
validate.field();
+                                meta.put(fullName, new 
ValidationMetadata(fullName, validate));
+                            }
+                            else {
+                                log.warn("Field name missing from nested 
@Validate: ", clazz,
+                                        ", property ", propertyName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        catch (RuntimeException e) {
+            log.error(e, "Failure checking @Validate annotations ", 
getClass().getName());
+            throw e;
+        }
+        catch (Exception e) {
+            log.error(e, "Failure checking @Validate annotations ", 
getClass().getName());
+            StripesRuntimeException sre = new 
StripesRuntimeException(e.getMessage(), e);
+            sre.setStackTrace(e.getStackTrace());
+            throw sre;
+        }
+
+        // Print out a pretty debug message showing what validations got 
configured
+        StringBuilder builder = new StringBuilder(128);
+        for (Map.Entry<String, ValidationMetadata> entry : meta.entrySet()) {
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(entry.getKey());
+            builder.append("->");
+            builder.append(entry.getValue());
+        }
+        log.debug("Loaded validations for ActionBean ", clazz.getSimpleName(), 
": ", builder
+                .length() > 0 ? builder : "<none>");
+
+        return Collections.unmodifiableMap(meta);
+    }
+}

Added: 
trunk/stripes/src/net/sourceforge/stripes/validation/ValidationMetadataProvider.java
===================================================================
--- 
trunk/stripes/src/net/sourceforge/stripes/validation/ValidationMetadataProvider.java
                                (rev 0)
+++ 
trunk/stripes/src/net/sourceforge/stripes/validation/ValidationMetadataProvider.java
        2007-12-11 17:22:44 UTC (rev 662)
@@ -0,0 +1,49 @@
+/* Copyright 2007 Ben Gunter
+ *
+ * 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 net.sourceforge.stripes.validation;
+
+import java.util.Map;
+
+import net.sourceforge.stripes.action.ActionBean;
+import net.sourceforge.stripes.config.ConfigurableComponent;
+
+/**
+ * 
+ * 
+ * @author Ben Gunter
+ * @since Stripes 1.5
+ */
+public interface ValidationMetadataProvider extends ConfigurableComponent {
+    /**
+     * Get a map of property names to [EMAIL PROTECTED] ValidationMetadata} 
for the given [EMAIL PROTECTED] ActionBean}
+     * class.
+     * 
+     * @param beanType any class
+     * @return A map of property names to [EMAIL PROTECTED] 
ValidationMetadata}. If no validation information
+     *         is present for the given class, then an empty map will be 
returned.
+     */
+    Map<String, ValidationMetadata> getValidationMetadata(Class<?> beanType);
+
+    /**
+     * Get the validation metadata associated with the named [EMAIL PROTECTED] 
property} of the given
+     * [EMAIL PROTECTED] ActionBean} class.
+     * 
+     * @param beanType any class
+     * @param property a (possibly nested) property of [EMAIL PROTECTED] 
beanType}
+     * @return A [EMAIL PROTECTED] ValidationMetadata} object, if there is one 
associated with the property. If
+     *         the property is not to be validated, then null.
+     */
+    ValidationMetadata getValidationMetadata(Class<?> beanType, String 
property);
+}


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
SF.Net email is sponsored by: 
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services for
just about anything Open Source.
http://sourceforge.net/services/buy/index.php
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to